Реализуйте ограничитель частоты по алгоритму token bucket. Он ограничивает среднюю частоту разрешённых операций до rps в секунду и допускает всплеск не больше rps операций подряд.
Метод Allow() не блокируется. Если в бакете есть токен, он тратит один токен и возвращает true. Если бакет пуст, возвращает false. Со временем токены пополняются со скоростью rps в секунду, но не больше ёмкости бакета. Ёмкость равна rps, и в начале бакет полон.
type RateLimiter struct { /* ... */ }
// NewRateLimiter создаёт лимитер на rps разрешений в секунду (rps > 0).
func NewRateLimiter(rps int) *RateLimiter
// Allow возвращает true, если операция укладывается в лимит.
func (rl *RateLimiter) Allow() bool
Пример при rps = 10:
rl := NewRateLimiter(10)
// мгновенно: 10 раз true (полный бакет), дальше false
time.Sleep(150 * time.Millisecond)
// за 150 мс накопилось 1.5 токена: следующий Allow() вернёт true, ещё один сразу после него false
// накопленные 0.5 токена не пропадают, они войдут в следующее пополнение
Что важно понимать:
- Это не лимит «не больше
rps вызовов в любом скользящем окне в одну секунду». Бакет допускает короткий всплеск, а средняя частота ограничена.
- Дробные токены нужно сохранять. Если после каждого пополнения округлять запас до целого и сдвигать метку времени, половина токена теряется, и реальная частота окажется ниже заявленной.
Allow() вызывается из многих горутин одновременно. Состояние должно быть защищено, проверка идёт с -race.
- «Не блокируется» значит «не ждёт появления токена». Короткое ожидание мьютекса допустимо.
- Пополнение вычисляется по прошедшему времени в момент вызова
Allow(), фоновые горутины и таймеры не нужны.
Тесты: ёмкость ограничена rps (после простоя всплеск не превышает её); дробные токены не теряются (опрос раз в 40 мс при rps = 10 за 1,2 с пропускает столько, сколько накопилось, плюс-минус один); 1000 вызовов подряд при rps = 100 пропускают от 90 до 130; после исчерпания бакета и паузы 1,1 с снова проходит не меньше 90 запросов; 50 горутин по 100 вызовов под -race пропускают не больше 300.