阶段2(上):后端专题 Go/Gin/PostgreSQL/MySQL/Redis/MQ/gRPC 的课程、企业场景、面试题和可运行示例
This commit is contained in:
1 parent
0bae9916a9
commit
5eb67c5d08
65 files changed
+6457
-77
No files matched your search
@@ -0,0 +1,42 @@
|
||||
package stocklock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// 滑动窗口:ZSET 里存每次请求的时间戳(毫秒),先删掉窗口外的,再数窗口内的
|
||||
var slidingScript = redis.NewScript(`
|
||||
local key, now, window, limit, member = KEYS[1], tonumber(ARGV[1]), tonumber(ARGV[2]), tonumber(ARGV[3]), ARGV[4]
|
||||
redis.call("ZREMRANGEBYSCORE", key, 0, now - window)
|
||||
if redis.call("ZCARD", key) >= limit then
|
||||
return 0
|
||||
end
|
||||
redis.call("ZADD", key, now, member)
|
||||
redis.call("PEXPIRE", key, window)
|
||||
return 1`)
|
||||
|
||||
type SlidingWindow struct {
|
||||
rdb *redis.Client
|
||||
limit int
|
||||
window time.Duration
|
||||
seq func() string // 生成唯一成员,避免同一毫秒的请求互相覆盖
|
||||
}
|
||||
|
||||
func NewSlidingWindow(rdb *redis.Client, limit int, window time.Duration) *SlidingWindow {
|
||||
var n atomic.Uint64 // 并发安全的序号;多实例部署时成员里再加上实例标识或用 UUID
|
||||
return &SlidingWindow{rdb: rdb, limit: limit, window: window, seq: func() string {
|
||||
return fmt.Sprintf("%d-%d", time.Now().UnixNano(), n.Add(1))
|
||||
}}
|
||||
}
|
||||
|
||||
// Allow 判断 key(如 "rl:user:42")在当前窗口内是否还能通过
|
||||
func (s *SlidingWindow) Allow(ctx context.Context, key string, now time.Time) (bool, error) {
|
||||
n, err := slidingScript.Run(ctx, s.rdb, []string{key},
|
||||
now.UnixMilli(), s.window.Milliseconds(), s.limit, s.seq()).Int()
|
||||
return n == 1, err
|
||||
}
|
||||
Reference in new issue
Block a user