Redis应用-简单限流策略
限流的目的是通过对并发访问/请求进行限速或者对一个时间窗口内的的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务。
在讲用redis实现之前,先简单介绍下redis中最重要的一种数据结构 ==zset==
1.ZSET(有序集合)
zset类似于 Java 的 SortedSet 和 HashMap 的结合体,一方面它是一个 set,保证了内部 value 的唯一性,另一方面它可以给每个 value 赋予一个 ==score==,代表这个 value 的排序权重
ZSET的常见命令
- ZADD key score1 member1 [score2 member2]
//向有序集合添加一个或者多个成员
C:\Users\ke_haodong>redis-cli -h 192.168.212.55 -p 6379 -a *****
192.168.212.55:6379> select 1
OK
192.168.212.55:6379[1]> zadd rank 100 java
(integer) 1
192.168.212.55:6379[1]> zadd rank 98 python 95 c++
(integer) 2
- ZRANGE key start stop [WITHSCORES]
//通过索引区间返回有序集合指定区间内的成员
192.168.212.55:6379[1]> zrange rank 0 100
1) "c++"
2) "python"
3) "java"
- ZCARD key
//获取有序集合的成员数
192.168.212.55:6379[1]> zcard rank
(integer) 3
- ZREMRANGEBYSCORE key min max
//移除有序集合中给定的排名区间的所有成员
192.168.212.55:6379[1]> ZREMRANGEBYSCORE rank 95 96
(integer) 1
192.168.212.55:6379[1]> ZRANGE rank 0 100
1) "python"
2) "java"
其他的命令这里不一一列举了,下面进入正题,如何用zset实现限流
2.Redis实现简单限流
首先来看下需求场景:系统要限定用户的某个行为在==指定的时间==(period)里只能允许发生 N 次,如何使用 Redis 的数据结构来实现这个限流的功能?
解决方案:这个限流需求中我们把这个==指定的时间==当做一个滑动时间窗口,想想 zset 数据结构的 score 值,是不是可以通过 score 保存毫秒时间戳 来圈出这个时间窗口来。而且我们只需要保留这个时间窗口,窗口之外的数据都可以砍掉。zset中的value为了保持唯一性,也存放毫秒时间戳
上代码:
package com.kehd.RedisStudy;
import java.io.IOException;
import redis.clients.jedis.Jedis;
/***
* redis实现简单限流
*
* @author ke_haodong
*/
public class SimpleRateLimiter {
private Jedis jedis;
public SimpleRateLimiter(Jedis jedis) {
this.jedis = jedis;
}
public boolean isActionAllowed(String userId, String actionKey, int period, int maxCount) throws IOException {
// 1.用户id和操作id拼接作为key
String key = String.format("%s:%s", userId, actionKey);
long nowTs = System.currentTimeMillis();
// 2.score和value都用毫秒时间戳
jedis.zadd(key, nowTs, "" + nowTs);
// 3.移除时间窗口之前的行为记录,剩下的都是时间窗口内的
jedis.zremrangeByScore(key, 0, nowTs - period * 1000);
// 4.获取时间窗口内的操作次数
long count = jedis.zcard(key);
// 5.每次为key设置过期时间,period时间内不操作,判定为冷用户
jedis.expire(key, period * 5 + 1);
jedis.close();
return count <= maxCount;
}
public static void main(String[] args) throws IOException {
SimpleRateLimiter limiter = new SimpleRateLimiter(new JedisUtils().getJedis());
for (int i = 0; i < 20; i++) {
System.out.println(limiter.isActionAllowed("kehd", "reply", 60, 10));
}
}
}
运行结果
true
true
true
true
true
true
true
true
true
true
false
false
false
false
false
false
false
false
false
false
这段代码有几个值得注意的点:
- zset 集合中key值用userid + actionid表示某个用户的操作
- zset 集合中只有 score 值非常重要,value值没有特别的意义,只需要保证它是唯一的就可以了
- 每一个行为到来时,都维护一次时间窗口。将时间窗口外的记录全部清理掉,只保留窗口内的记录
- 每一次都去维护一下key的得过期时间,过期后置为冷数据,节省内存
网友评论