IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> Redis之zset实现滑动窗口限流 -> 正文阅读

[大数据]Redis之zset实现滑动窗口限流

本文已收录于专栏

??《Redis之大厂必备技能包》??

上千人点赞收藏的,全套Redis学习资料,大厂必备技能!


?目录

1、需求

2、常见的错误设计

3、滑动窗口算法

3.1 解决方案

3.2 pipeline代码实现

3.3 lua代码实现


1、需求

限定用户的某个行为在指定时间T内,只允许发生N次。假设T为1秒钟,N为1000次。

2、常见的错误设计

程序员设计了一个在每分钟内只允许访问1000次的限流方案,如下图01:00s-02:00s之间只允许访问1000次,这种设计最大的问题在于,请求可能在01:59s-02:00s之间被请求1000次,02:00s-02:01s之间被请求了1000次,这种情况下01:59s-02:01s间隔0.02s之间被请求2000次,很显然这种设计是错误的。

错误限流.png

3、滑动窗口算法

3.1 解决方案

指定时间T内,只允许发生N次。我们可以将这个指定时间T,看成一个滑动时间窗口(定宽)。我们采用Redis的zset基本数据类型的score来圈出这个滑动时间窗口。在实际操作zset的过程中,我们只需要保留在这个滑动时间窗口以内的数据,其他的数据不处理即可。

  • 每个用户的行为采用一个zset存储,score为毫秒时间戳,value也使用毫秒时间戳(比UUID更加节省内存)
  • 只保留滑动窗口时间内的行为记录,如果zset为空,则移除zset,不再占用内存(节省内存)

zset滑动窗口限流.png

3.2 pipeline代码实现

代码的实现的逻辑是统计滑动窗口内zset中的行为数量,并且与阈值maxCount直接进行比较就可以判断当前行为是否被允许。这里涉及多个redis操作,因此使用pipeline可以大大提升效率

package?com.lizba.redis.limit;

import?redis.clients.jedis.Jedis;
import?redis.clients.jedis.Pipeline;
import?redis.clients.jedis.Response;

/**
?*?<p>
?*?????通过zset实现滑动窗口算法限流
?*?</p>
?*
?*?@Author:?Liziba
?*?@Date:?2021/9/6?18:11
?*/
public?class?SimpleSlidingWindowByZSet?{

????private?Jedis?jedis;

????public?SimpleSlidingWindowByZSet(Jedis?jedis)?{
????????this.jedis?=?jedis;
????}

????/**
?????*?判断行为是否被允许
?????*
?????*?@param?userId????????用户id
?????*?@param?actionKey?????行为key
?????*?@param?period????????限流周期
?????*?@param?maxCount??????最大请求次数(滑动窗口大小)
?????*?@return
?????*/
????public?boolean?isActionAllowed(String?userId,?String?actionKey,?int?period,?int?maxCount)?{
????????String?key?=?this.key(userId,?actionKey);
????????long?ts?=?System.currentTimeMillis();
????????Pipeline?pipe?=?jedis.pipelined();
????????pipe.multi();
????????pipe.zadd(key,?ts,?String.valueOf(ts));
????????//?移除滑动窗口之外的数据
????????pipe.zremrangeByScore(key,?0,?ts?-?(period?*?1000));
????????Response<Long>?count?=?pipe.zcard(key);
????????//?设置行为的过期时间,如果数据为冷数据,zset将会删除以此节省内存空间
????????pipe.expire(key,?period);
????????pipe.exec();
????????pipe.close();
????????return?count.get()?<=?maxCount;
????}


????/**
?????*?限流key
?????*
?????*?@param?userId
?????*?@param?actionKey
?????*?@return
?????*/
????public?String?key(String?userId,?String?actionKey)?{
????????return?String.format("limit:%s:%s",?userId,?actionKey);
????}

}

测试代码:

package?com.lizba.redis.limit;

import?redis.clients.jedis.Jedis;

