关于用 Redis 限制用户行为频率的方法,麻烦 V 友帮看看

2020-03-28 19:33:02 +08:00
 yangbin9317

需求:

这是昨天面试中遇到的一个问题,当用户 x 秒内请求 y 次,禁止用户访问

先写一些我自己想的方案,下面的windowStart为 x 秒之前的 timestamp,current为现在的 timestamp,threshold为请求次数限制。下列方法都需要设置和更新 key 过期时间,以免造成 key 泄漏。

方案 1:

用 Redis 的 List 数据结构记录用户访问的时间

当用户访问时 rpush 当前时间戳并删掉左侧 x 秒之前的时间戳,此时该列表的长度为 x 秒请求次数,注意此方法的timestamp需要用 Redis 的 Lua 脚本否则可能因为各机器时间不同出问题(例如某台机器时间为未来,则 ltrim 不到该条数据后面的正常时间戳)

伪代码:

redis.rpush(key, current);
redis.expire(key, current + y);
counter = 0;
while (redis.get(key, 0) < windowStart) {
	counter++;
}
redis.ltrim(0, counter);
if (redis.llen(key) > y) {
	doBanUser();
}

方案 1 的问题:

只需要最多存 y 条数据,但是这里用到了 List,Redis 中的 List 为双向链表,每个节点包含两个指针,在 64 位机器上占用 128bit,而实际有用的时间戳只要 64bit 。迭代链表时会更慢一些。

方案 2:

使用数组 ringbuffer 代替方案一的链表。

方案 2 的问题:

需要自己开发 Redis module,开发和运维成本较高。另外方案 1 和方案 2 中均存不方便重试和测试的问题。例如其他业务调自己服务但是自己服务超时,此时业务方重试,这种情况不应该算用户访问了两次。

方案 3:

使用 Redis 的 ZSet 数据结构

以用户请求的唯一 requestId 为 member,当前时间为 score,如果 requestId 已存在( logn 复杂度),不进行任何操作,如果不存在,则 ZADD,然后使用 ZRANGEBYSCORE 查看该时间段内请求数量。该方案同时需要使用 ZREMRANGEBYSCORE 来删除过老的元素,我认为可以每次或 N 次请求的时候删除,面试官说有更好的删除时机,但是我也没有问,这个问题就这么过去了。

想问问 V 友该方法应该如何删除,或者有没有更好的方法来用 Redis 限制用户行为频率?

9977 次点击
所在节点    Redis
44 条回复
aliez1995
2020-07-03 00:00:48 +08:00
@xuanbg 先访问一次,等待到第 59 秒访问 9 次,下一秒就可以再来十次了
xuanbg
2020-07-03 06:33:27 +08:00
@huntcool001
@aliez1995
你觉得过了 59 秒计时已经快结束了,可实际上 key 的 60 秒计时刚开始呢,下一秒你什么也干不了。key 是你访问接口的特征字符串,只有在你访问接口的时候才会在 Redis 中存在 60 秒。也就是说,只要 Redis 里面有 key,你就只能访问 10 次。要想访问第 11 次,得等这个 key 自己消失才行。但你一旦进行第 11 次访问,这个 key 就又自动出现了,所以你接下去也就只能是 10 次。
TeslaLyon
2020-10-26 18:02:54 +08:00
@labulaka521 站点不维护了?
Evilk
2020-12-10 09:51:04 +08:00
最近正在做类似的东西
某个接口,需要限制每个用户 1 分钟内,只能访问 10 次
打算使用 redis-cell 模块来做,应用层直接调用 redis 命令,直接交给 redis 自身来做
现成的轮子,方便,高效

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/657135

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX