С ростом популярности микросервисов зависимости и отношения вызовов между сервисами становятся все более сложными, а стабильность сервисов становится особенно важной. Бизнес-сценарии часто включают в себя мгновенное воздействие на трафик, что может привести к тайм-аутам запросов и ответов или даже к перегрузке, отключению и недоступности сервера. Чтобы защитить саму систему, а также восходящие и нисходящие службы, мы обычно ограничиваем поток запросов и быстро отклоняем запросы, которые превышают верхний предел конфигурации, чтобы обеспечить стабильность системы или вышестоящих и нисходящих сервисных систем. Разумные стратегии могут эффективно справляться с последствиями дорожного движения и обеспечивать доступность и производительность системы. В этой статье подробно представлены несколько алгоритмов ограничения тока, сравниваются преимущества и недостатки каждого алгоритма, даются некоторые предложения по выбору алгоритмов ограничения тока, а также предлагаются некоторые решения для ограничения распределенного тока, обычно используемые в бизнесе.
Хорошая конструкция ограничения тока должна учитывать характеристики и потребности бизнеса и иметь следующие шесть пунктов:
Существует три основных инструмента для защиты стабильности высокопараллельных сервисов: кэширование, понижение версии и ограничение тока.
Каждый из этих трех «острых инструментов» имеет свои особенности и обычно используется в сочетании для достижения наилучших результатов. Например, кэширование можно использовать для уменьшения доступа к базе данных, переход на более раннюю версию можно использовать для борьбы с сбоями системы, а ограничение тока можно использовать для предотвращения перегрузки системы. При проектировании системы с высоким уровнем параллелизма эти технологии необходимо использовать рационально, исходя из конкретных потребностей и характеристик системы. Далее в этой статье будут представлены некоторые современные методы ограничения, обычно используемые в отрасли.
Ограничение тока — это ключевое техническое средство ограничения количества запросов или одновременного выполнения с целью обеспечения нормальной работы системы. Когда ресурсы службы и возможности обработки ограничены, ограничение потока может ограничить восходящие запросы на вызов служб, чтобы предотвратить остановку службы из-за исчерпания ресурсов.
При ограничении тока необходимо понимать две важные концепции:
Устанавливая разумные пороговые значения и выбирая соответствующие стратегии отклонения, технология ограничения тока может помочь системе справиться с внезапными скачками объема запросов, злонамеренным доступом пользователей или чрезмерной частотой запросов, обеспечивая стабильность и доступность системы. Схемы ограничения тока можно разделить на Ограничение тока одной машины и ограничение распределенного тока;Чтосередина Автономный Ограничение токав соответствии салгоритм,также можно разделить на фиксированное окно、раздвижное окно, дырявая бочка и ведро жетонов токоограничивающий и другие четыре распространенных типа。本文将верно上述Ограничение тока План представлен подробно。
3.1 Фиксированное ограничение тока окна
3.1.1 Введение в алгоритм
Алгоритм фиксированного окна — это простой и интуитивно понятный алгоритм ограничения тока. Его принцип состоит в том, чтобы разделить время на окна фиксированного размера и ограничить количество или скорость запросов в каждом окне. В конкретной реализации счетчик может использоваться для записи количества запросов в текущем окне и сравнения его с заданным пороговым значением. Принцип алгоритма фиксированного окна заключается в следующем:
3.1.2 Реализация кода
type FixedWindowLimiter struct {
windowSize time.Duration // размер окна
maxRequests int // Максимальное количество запросов
requests int // Количество запросов в текущем окне
lastReset int64 // Время сброса последнего окна (метка времени второго уровня)
resetMutex sync.Mutex // сбросить блокировку
}
func NewFixedWindowLimiter(windowSize time.Duration, maxRequests int) *FixedWindowLimiter {
return &FixedWindowLimiter{
windowSize: windowSize,
maxRequests: maxRequests,
lastReset: time.Now().Unix(),
}
}
func (limiter *FixedWindowLimiter) AllowRequest() bool {
limiter.resetMutex.Lock()
defer limiter.resetMutex.Unlock()
// Проверьте, нужно ли сбросить окно
if time.Now().Unix()-limiter.lastReset >= int64(limiter.windowSize.Seconds()) {
limiter.requests = 0
limiter.lastReset = time.Now().Unix()
}
// Проверьте, превышает ли количество запросов порог
if limiter.requests >= limiter.maxRequests {
return false
}
limiter.requests++
return true
}
func main() {
limiter := NewFixedWindowLimiter(1*time.Second, 3) // Разрешить до 3 запросов в секунду
for i := 0; i < 15; i++ {
now := time.Now().Format("15:04:05")
if limiter.AllowRequest() {
fmt.Println(now + " Запрос через ")
} else {
fmt.Println(now + " Запросы ограничиваются")
}
time.Sleep(100 * time.Millisecond)
}
}
Результат выполнения:
3.1.3 Преимущества и недостатки
преимущество:
недостаток:
Алгоритм с фиксированным окном подходит для сценариев, где существуют четкие требования к скорости запросов и трафик относительно стабилен. Однако в ситуациях, когда пакетный трафик и запросы распределяются неравномерно, может потребоваться рассмотреть другие более гибкие алгоритмы ограничения тока.
3.2 Ограничение тока скользящего окна
3.2.1 Знакомство с алгоритмом
Выше было объяснено, что при возникновении критических изменений во временном окне алгоритм фиксированного окна может оказаться неспособным гибко реагировать на изменения в трафике. Например, если в конце временного окна внезапно появляется большое количество запросов, алгоритм фиксированного окна может привести к отклонению запросов, даже если в следующем временном окне запросов не так много. В этом случае нам нужен более гибкий алгоритм для борьбы с пакетным трафиком и неравномерным распределением запросов — алгоритм скользящего окна. Этот алгоритм является усовершенствованием алгоритма фиксированного окна, который динамически регулирует размер окна, чтобы лучше адаптироваться к изменениям трафика. В отличие от алгоритма фиксированного окна, алгоритм скользящего окна может регулировать размер окна перед появлением следующего временного окна, чтобы лучше контролировать скорость запросов. Принцип алгоритма следующий:
3.2.2 Реализация кода
package main
import (
"fmt"
"sync"
"time"
)
type SlidingWindowLimiter struct {
windowSize time.Duration // размер окна
maxRequests int // Максимальное количество запросов
requests []time.Time // Время запроса в пределах окна
requestsLock sync.Mutex // запросить блокировку
}
func NewSlidingWindowLimiter(windowSize time.Duration, maxRequests int) *SlidingWindowLimiter {
return &SlidingWindowLimiter{
windowSize: windowSize,
maxRequests: maxRequests,
requests: make([]time.Time, 0),
}
}
func (limiter *SlidingWindowLimiter) AllowRequest() bool {
limiter.requestsLock.Lock()
defer limiter.requestsLock.Unlock()
// Удалить запросы с истекшим сроком действия
currentTime := time.Now()
for len(limiter.requests) > 0 && currentTime.Sub(limiter.requests[0]) > limiter.windowSize {
limiter.requests = limiter.requests[1:]
}
// Проверьте, превышает ли количество запросов порог
if len(limiter.requests) >= limiter.maxRequests {
return false
}
limiter.requests = append(limiter.requests, currentTime)
return true
}
func main() {
limiter := NewSlidingWindowLimiter(500*time.Millisecond, 2) // Разрешить до 4 запросов в секунду
for i := 0; i < 15; i++ {
now := time.Now().Format("15:04:05")
if limiter.AllowRequest() {
fmt.Println(now + " Запрос через ")
} else {
fmt.Println(now + " Запросы ограничиваются")
}
time.Sleep(100 * time.Millisecond)
}
}
Результат выполнения:
3.2.3 Преимущества и недостатки
преимущество:
недостаток:
С точки зрения кода мы можем обнаружить, что алгоритм скользящего окна на самом деле представляет собой алгоритм фиксированного окна с меньшей детализацией. Он может в определенной степени повысить точность и производительность ограничения тока в реальном времени, но не может фундаментально решить проблему неравномерности. распространение запроса. Алгоритм ограничен размером окна и временным интервалом. Особенно в крайних случаях, например, когда пакетный трафик слишком велик или распределение запросов крайне неравномерно, это все равно может привести к неточному ограничению тока. Поэтому в практических приложениях необходимо использовать более сложные алгоритмы или стратегии для дальнейшей оптимизации эффекта ограничения тока.
3.3 Ограничение тока утечки ведра
3.3.1 Знакомство с алгоритмом
хотяраздвижное Оконныйалгоритм может обеспечить определенный эффект ограничения потока, но он по-прежнему ограничен размером окна и временным интервалом. В некоторых случаях пакет может привести к превышению лимита количества запросов в окне. Для лучшего сглаживания потока требуется Ограничение тока дырявого ведраалгоритма может быть использовано как усовершенствование алгоритма раздвижного окна. Принцип алгоритма прост: он поддерживает дырявое ведро с фиксированной емкостью.,Запросы попадают в дырявое ведро с переменной скоростью.,Дырявое ведро вытекает с фиксированной скоростью. Если запрос поступил,Дырявое ведро полно,но Политика запрета будет активирована。Можно получить измодель производитель-потребительчтобы понять этоалгоритм,Просьба выступить в роли продюсера,Каждая просьба как капля воды,когда поступит запрос,Он ставится в очередь (дырявое ведро) середина, а дырявое ведро выступает в роли потребителя.,Потребляйте запросы из очереди с фиксированной скоростью.,Как вода, капающая из дырок на дне бочки. Скорость потребления равна порогу ограничения тока,Например, обрабатывать 2 запроса в секунду.,То есть на обработку одного запроса уходит 500 миллисекунд. Утечка емкости ковшакак очередьиземкость,Когда количество запросов превышает указанную емкость,Политика запрета будет активирована,То есть вновь поступающие запросы будут отбрасываться или задерживаться. Реализация алгоритма следующая:
3.3.2 Реализация кода
package main
import (
"fmt"
"time"
)
type LeakyBucket struct {
rate float64 // Частота дырявого сегмента, количество запросов в секунду
capacity int // Емкость дырявого сегмента, максимальное количество запросов, которые можно сохранить.
water int // Текущий объем воды указывает количество запросов в текущем дырявом ведре.
lastLeakMs int64 // Временная метка последней утечки воды, в секундах
}
func NewLeakyBucket(rate float64, capacity int) *LeakyBucket {
return &LeakyBucket{
rate: rate,
capacity: capacity,
water: 0,
lastLeakMs: time.Now().Unix(),
}
}
func (lb *LeakyBucket) Allow() bool {
now := time.Now().Unix()
elapsed := now - lb.lastLeakMs
// Утечка воды, рассчитайте количество утекшей воды в зависимости от временного интервала.
leakAmount := int(float64(elapsed) / 1000 * lb.rate)
if leakAmount > 0 {
if leakAmount > lb.water {
lb.water = 0
} else {
lb.water -= leakAmount
}
}
// Определите, превышает ли текущий объем воды емкость
if lb.water > lb.capacity {
lb.water-- // Если емкость превышена, вычтите количество только что добавленной воды.
return false
}
// увеличить объем воды
lb.water++
lb.lastLeakMs = now
return true
}
func main() {
// Создайте дырявое ведро со скоростью 3 запроса в секунду и емкостью 4 запроса.
leakyBucket := NewLeakyBucket(3, 4)
// Запрос на моделирование
for i := 1; i <= 15; i++ {
now := time.Now().Format("15:04:05")
if leakyBucket.Allow() {
fmt.Printf(now+" Нет. %d запросы переданы\n", i)
} else {
fmt.Printf(now+" Нет. %d запросы ограничены\n", i)
}
time.Sleep(200 * time.Millisecond) // Запрос на моделированиеинтервал
}
}
Результат выполнения:
3.3.3 Преимущества и недостатки
преимущество:
недостаток:
3.4 Ограничение тока корзины токенов
3.4.1 Знакомство с алгоритмом
Алгоритм ведра токенов — это распространенная идея реализации ограничения тока, которая используется для ограничения скорости запросов. Это гарантирует, что система по-прежнему сможет предоставлять стабильные услуги в условиях высокой нагрузки, и предотвращает перегрузку системы пакетным трафиком. Текущий класс инструмента ограничения RateLimiter в наиболее часто используемом наборе инструментов разработки Google Java Guava представляет собой реализацию корзины токенов. Алгоритм корзины токенов основан на концепции корзины токенов, где токены генерируются с фиксированной скоростью и помещаются в корзину. Каждый токен представляет запрошенное разрешение. Когда поступает запрос, для его передачи необходимо получить токен из корзины токенов. Если в корзине токенов недостаточно токенов, запрос регулируется или отбрасывается. Шаги реализации алгоритма корзины токенов следующие:
3.4.2 Реализация кода
package main
import (
"fmt"
"sync"
"time"
)
// TokenBucket означает ведро жетонов。
type TokenBucket struct {
rate float64 // Скорость, с которой токены добавляются в корзину.
capacity float64 // Максимальная вместимость ковша.
tokens float64 // Количество токенов, находящихся в данный момент в корзине.
lastUpdate time.Time // Время последнего обновления суммы токена.
mu sync.Mutex // Блокировка мьютекса для обеспечения потокобезопасности.
}
// NewTokenBucket Создать новое ведро жетонов, учитывая скорость добавления жетонов и емкость ведра.
func NewTokenBucket(rate float64, capacity float64) *TokenBucket {
return &TokenBucket{
rate: rate,
capacity: capacity,
tokens: capacity, // Изначально ведро полное.
lastUpdate: time.Now(),
}
}
// Allow Проверьте, можно ли удалить токен из корзины. Если это возможно, он удаляет токен и возвращает true。
// Если нет, он возвращает false。
func (tb *TokenBucket) Allow() bool {
tb.mu.Lock()
defer tb.mu.Unlock()
// Рассчитайте количество токенов, которые нужно добавить, исходя из затраченного времени.
now := time.Now()
elapsed := now.Sub(tb.lastUpdate).Seconds()
tokensToAdd := elapsed * tb.rate
tb.tokens += tokensToAdd
if tb.tokens > tb.capacity {
tb.tokens = tb.capacity // Убедитесь, что количество жетонов не превышает вместимости корзины.
}
if tb.tokens >= 1.0 {
tb.tokens--
tb.lastUpdate = now
return true
}
return false
}
func main() {
tokenBucket := NewTokenBucket(2.0, 3.0)
for i := 1; i <= 10; i++ {
now := time.Now().Format("15:04:05")
if tokenBucket.Allow() {
fmt.Printf(now+" Нет. %d запросы переданы\n", i)
} else { // Если токен невозможно удалить, запрос отклоняется.
fmt.Printf(now+" Нет. %d запросы ограничены\n", i)
}
time.Sleep(200 * time.Millisecond)
}
}
Результат выполнения:
3.4.3 Преимущества и недостатки
преимущество:
недостаток:
3.5 Сравнение четырех основных алгоритмов
алгоритм | преимущество | недостаток | Подходит для сцены |
---|---|---|---|
фиксированное окно | Простой и интуитивно понятный, легко реализуемый. Подходит для стабильного контроля расхода и простого в реализации контроля скорости. | Неспособность справиться с всплеском трафика за короткий период времени. Неравномерный трафик может привести к всплеску трафика. | Стабильный контроль потока, отсутствие необходимости обеспечивать равномерное распределение запросов |
раздвижное окно | Плавная обработка пакетного трафика имеет меньшую степень детализации и может обеспечить более точное управление ограничением тока. | Реализация относительно сложна и требует поддержания состояния раздвижного окна, что приводит к высокому потреблению памяти. | Сценарии, требующие плавной обработки пакетного трафика. |
утечкаведроалгоритм | Плавная обработка пакетного трафика позволяет зафиксировать скорость вывода и эффективно предотвратить перегрузку. | Обработка пакетного трафика недостаточно гибка, чтобы справляться с колебаниями трафика. | Сценарии, требующие фиксированной скорости вывода, чтобы избежать влияния внезапного увеличения трафика на систему. |
ведро жетонов | Плавно обрабатывайте всплеск трафика и динамически настраивайте текущие правила ограничения, чтобы адаптироваться к изменениям трафика в различные периоды времени. | Реализация относительно сложна и требует поддержания состояния ведро жетонов. | Сценарии, требующие динамической корректировки действующих ограничивающих правил. |
Ограничение тока одной машины относится к ситуации одного Служить,За счет ограничения количества запросов, обрабатываемых одним сервером Служить в единицу времени.,Не допускайте перегрузки устройства «Служить». Общие методы ограничения тока были представлены выше.,Его преимущество заключается в простоте реализации.,Высокая эффективность,Эффект очевиден. С популярностью микро-Служить,Служить системы обычно развертывается на нескольких серверах Служить.,В это время необходимо ограничить ток, чтобы обеспечить стабильность всей системы. В следующей статье будут представлены несколько распространенных технологических решений распределенного ограничения тока:
4.1 Централизованное решение для ограничения тока
4.1.1 Принцип плана
Все запросы к серверу контролируются через централизованный ограничитель потока. Метод реализации:
4.1.2 Реализация кода
package main
import (
"context"
"fmt"
"go.uber.org/atomic"
"sync"
"git.code.oa.com/pcg-csd/trpc-ext/redis"
)
type RedisClient interface {
Do(ctx context.Context, cmd string, args ...interface{}) (interface{}, error)
}
// Client база данных
type Client struct {
client RedisClient // redis действовать
script string // Lua-скрипт
}
// NewBucketClient Создать редисведро жетонов
func NewBucketClient(redis RedisClient) *Client {
helper := redis
return &Client{
client: helper,
script: `
-- ведро жетонов скрипт ограничения тока
-- KEYS[1]: имя сегмента
-- ARGV[1]: емкость ковша
-- ARGV[2]: Скорость генерации токенов
local bucket = KEYS[1]
local capacity = tonumber(ARGV[1])
local tokenRate = tonumber(ARGV[2])
local redisTime = redis.call('TIME')
local now = tonumber(redisTime[1])
local tokens, lastRefill = unpack(redis.call('hmget', bucket, 'tokens', 'lastRefill'))
tokens = tonumber(tokens)
lastRefill = tonumber(lastRefill)
if not tokens or not lastRefill then
tokens = capacity
lastRefill = now
else
local intervalsSinceLast = (now - lastRefill) * tokenRate
tokens = math.min(capacity, tokens + intervalsSinceLast)
end
if tokens < 1 then
return 0
else
redis.call('hmset', bucket, 'tokens', tokens - 1, 'lastRefill', now)
return 1
end
`,
}
}
// Получите токен. Если приобретение прошло успешно, он немедленно вернет true, в противном случае — false.
func (c *Client) isAllowed(ctx context.Context, key string, capacity int64, tokenRate int64) (bool, error) {
result, err := redis.Int(c.client.Do(ctx, "eval", c.script, 1, key, capacity, tokenRate))
if err != nil {
fmt.Println("Redis Ошибка выполнения: ", err)
return false, err
}
return result == 1, nil
}
// обнаружение звонков
func main() {
c := NewBucketClient(redis.GetPoolByName("redis://127.0.0.1:6379"))
gw := sync.WaitGroup{}
gw.Add(120)
count := atomic.Int64{}
for i := 0; i < 120; i++ {
go func(i int) {
defer gw.Done()
status, err := c.isAllowed(context.Background(), "test", 100, 10)
if status {
count.Add(1)
}
fmt.Printf("go %d status:%v error: %v\n", i, status, err)
}(i)
}
gw.Wait()
fmt.Printf("allow %d\n\n", count.Load())
}
Результат выполнения:
4.1.3 Существующие проблемы
4.2 Распределенное решение по ограничению тока на основе балансировки нагрузки
4.2.1 Принцип плана
Видно, что централизованное решение по ограничению тока имеет высокий риск отказа в одной точке, а узкое место в полосе пропускания является серьезным. На основе этого в данной статье разрабатывается новое решение для ограничения распределенного тока, сочетающее ограничение тока на одной машине с локальным кэшем и балансировку нагрузки. Конкретные планы таковы:
4.2.2 Проблемы
4.3 Ограничение тока на основе службы распределенной координации
4.3.1 Принцип схемы
Используйте службы распределенной координации, такие как ZooKeeper или etcd, для реализации ограничения тока. Каждый сервер подает заявку на получение токена от службы распределенной координации, и обрабатываться могут только те запросы, которые получают токен. Базовый план:
4.3.2 Проблемы
Преимуществом этого решения является точное ограничение глобального потока.,И единственных точек отказа можно избежать. но,недостаток этого решения сложен в реализации,и да ZooKeeper К производительности предъявляются более высокие требования. если ZooKeeper Неспособность обрабатывать большое количество операций применения и выпуска токенов может стать узким местом системы.
Короче говоря, не существует лучшего решения, есть только правильное решение.В выборе подходящеготекущий пределво время планирования,Нам нужно учитывать множество факторов,Включая требования системы, существующий стек технологий, состояние нагрузки системы и производительность базовой системы и т. д. Поймите, как работает каждый вариант и его особенности.,Чтобы сделать лучший выбор в практическом применении.
Ограничение тока является важным средством обеспечения стабильной и эффективной работы системы, но не единственным решением.我们还需要考虑Что他изсистема Инструменты проектирования и оптимизации,Например, балансировка нагрузки, кэширование, асинхронная обработка и т. д. (в условиях взрывного объема,Расширение – всегда лучший способ,Вот только это дорого! ). Эти средства работают вместе,Чтобы построить систему, способную обрабатывать большое количество одновременных запросов,И можем гарантировать качество Служить системы.
-End-
Автор оригинала|Чэнь Дайфу