/**
?*
?*?@Author:?Liziba
?*?@Date:?2021/9/6?20:10
?*/
public?class?TestSimpleSlidingWindowByZSet?{

????public?static?void?main(String[]?args)?{
????????Jedis?jedis?=?new?Jedis("192.168.211.108",?6379);
????????SimpleSlidingWindowByZSet?slidingWindow?=?new?SimpleSlidingWindowByZSet(jedis);
????????for?(int?i?=?1;?i?<=?15;?i++)?{
????????????boolean?actionAllowed?=?slidingWindow.isActionAllowed("liziba",?"view",?60,?5);

????????????System.out.println("第"?+?i?+"次操作"?+?(actionAllowed???"成功"?:?"失败"));
????????}

????????jedis.close();
????}

}

测试效果:
从测试输出的数据可以看出,起到了限流的效果,从第11次以后的请求操作都是失败的,但是这个和我们允许的5次误差还是比较大的。这个问题的原因是我们测试System.currentTimeMillis()的毫秒可能相同,而且此时value也是System.currentTimeMillis()也相同,会导致zset中元素覆盖!

image.png


修改代码测试:
在循环中睡眠1毫秒即可,测试结果符合预期!

?TimeUnit.MILLISECONDS.sleep(1);

image.png

3.3 lua代码实现

我们在项目中使用原子性的lua脚步来实现限流的使用会更多,因此这里也提供一个基于操作zset的lua版本

package?com.lizba.redis.limit;

import?com.google.common.collect.ImmutableList;
import?redis.clients.jedis.Jedis;
import?redis.clients.jedis.Pipeline;
import?redis.clients.jedis.Response;

/**
?*?<p>
?*?????通过zset实现滑动窗口算法限流
?*?</p>
?*
?*?@Author:?Liziba
?*?@Date:?2021/9/6?18:11
?*/
public?class?SimpleSlidingWindowByZSet?{

????private?Jedis?jedis;

????public?SimpleSlidingWindowByZSet(Jedis?jedis)?{
????????this.jedis?=?jedis;
????}

????/**
?????*?lua脚本限流
?????*
?????*?@param?userId
?????*?@param?actionKey
?????*?@param?period
?????*?@param?maxCount
?????*?@return
?????*/
????public?boolean?isActionAllowedByLua(String?userId,?String?actionKey,?int?period,?int?maxCount)?{
????????String?luaScript?=?this.buildLuaScript();

????????String?key?=?key(userId,?actionKey);
????????long?ts?=?System.currentTimeMillis();
????????System.out.println(ts);
????????ImmutableList<String>?keys?=?ImmutableList.of(key);
????????ImmutableList<String>?args?=?ImmutableList.of(String.valueOf(ts),String.valueOf((ts?-?period?*?1000)),?String.valueOf(period));
????????Number?count?=?(Number)?jedis.eval(luaScript,?keys,?args);

????????return?count?!=?null?&&?count.intValue()?<=?maxCount;
????}


????/**
?????*?限流key
?????*
?????*?@param?userId
?????*?@param?actionKey
?????*?@return
?????*/
????private?String?key(String?userId,?String?actionKey)?{
????????return?String.format("limit:%s:%s",?userId,?actionKey);
????}


????/**
?????*?针对某个key使用lua脚本限流
?????*
?????*?@return
?????*/
????private?String?buildLuaScript()?{
????????return?"redis.call('ZADD',?KEYS[1],?tonumber(ARGV[1]),?ARGV[1])"?+
????????????????"\nlocal?c"?+
????????????????"\nc?=?redis.call('ZCARD',?KEYS[1])"?+
????????????????"\nredis.call('ZREMRANGEBYSCORE',?KEYS[1],?0,?tonumber(ARGV[2]))"?+
????????????????"\nredis.call('EXPIRE',?KEYS[1],?tonumber(ARGV[3]))"?+
????????????????"\nreturn?c;";

????}

}

测试代码不变,大家可以自行测试,记得还是要考虑我们测试的时候System.currentTimeMillis()相等的问题,不信你输出System.currentTimeMillis()就知道了!多思考问题,技术其实都在心里!

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2021-09-08 10:48:29  更:2021-09-08 10:48:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 20:42:59-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码