Реализуйте rate limiter по алгоритму token bucket, который ограничивает
число разрешённых операций до rps штук в секунду.
Метод Allow() должен быть неблокирующим: он мгновенно возвращает true,
если в текущем окне ещё есть свободный «токен», и false — если лимит исчерпан.
Со временем токены восполняются (refill), так что в следующем окне снова можно
выполнить до rps операций.
type RateLimiter struct {
// ...
}
// NewRateLimiter создаёт лимитер на rps разрешений в секунду.
func NewRateLimiter(rps int) *RateLimiter
// Allow возвращает true, если операция укладывается в лимит.
// Метод не блокирует: горячий путь быстрый и без аллокаций.
func (rl *RateLimiter) Allow() bool
Метод Allow() вызывается конкурентно из множества горутин, поэтому доступ к
внутреннему состоянию обязан быть потокобезопасным. Проверка идёт под детектором
гонок (-race).
В тесте при rps=100 выполняется быстрый «всплеск» из множества вызовов
Allow(); число true в пределах одного окна должно быть близко к 100
(допускается небольшой разброс). После паузы бакет должен восполниться и снова
разрешать запросы.
На что смотрит интервьюер:
- Понимание алгоритма token bucket: ёмкость =
rps, токены тратятся на
каждый разрешённый запрос и пополняются по времени.
- Потокобезопасность горячего пути:
sync/atomic или sync.Mutex,
отсутствие гонок под -race.
Allow() не блокирует — это быстрый предикат, а не ожидание.
- Бонус: горячий путь без аллокаций (zero-alloc), refill через сравнение
временных меток, а не через фоновый таймер на каждый вызов.