Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion internal/ratelimit/redis_slide_window.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ package ratelimit
import (
"context"
_ "embed"
"fmt"
"time"

"github.com/google/uuid"
"github.com/redis/go-redis/v9"
)

Expand All @@ -38,6 +40,10 @@ type RedisSlidingWindowLimiter struct {
}

func (r *RedisSlidingWindowLimiter) Limit(ctx context.Context, key string) (bool, error) {
uid, err := uuid.NewUUID()
if err != nil {
return false, fmt.Errorf("generate uuid failed: %w", err)
}
return r.Cmd.Eval(ctx, luaSlideWindow, []string{key},
r.Interval.Milliseconds(), r.Rate, time.Now().UnixMilli()).Bool()
r.Interval.Milliseconds(), r.Rate, time.Now().UnixMilli(), uid.String()).Bool()
}
30 changes: 30 additions & 0 deletions internal/ratelimit/redis_slide_window_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,33 @@ func initRedis() redis.Cmdable {
})
return redisClient
}

func TestRedisSlidingWindowLimiter(t *testing.T) {
r := &RedisSlidingWindowLimiter{
Cmd: initRedis(),
Interval: time.Second,
Rate: 1200,
}
var (
total = 1500 // 总请求数
succCount int // 成功请求数
limitCount int // 被限流的请求数
)
start := time.Now()
for i := 0; i < total; i++ {
limit, err := r.Limit(context.Background(), "test")
if err != nil {
t.Fatalf("limit error: %v", err)
return
}
if limit {
limitCount++
continue
}
succCount++
}
end := time.Now()
t.Logf("开始时间: %v", start.Format(time.StampMilli))
t.Logf("结束时间: %v", end.Format(time.StampMilli))
t.Logf("total: %d, succ: %d, limited: %d", total, succCount, limitCount)
}
7 changes: 5 additions & 2 deletions internal/ratelimit/slide_window.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ local window = tonumber(ARGV[1])
-- 阈值
local threshold = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
-- 唯一ID, 用于解决同一时间内多个请求只统计一次的问题
-- SEE: issue #27
local uid = ARGV[4]
-- 窗口的起始时间
local min = now - window

Expand All @@ -15,8 +18,8 @@ if cnt >= threshold then
-- 执行限流
return "true"
else
-- score member 都设置成 now
redis.call('ZADD', key, now, now)
-- score 设置为当前时间, member 设置为唯一id
redis.call('ZADD', key, now, uid)
redis.call('PEXPIRE', key, window)
return "false"
end