микросервисы go-kit: ограничение тока

Микросервисы
микросервисы go-kit: ограничение тока

Из-за ограниченной грузоподъемности системы бизнес-приложений, чтобы непреднамеренные запросы не оказывали слишком большой нагрузки на систему и тормозили систему бизнес-приложений, каждый интерфейс API имеет ограничение доступа. Политики управления трафиком для интерфейсов API: шунтирование, переход на более раннюю версию и ограничение тока. В этой статье обсуждается текущая стратегия ограничения.Хотя частота доступа и параллелизм интерфейса службы снижены, они заменены высокой доступностью интерфейса службы и системы бизнес-приложений.

Целью текущего ограничения является защита системы путем ограничения скорости одновременного доступа/запросов или ограничения скорости запросов в пределах временного окна.

Алгоритм ограничения тока

Существует два широко используемых алгоритма ограничения тока: алгоритм дырявого ведра и алгоритм ведра с маркерами.

алгоритм дырявого ведра

Алгоритм дырявого ведра — это алгоритм, который часто используется при формировании трафика или ограничении скорости в сетевом мире для отправки трафика. Алгоритм дырявого ведра обеспечивает механизм, с помощью которого пакетный трафик может формироваться для обеспечения стабильного потока в сети.

Идея алгоритма дырявого ведра очень проста, вода (запрос) сначала поступает в дырявое ведро, а дырявое ведро выходит из воды с определенной скоростью (у интерфейса есть скорость отклика), а когда скорость притока воды слишком высок, он будет переполняться напрямую (частота доступа превышает скорость отклика интерфейса), а затем, пока запрос отклонен, видно, что алгоритм дырявого ведра может принудительно ограничивать скорость передачи данных. Схематическая диаграмма выглядит следующим образом:

Поскольку скорость утечки дырявого ведра является фиксированным параметром, даже если в сети нет конфликта ресурсов (нет перегрузки), алгоритм дырявого ведра не может увеличить скорость потока до скорости порта. Таким образом, алгоритм дырявого ведра неэффективен для трафика с пакетными характеристиками.

Алгоритм ведра токенов

Алгоритм Token Bucket — один из наиболее часто используемых алгоритмов в формировании сетевого трафика (Traffic Shaping) и ограничении скорости (Rate Limiting). Как правило, алгоритм корзины маркеров используется для управления объемом данных, отправляемых в сеть, и для разрешения отправки пакетов данных.

Принцип алгоритма ведра токенов заключается в том, что система будет помещать токены в ведро с постоянной скоростью, и если запрос необходимо обработать, то сначала нужно получить токен из ведра. ведро, затем отказ в обслуживании. С принципиальной точки зрения, алгоритм ведра с токенами и алгоритм с дырявым ведром противоположны, один — «вода внутри», другой — «утечка».

Еще одним преимуществом ведер с токенами является простота изменения скорости. Как только скорость необходимо увеличить, скорость токенов, помещаемых в корзину, увеличивается по запросу. Как правило, определенное количество токенов добавляется в корзину через равные промежутки времени (например, 100 миллисекунд), а некоторые варианты алгоритмов рассчитывают количество токенов, которые следует добавить, в режиме реального времени.

Реализация ограничения тока микросервиса Go-kit

На основе вышеприведенного анализа буду реализовывать текущую ограничивающую функцию микросервисов на основе go-kit. консультируясьgokit/kit/ratelimitИсходный код, обнаружил, что gokit основан на пакете gogolang.org/x/time/rateЕсть встроенная реализация, кроме того, ранее gokit использовал дефолтныйjuju/ratelimitСхему реализации (которая в настоящее время официально удалена), я буду реализовывать на основе двух методов.

В отличие от двух предыдущих статей, эта реализация будет основана на встроенных типах gokit.endpoint.Middleware, тип на самом деле является функцией, которая использует шаблон декоратора для инкапсуляции конечной точки. Определяется следующим образом:

# Go-kit Middleware Endpoint
type Middleware func(Endpoint) Endpoint

