Skip to content

Commit

Permalink
修复Redis限流器同一时间多个请求只计算一次的问题
Browse files Browse the repository at this point in the history
  • Loading branch information
bootun committed Nov 17, 2024
1 parent 6f3c1e3 commit 4efe398
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 3 deletions.
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

0 comments on commit 4efe398

Please sign in to comment.