Redis应用-简单限流策略

2019-08-29  本文已影响0人  CorDen_0314

Redis应用-简单限流策略

限流的目的是通过对并发访问/请求进行限速或者对一个时间窗口内的的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务。

在讲用redis实现之前,先简单介绍下redis中最重要的一种数据结构 ==zset==

1.ZSET(有序集合)

zset类似于 Java 的 SortedSet 和 HashMap 的结合体,一方面它是一个 set,保证了内部 value 的唯一性,另一方面它可以给每个 value 赋予一个 ==score==,代表这个 value 的排序权重

ZSET的常见命令

//向有序集合添加一个或者多个成员
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
//通过索引区间返回有序集合指定区间内的成员
192.168.212.55:6379[1]> zrange rank 0 100
1) "c++"
2) "python"
3) "java"
//获取有序集合的成员数
192.168.212.55:6379[1]> zcard rank
(integer) 3
//移除有序集合中给定的排名区间的所有成员
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为了保持唯一性,也存放毫秒时间戳

image

上代码:

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

这段代码有几个值得注意的点:

上一篇 下一篇

猜你喜欢

热点阅读