схема juju/ratelimit

Примером в этой статье будет продолжать улучшаться на основе Кодекса предыдущей статьи (в конце прилагаемого адреса) первые две статьи забыли поставить адрес.

Шаг 1: Создайте текущий ограничитель

Сначала установите последнюю версию, используя следующую командуjuju/ratelimitБиблиотеки:

go get github.com/juju/ratelimit

Затем создайте новый файл go с именемinstrument.go, чтобы реализовать текущий метод ограничения: параметр — возвращаемое ведро токенов (bkt).endpoint.Middleware. с помощью ведра токеновTakeAvaiableМетод получает токен и, если получение прошло успешно, продолжает выполнение, а если получение не удалось, возвращает исключение (то есть ограничение тока). код показывает, как показано ниже:

var ErrLimitExceed = errors.New("Rate limit exceed!")

// NewTokenBucketLimitterWithJuju 使用juju/ratelimit创建限流中间件
func NewTokenBucketLimitterWithJuju(bkt *ratelimit.Bucket) endpoint.Middleware {
	return func(next endpoint.Endpoint) endpoint.Endpoint {
		return func(ctx context.Context, request interface{}) (response interface{}, err error) {
			if bkt.TakeAvailable(1) == 0 {
				return nil, ErrLimitExceed
			}
			return next(ctx, request)
		}
	}
}

Шаг 2: Изменить основной

вниз использоватьjuju/ratelimitСоздайте ведро токенов (обновляется каждую секунду с емкостью 3), затем вызовитеStep-1Реализуйте текущий метод ограничения, чтобы украсить конечную точку. Добавьте следующий код в основной метод.

// add ratelimit,refill every second,set capacity 3
ratebucket := ratelimit.NewBucket(time.Second*1, 3)
endpoint = NewTokenBucketLimitterWithJuju(ratebucket)(endpoint)

После модификации полный код выглядит следующим образом:

func main() {

	ctx := context.Background()
	errChan := make(chan error)

	var logger log.Logger
	{
		logger = log.NewLogfmtLogger(os.Stderr)
		logger = log.With(logger, "ts", log.DefaultTimestampUTC)
		logger = log.With(logger, "caller", log.DefaultCaller)
	}

	var svc Service
	svc = ArithmeticService{}

	// add logging middleware
	svc = LoggingMiddleware(logger)(svc)

	endpoint := MakeArithmeticEndpoint(svc)

	// add ratelimit,refill every second,set capacity 3
	ratebucket := ratelimit.NewBucket(time.Second*1, 3)
	endpoint = NewTokenBucketLimitterWithJuju(ratebucket)(endpoint)

	r := MakeHttpHandler(ctx, endpoint, logger)

	go func() {
		fmt.Println("Http Server start at port:9000")
		handler := r
		errChan <- http.ListenAndServe(":9000", handler)
	}()

	go func() {
		c := make(chan os.Signal, 1)
		signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
		errChan <- fmt.Errorf("%s", <-c)
	}()

	fmt.Println(<-errChan)
}

Шаг 3: Скомпилируйте и запустите

Скомпилируйте и запустите приложение на консоли, а затем протестируйте его через интерфейс запроса Postman, вы можете увидеть информацию журнала вывода:

ts=2019-02-19T03:20:13.1908613Z caller=logging.go:41 function=Subtract a=10 b=1 result=9 took=0s
ts=2019-02-19T03:20:13.7144627Z caller=logging.go:41 function=Subtract a=10 b=1 result=9 took=0s
ts=2019-02-19T03:20:14.2276079Z caller=logging.go:41 function=Subtract a=10 b=1 result=9 took=0s
ts=2019-02-19T03:20:14.7414288Z caller=server.go:112 err="Rate limit exceed!"
ts=2019-02-19T03:20:15.2091773Z caller=logging.go:41 function=Subtract a=10 b=1 result=9 took=0s
ts=2019-02-19T03:20:16.0261559Z caller=logging.go:41 function=Subtract a=10 b=1 result=9 took=0s
ts=2019-02-19T03:20:16.6406654Z caller=server.go:112 err="Rate limit exceed!"
ts=2019-02-19T03:20:17.1912533Z caller=logging.go:41 function=Subtract a=10 b=1 result=9 took=0s
ts=2019-02-19T03:20:17.7828906Z caller=server.go:112 err="Rate limit exceed!"

