Redis实现简单限流

使用Redis进行简单的限流

限流

限流的目的是当系统的处理能力有限时,阻止计划外的请求继续对系统施压,通过对并发/请求进行限速或者一个时间窗口内的请求进行限速来保护系统,达到限制速率则可以拒绝服务。还有一个应用目的是用于控制用户的行为,比如在论坛中的发帖,回复等。一般是要控制某行为在规定时间内允许的次数。

redis实现的限流

常见的限流算法有:计数器,令牌桶和漏桶算法

计数器算法是最简单粗暴的算法,系统限制在指定时间内只允许发生N次事件。使用时间窗口

使用到的redis数据结构

redis中有个很特色的数据结构:zset(有序集合)
有点儿类似于java的SortSet和HashMap的结合体,一方面是个set,保证内部的value的唯一性,另一方面可以给每个value赋予一个score,表示这个value的排序权重。
命令为:ZADD KEY SCORE VALUE
redis限流

通过zset的score值,圈出一个时间窗口,将时间窗口之外的数据移除,时间窗口内的数据就是需要的数据。
因为value的值需要唯一,value中存放纳秒级的时间戳,存放毫秒级的可能会出现重复在高并发下。

每个用户使用一个zset记录其操作记录,因为zset为空时会自动从内存中移除,所以低活跃用户不会有自己的zset。降低了内存消耗。

具体实现
   public void userReply () throws IOException {
        String actionKey = "test";
        String userId = "cjl";
        for(int i=0;i<20;i++) {
            System.out.println(isActionAllowed(userId, actionKey, 30, 5));
        }
    }
	
	/**
     * 查看用户操作是否正常
     * @param userId 用户Id
     * @param actionKey 行为名
     * @param period 时间范围
     * @param maxCount 最大次数
     * @return 是否正常
     * @throws IOException
     */
    public boolean isActionAllowed(String userId, String actionKey, int period, int maxCount) throws IOException {
        String key = String.format("hist:%s:%s", userId, actionKey);
        long nowTs = System.nanoTime();
        Pipeline pipe = jedis.pipelined();
        pipe.multi();
        // 记录行为
        pipe.zadd(key, nowTs, "" + nowTs);
        // 移除超时行为
        pipe.zremrangeByScore(key, 0, nowTs - period * 1000*1000);
        // 查找时间窗口中的行为数量
        Response<Long> count = pipe.zcard(key);
        // 重制所有行为的过期时间
        pipe.expire(key, period + 1);
        pipe.exec();
        pipe.close();
        return count.get() <= maxCount;
    }
运行结果
true
true
true
true
true
false
false
false
false
false
false
false
false
false
false
false
false
false
false
false

因为操作都是针对同一个key,所以使用pipline提升redis的存取效率。

pipline是将多条命令一次性发送给redis,并在所有命令执行完后一次性返回,通过减少客户端和redis的通行次数来实现降低往返延时时间。

缺点

如果在时间窗口中需要记录的行为次数过多,将会有很大的消耗,比如60s内1000次操作。

posted @ 2019-01-21 20:42  司霖  阅读(3848)  评论(0编辑  收藏  举报