Add gRPC server implementation and database integration for marketplace and products
This commit is contained in:
67
internal/ozon/rate_limiter.go
Normal file
67
internal/ozon/rate_limiter.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package ozon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/redis/rueidis"
|
||||
"net/http"
|
||||
"sipro-mps/internal/redis"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
windowSize = time.Second
|
||||
rps = 50 // requests per second
|
||||
)
|
||||
|
||||
var (
|
||||
rateLimiterScript = rueidis.NewLuaScript(`
|
||||
local key = KEYS[1]
|
||||
local now = tonumber(ARGV[1])
|
||||
local window = tonumber(ARGV[2])
|
||||
local limit = tonumber(ARGV[3])
|
||||
|
||||
-- Удаляем старые записи вне окна времени
|
||||
redis.call('ZREMRANGEBYSCORE', key, '-inf', now - window)
|
||||
local count = redis.call('ZCARD', key)
|
||||
|
||||
if count < limit then
|
||||
-- Лимит не превышен, добавляем новую метку и устанавливаем TTL
|
||||
redis.call('ZADD', key, now, now)
|
||||
redis.call('EXPIRE', key, math.ceil(window / 1000000000))
|
||||
return 0 -- Можно выполнять запрос сразу
|
||||
else
|
||||
-- Лимит превышен, находим самую старую метку
|
||||
local oldest = redis.call('ZRANGE', key, 0, 0, 'WITHSCORES')[2]
|
||||
-- Возвращаем время, которое нужно подождать до освобождения слота
|
||||
return (tonumber(oldest) + window) - now
|
||||
end
|
||||
`)
|
||||
)
|
||||
|
||||
type RateLimitTransport struct {
|
||||
http.RoundTripper
|
||||
}
|
||||
|
||||
func (t *RateLimitTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
fmt.Println(time.Now().Format("2006-01-02 15:04:05"))
|
||||
ctx := req.Context()
|
||||
clientId := req.Header.Get("Client-Id")
|
||||
now := time.Now().UnixNano()
|
||||
|
||||
waitTime, err := rateLimiterScript.Exec(ctx, *redis.Client, []string{clientId}, []string{
|
||||
fmt.Sprintf("%d", now),
|
||||
fmt.Sprintf("%d", int64(windowSize)),
|
||||
fmt.Sprintf("%d", 50),
|
||||
}).ToInt64()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute rate limit script: %w", err)
|
||||
}
|
||||
if waitTime > 0 {
|
||||
time.Sleep(time.Duration(waitTime))
|
||||
}
|
||||
return t.RoundTripper.RoundTrip(req)
|
||||
}
|
||||
func NewRateLimitTransport() *RateLimitTransport {
|
||||
|
||||
return &RateLimitTransport{RoundTripper: http.DefaultTransport}
|
||||
}
|
||||
Reference in New Issue
Block a user