Как видно из лога, запрос появляетсяRate limit exceed!, то есть текущий ограничитель прервет запрос после выдачи токена, и сервис будет недоступен, при продолжении доступа сервис возобновится, то есть текущий ограничитель возобновит заполнение корзины токенов.

Встроенная реализация Gokit

Шаг 1: Создайте текущий ограничитель

Сначала скачайте зависимый go/time/rateПакет устанавливается следующим образом (команду go get нельзя использовать напрямую):

git clone https://github.com/golang/time.git [Your GOPATH]/src/golang.org/x

затем вinstrument.goдобавить методNewTokenBucketLimitterWithBuildIn, в котором используетсяx/time/rateРеализовать метод ограничения тока:

// NewTokenBucketLimitterWithBuildIn 使用x/time/rate创建限流中间件
func NewTokenBucketLimitterWithBuildIn(bkt *rate.Limiter) endpoint.Middleware {
	return func(next endpoint.Endpoint) endpoint.Endpoint {
		return func(ctx context.Context, request interface{}) (response interface{}, err error) {
			if !bkt.Allow() {
				return nil, ErrLimitExceed
			}
			return next(ctx, request)
		}
	}
}

Шаг 2: Изменить основной

Измените текущую инкапсуляцию метода ограничения на следующую реализацию:

//add ratelimit,refill every second,set capacity 3
ratebucket := rate.NewLimiter(rate.Every(time.Second*1), 3)
endpoint = NewTokenBucketLimitterWithBuildIn(ratebucket)(endpoint)

Шаг 3: Скомпилируйте и запустите

Скомпилируйте и запустите приложение на консоли, а затем протестируйте его через интерфейс запроса Postman, вы можете увидеть информацию журнала вывода:

ts=2019-02-19T06:03:26.8650217Z caller=logging.go:41 function=Subtract a=10 b=1 result=9 took=0s
ts=2019-02-19T06:03:27.5747177Z caller=logging.go:41 function=Subtract a=10 b=1 result=9 took=0s
ts=2019-02-19T06:03:28.1274404Z caller=logging.go:41 function=Subtract a=10 b=1 result=9 took=0s
ts=2019-02-19T06:03:28.5892068Z caller=logging.go:41 function=Subtract a=10 b=1 result=9 took=0s
ts=2019-02-19T06:03:29.1327522Z caller=server.go:112 err="Rate limit exceed!"
ts=2019-02-19T06:03:29.59453Z caller=logging.go:41 function=Subtract a=10 b=1 result=9 took=0s
ts=2019-02-19T06:03:30.2138805Z caller=server.go:112 err="Rate limit exceed!"
ts=2019-02-19T06:03:30.6257682Z caller=logging.go:41 function=Subtract a=10 b=1 result=9 took=0s
ts=2019-02-19T06:03:31.2772011Z caller=server.go:112 err="Rate limit exceed!"

Из лога видно, что эффект тот же, что иjuju/ratelimitТот же план.

Суммировать

В этой статье сначала представлены два широко используемых алгоритма ограничения тока, алгоритм дырявого ведра и алгоритм ведра с маркерами, а затем используются две схемы (juju/ratelimitи встроенная библиотека gokit) для реализации ограничения тока службы.

В процессе разработки сервиса нам необходимо полностью учитывать доступность сервисов, особенно тех сервисов, которые потребляют системные ресурсы, и добавить текущий механизм ограничения для обеспечения стабильной и надежной работы сервисов.

Картинки из интернета.

Эта статья была впервые опубликована в моей публичной учетной записи WeChat [Xi Yiang Bar], пожалуйста, отсканируйте код, чтобы следовать!