Redis限流方案

限流简介

限流算法在分布式领域是一个经常被提起的话题,当系统的处理能力有限时,如何阻止计划外的请求继续对系统施压,是一个需要重视的问题。

除了控制流量,限流还有一个应用目的是用于控制用户行为,避免垃圾请求,比如在UGC社区,用户的发帖、回复、点赞等行为都要严格受控,一般要严格限定某行为在规定时间内允许的次数,超过了次数那就是非法行为。对于非法行为,业务必须规定适当的惩处策略。

简单限流

首先我们来看一个常见的简单限流策略。系统要限定用户某个行为在指定的时间里只能发生N次,如何使用Redis的数据结构来实现这个限流功能。

首先,这个接口定义如下

#指定用户use_id的某个行为action_key在特定的时间内period只允许发生一定的次数
int max_count;
boolean is_action_allowed(user_id,action_key,period,max_count){return true;
}
# 调用这个接口,一分钟内只允许最多回复5个贴子
can_reply = is_action_allowed("user1","reply",60,5);
if(can_reply) do();
else return ;

解决方案
这个限流需求中存在一个滑动时间窗口,在zset数据结构中提供了zremrangebyscore key-name min max指令用于移除有序集合中分值介于min和max之间的元素集合。因此可以使用行为发生的时间戳作为zset集合中元素的score,时间戳越大,相应score越高。 而且我们只需要保留这个时间窗口,窗口之外的数据都可以砍掉,从而节省内存。

示意图如下
在这里插入图片描述
代码如下:

package example;import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Response;public class SimpleRateLimiter {private Jedis jedis;public SimpleRateLimiter(Jedis jedis) {this.jedis = jedis;}public boolean isActionAllowed(String userId, String actionKey, int period, int maxCount) {String key = String.format("hist:%s:%s", userId, actionKey);long nowTs = System.currentTimeMillis();Pipeline pipe = jedis.pipelined();pipe.multi();pipe.zadd(key, nowTs, "" + nowTs);pipe.zremrangeByScore(key, 0, nowTs - period * 1000);Response<Long> count = pipe.zcard(key);pipe.expire(key, period + 1);pipe.exec();pipe.close();return count.get() <= maxCount;}public static void main(String[] args) {Jedis jedis = new Jedis();SimpleRateLimiter limiter = new SimpleRateLimiter(jedis);for (int i = 0; i < 20; i++) {System.out.println(limiter.isActionAllowed("laoqian", "reply", 60, 5));}}
}

代码整体思路:每一个行为到来时,都维护一次时间窗口,将时间窗口外的记录全部清理掉,只保留窗口内的记录。zset集合中只有score值非常重要,value值没有特别意义。只需要保证他的唯一性即可。

因为这几个连续的Redis操作都是针对同一个key的,使用pipeline可以显著提升Redis存取效率。但是这种方案也有缺点,因为他要记录时间窗口内所有的行为记录。如果这个量很大,比如限定60s内操作不得超过100w次这样的参数,他是不适合做这样的限流的,因为会消耗大量的存储空间。

高级限流算法——漏洞限流

漏洞限流是最常用的限流方法之一,这个算法的灵感来源于漏斗的结构。漏斗的容量是有限的,如果将漏嘴堵住,然后一直往里面灌水,他就会变满,直至再也装不进去。如果将漏嘴放开,水就会往下流,流走一部分之后,就可以继续往里面灌水。如果漏嘴流水的速率大于灌水的速率。那么漏洞永远都不会装满。如果漏嘴流水速率小雨灌水速率,那么一旦漏斗满了,灌水就需要暂停并等待漏斗腾空。

所以,漏斗的剩余空间就代表着当前行为可以持续进行的数量,漏嘴的流水速率代表着系统允许该行为的最大频率。

单机漏斗算法:

package example;import java.util.HashMap;
import java.util.Map;public class FunnelRateLimiter {static class Funnel {//漏斗容量int capacity;//漏斗速率float leakingRate;//漏斗剩余容量int leftQuota;//滑动窗口的开始时间long leakingTs;public Funnel(int capacity, float leakingRate) {this.capacity = capacity;this.leakingRate = leakingRate;this.leftQuota = capacity;this.leakingTs = System.currentTimeMillis();}void makeSpace() {//获取当前时间long nowTs = System.currentTimeMillis();//当前时间-滑动窗口的开始时间= 滑动窗口的时长long deltaTs = nowTs - leakingTs;//滑动窗口的时长*漏水速率 = 滑动窗口内腾出的容量int deltaQuota = (int) (deltaTs * leakingRate);if (deltaQuota < 0) { // 间隔时间太长,整数数字过大溢出this.leftQuota = capacity;this.leakingTs = nowTs;return;}if (deltaQuota < 1) { // 腾出空间太小,最小单位是 1return;}//剩余容量 = 当前剩余容量+腾出的容量this.leftQuota += deltaQuota;//重置窗口开始时间this.leakingTs = nowTs;if (this.leftQuota > this.capacity) {this.leftQuota = this.capacity;}}boolean watering(int quota) {makeSpace();//剩余容量>所需容量if (this.leftQuota >= quota) {this.leftQuota -= quota;return true;}return false;}}private Map<String, Funnel> funnels = new HashMap<>();public boolean isActionAllowed(String userId, String actionKey, int capacity, float leakingRate) {String key = String.format("%s:%s", userId, actionKey);Funnel funnel = funnels.get(key);if (funnel == null) {funnel = new Funnel(capacity, leakingRate);funnels.put(key, funnel);}return funnel.watering(1); // 需要 1quota }
}

Funnel对象的make_space方法是漏斗算法的核心,其次在每次灌水前都会被调用以触发漏水,给漏斗腾出空间。能腾出多少空间取决于过去了多久以及流水的速率。Funnel对象占据的空间大小不再和行为的频率成正比,她的空间占用是一个常量。

分布式限流 Redis-Cell

Redis-Cell语法
在这里插入图片描述

127.0.0.1:6379> cl.throttle mytest 99 5 100 2
1) (integer) 0                        #0 表示成功, 1表示失败
2) (integer) 100                      # 令牌桶的容量
3) (integer) 98                       # 当前令牌桶的令牌数
4) (integer) -1                       # 成功时该值为-1,失败时表还需要等待多少秒可以有足够的令牌
5) (integer) 41                       # 预计多少秒后令牌桶会满

多次从令牌桶中取出数据

127.0.0.1:6379> cl.throttle mytest 99 5 100 40
1) (integer) 0
2) (integer) 100
3) (integer) 54
4) (integer) -1
5) (integer) 911
127.0.0.1:6379> cl.throttle mytest 99 5 100 40
1) (integer) 0
2) (integer) 100
3) (integer) 14
4) (integer) -1
5) (integer) 1708
127.0.0.1:6379> cl.throttle mytest 99 5 100 40
1) (integer) 1                      #失败,拒绝取出
2) (integer) 100
3) (integer) 14
4) (integer) 505                  # 取出失败,令牌桶还有14个令牌,还需505秒才能够取出
5) (integer) 1705

代码示例

package example;import io.rebloom.client.Client;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.Jedis;import java.util.ArrayList;
import java.util.List;public class RedisCellExample {private Jedis jedis;public RedisCellExample() {jedis = new Jedis("127.0.0.1", 6379);}public Boolean rush() {//对接口进行限流操作//检查令牌桶,返回的第一值是否为 0: 0-流量够,1-限流中String script = "return redis.call('cl.throttle',KEYS[1],ARGV[1],ARGV[2],ARGV[3],ARGV[4])";List<String> keys = new ArrayList<>();keys.add("redbag");String maxBurst = "99";  //漏洞容量String countPerPeriod = "10";String period = "100";String quantity = "10";List<String> values = new ArrayList<>();values.add(maxBurst);values.add(countPerPeriod);values.add(period);values.add(quantity);List<Integer> list = (List) jedis.eval(script, keys, values);if (!list.isEmpty() && list.get(0) == 0) {return true;} else {return false;}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/849465.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

企业公户验证API在Java、Python、PHP中的使用教程

在金融和商业领域&#xff0c;企业公户验证API是一种用于验证企业对公账户的真实性和合法性的技术解决方案。这种API通常由金融机构或第三方服务提供商提供&#xff0c;旨在帮助企业加快账户认证流程&#xff0c;提高效率&#xff0c;降低审核成本&#xff0c;并确保符合法规要…

苹果Safari怎么清理缓存?原来快速清除浏览器的历史记录那么容易

在数字化时代&#xff0c;互联网已经成为我们日常生活中不可或缺的一部分。我们使用各种设备&#xff0c;如智能手机、平板电脑和笔记本电脑来浏览网页、获取信息、娱乐和社交。而在这些设备中&#xff0c;iPhone无疑是最受欢迎的选择之一。iPhone搭载的Safari浏览器以其简洁的…

ui自动化中,鼠标操作

from selenium.webdriver import ActionChainsaction ActionChains(driver) # 然后把driver作为参数&#xff0c;实例化一个action对象 练习地址&#xff1a;https://sahitest.com/demo/ 悬停例子&#xff08;百度首页&#xff09; action.move_to_element(ele).perfor…

Springboot注意点

1.Usermapper里加param注解 2.RequestParam 和 RequestBody的区别&#xff1a; RequestParam 和 RequestBody的区别&#xff1a; RequestParam 和 RequestBody 是Spring框架中用于处理HTTP请求的两个不同的注 get请求一般用url传参数&#xff0c;所以参数名和参数的值就在ur…

Type-C音频转接器方案

在数字化时代&#xff0c;音频设备作为我们生活中不可或缺的一部分&#xff0c;其连接方式的便捷性和高效性显得尤为重要。Type-C音频转接器&#xff0c;作为一种新型的音频连接解决方案&#xff0c;正逐渐走进我们的生活&#xff0c;以其独特的优势改变着我们的音频体验。 一、…

Vue Router 4与路由管理实战

title: Vue Router 4与路由管理实战 date: 2024/6/7 updated: 2024/6/7 excerpt: 这篇文章介绍了如何在Vue.js应用中利用Vue Router实现单页面应用的路由管理&#xff0c;包括配置路由、导航守卫的使用、路由懒加载以优化性能以及动态路由的实现方法&#xff0c;旨在提升用户体…

xiaolingcoding 图解网络笔记——基础篇

文章目录 参考一、网络模型有哪几层DMANAPI 机制二、键入网址到网页显示&#xff0c;期间发生了什么&#xff1f;1. HTTP2. DNS3. 协议栈4. TCP5. IP6. MAC7. 网卡8. 交换机9. 路由器10. 服务器 与 客户端的互相扒皮&#xff08;添加、删除头部信息&#xff09;参考图HTTP 请求…

Vue3【六】setup的使用和setup的返回值

Vue3【六】setup的使用和setup的返回值 setup函数的使用&#xff0c;和vue2的选项式不同 vue3的组合式使用的是setup函数 通过返回值将数据和方法传到页面 返回值也可以是一个箭头函数 setup先于 data和method执行所有无法读取到this和data&#xff0c;method的内容&#xff0c…

顶顶通呼叫中心中间件-asr录音路径修改(mod_cti基于FreeSWITCH)

顶顶通呼叫中心中间件-asr录音路径修改(mod_cti基于FreeSWITCH) 录音路径模板。如果不是绝对路径&#xff0c;会把这个路径追加到FreeSWITCH的recordings后面。支持变量&#xff0c;比如日期 ${strftime(%Y-%m-%d)}。最后一个录音文件路径会保存到变量 ${cti_asr_last_record_…

C语言详解(动态内存管理)1

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f4a5;&#x1f4a5;个人主页&#xff1a;奋斗的小羊 &#x1f4a5;&#x1f4a5;所属专栏&#xff1a;C语言 &#x1f680;本系列文章为个人学习…

Django 传递额外参数给视图函数

本书1-7章样章及配套资源下载链接: https://pan.baidu.com/s/1OGmhHxEMf2ZdozkUnDkAkA?pwdnanc 源码、PPT课件、教学视频等&#xff0c;可以从前言给出的下载信息下载&#xff0c;大家可以评估一下。 在Django框架中&#xff0c;URLconf模块还支持一种传递额外参数给视图函…

2024-06-07 Unity 编辑器开发之编辑器拓展8 —— Scene 窗口拓展

文章目录 1 Handles 类1.1 Scene 响应函数1.2 自定义窗口中监听 Scene1.3 Handles 常用 API2.2.1 颜色控制2.2.2 文本2.2.3 线段2.2.4 虚线2.2.5 圆弧2.2.6 圆2.2.7 立方体2.2.8 几何体2.2.9 移动、旋转、缩放2.2.10 自由移动 / 旋转 2 Scene 窗口中显示 GUI3 HandleUtility4 G…

Python 将CSV文件转为PDF文件

CSV文件通常用于存储大量的数据&#xff0c;而PDF文件则是一种通用的文档格式&#xff0c;便于与他人共享和打印。将CSV文件转换成PDF文件可以帮助我们更好地管理和展示数据。本文将介绍如何通过Python编程将CSV文件导出为PDF文件。 Python Excel库安装及介绍 在 Python 中&am…

GIGE 协议摘录 —— GVSP 协议(三)

系列文章目录 GIGE 学习笔记 GIGE 协议摘录 —— 设备发现&#xff08;一&#xff09; GIGE 协议摘录 —— GVCP 协议&#xff08;二&#xff09; GIGE 协议摘录 —— GVSP 协议&#xff08;三&#xff09; GIGE 协议摘录 —— 引导寄存器&#xff08;四&#xff09; GIGE 协议…

华为面经整理

文章目录 实习第一面准备提问相关算法相关 第一面结果提问环节 总结 实习 第一面准备 提问相关 操作系统有哪些功能 进程管理&#xff1a; 进程调度、进程同步和通信、多任务处理 内存管理&#xff1a; 内存分配、虚拟内存技术、内存保护 文件系统管理&#xff1a; 文件存储…

基础概念解析:SOCKS5代理究竟是什么?SOCKS5代理ip使用场景有哪些?

在当今数字化时代&#xff0c;网络安全和隐私保护已成为我们日常生活中不可忽视的问题。随着网络攻击手段的日益复杂&#xff0c;如何安全地访问互联网资源成为了一个亟待解决的问题。SOCKS5代理作为一种先进的网络协议&#xff0c;为我们提供了解决这一问题的有效方案。 本文…

活动预热丨在 AGI Playground 2024 遇见一群 RTE+AI 的 Builders

6 月 22、23 日&#xff0c;北京。 AGI Playground 2024&#xff0c;这个夏日最火热的 AGI 盛会。 王小川、杨植麟等 AGI 创业者悉数参加。 RTE 开发者社区的 builders 和 RTE Open Day 也将在现场&#xff01; 我们将为大家呈现两大板块&#xff1a; 01 实时开发挑战 Wor…

OpenCV-最小外接圆cv::minEnclosingCircle

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 函数原型 void minEnclosingCircle(InputArray points, Point2f& center, float& radius); 参数说明 InputArray类型的…

从openstack环境中将服务器镜像导出的简单办法

1 登录openstack的页面&#xff0c;找到计划导出的主机信息。 通过实例名称&#xff0c; IP地址&#xff0c;找到对应的记录。点击实例名称&#xff0c;进入详情页。 在这里主要可以知道&#xff0c;当前主机在服务器上的文件ID&#xff0c;可以按这个ID去找对应的目录。 还可…

Nvidia Jetson/Orin/算能 +FPGA+AI大算力边缘计算盒子:无人机自主飞行软件平台

案例简介 所主导开发的 Generalized Autonomy Aviation System (GAAS) 是为无人机以及城市空中交通 (UAM, Urban Air Mobility) 所设计的开源无人机自主飞行框架。通过 SLAM、路径规划和 Global Optimization Graph 等功能为无人机提供在无 GPS 与外部通信情况下的自主飞行功…