Skip to content

基于Redis实现的滑动窗口限流存在BUG #27

@bootun

Description

@bootun

问题简要描述

由于代码里使用ZCOUNT统计key的数量来当作请求数量,同一毫秒内的多个请求会被按照一个进行计算。1秒内最多只有1000毫秒,因此如果我设置1秒内最多允许3000请求,那么这个条件永远不会成立。因为即使1秒进来10W个请求,ZCOUNT也只能拿到1000个key。这个问题把时间单位从毫秒改为微秒/纳秒可以一定程度上的缓解吗,但治标不治本,实测下来,连续请求100个,ZCOUNT拿到的值也不到100个。

复现步骤

func main() {
	client := redis.NewClient(&redis.Options{
		Addr: "local.machine:6379",
	})

	rdsLimiter := RedisSlidingWindowLimiter{
		Cmd:      client,
		Interval: time.Second,
		Rate:     100, // 每秒允许100个
	}

	var (
		total   = 1500 // 总请求数
		succ    int // 成功请求数
		limited int // 被限流的请求数
	)
	start := time.Now()
	for i := 0; i < total; i++ {
		limit, err := rdsLimiter.Limit(context.Background(), "test")
		if err != nil {
			log.Printf("limit error: %v", err)
			return
		}
		if limit {
			limited++
			log.Printf("%4d号请求被限流了", i)
			continue
		}
		succ++
	}
	end := time.Now()
	log.Printf("开始时间: %v", start.Format(time.StampMilli))
	log.Printf("结束时间: %v", end.Format(time.StampMilli))
	log.Printf("total: %d, succ: %d, limited: %d", total, succ, limited)
}

错误日志或者截图

image

你期望的结果

100个通过,剩下全部限流

你排查的结果,或者你觉得可行的修复方案

同一时间内多个请求进行自增记录,统计时使用窗口时间内,请求数量的和。或为每个请求生成一个随机uuid, 这样即使同一时间进来两个请求,由于uuid不同,在计算时它们还是会被当作两次请求计算

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions