【限流】基于springboot(拦截器) + redis(执行lua脚本)实现注解限流

实现了滑动窗口,固定窗口,令牌桶,漏桶四种限流算法,并且支持各种扩展和修改,源码简单易上手。
Giteehttps://gitee.com/sir-tree/rate-limiter-spring-boot-starter

一、令牌桶算法—入桶量限制

在客户端请求打过来的时候,会去桶里拿令牌,拿到就请求成功,拿不到不好意思,服务器拒绝服务。

关键点
  • 桶有多大?您定😊
  • 桶里的令牌一开始有多少?您定😊
  • 桶啥时候补充令牌?您定😊
  • 补充多少令牌?您定😊

在想好以上关键点后,我想你已经明白了。

主要步骤

请求打过来

  1. 看一下是否到补充桶的时候了,没到就到第2步,到了就补充完后再继续第2步
  2. 看一下桶里有没有令牌,有就拿走,没有就不好意思,服务器拒绝服务。
扩展:加个黑名单

请求打过来

  1. 看一下在不在黑名单,在就拒绝服务,不在,就进行第2步
  2. 看一下是否到补充桶的时候了,没到就到第2步,到了就补充完后再继续第3步
  3. 看一下桶里有没有令牌,有就拿走,没有就不好意思,加黑名单
Lua实现
-- 参数初始化
local key = KEYS[1]
local maxCapacity = tonumber(ARGV[1]) -- <= 桶有多大/桶里的令牌一开始有多少 我写成一样的了
local fill = tonumber(ARGV[2]) -- <= 补充多少令牌
local now = tonumber(ARGV[3])
local duration = tonumber(ARGV[4]) -- <= 桶啥时候补充令牌
local blackKey = key..'-black'
local blackDuration = tonumber(ARGV[5]) -- <= 黑名单多久后放开
local expire = math.ceil(now / fill) * duration-- 黑名单
local blackValue = redis.call('get', blackKey)
if blackValue thenreturn -1;
end-- 当前值
local kValue = redis.call('get', key)-- 第一次
if not kValue then-- 当前消耗#刷新时间#容量kValue = table.concat({'0', tostring(now + duration), tostring(maxCapacity)}, '#')
end-- 解析
local parts = {}
for part in string.gmatch(kValue, "[^#]+") dotable.insert(parts, part)
endlocal value = tonumber(parts[1])
local refresh = tonumber(parts[2])
local capacity = tonumber(parts[3])
local fillTimes = math.floor((now - refresh) / duration) + 1-- 刷新桶令牌
if now >= refresh thenvalue = 0-- 补充capacity = capacity + fill * fillTimes-- 最多补满if capacity > maxCapacity thencapacity = maxCapacityend-- 刷新时间refresh = now + duration
end-- 拿完
if value == capacity then-- 加入黑名单if blackDuration > 0 thenredis.call('set', blackKey, '_', 'PX', blackDuration)redis.call('del', key)endreturn -1
end-- 期间
value = value + 1
kValue = table.concat({tostring(value), tostring(refresh), tostring(capacity)}, '#')
redis.call('set', key, kValue, 'PX', expire)return capacity - value

二、 漏桶算法—出桶量限制

在客户端请求打过来的时候,会去桶里放令牌(有唯一标识),放成功后尝试把自己放的令牌拿走,拿不走就不断的尝试拿,直到拿走;但如果放失败了,不好意思,服务器拒绝服务。

关键点
  • 桶有多大?您定😊
  • 哪些令牌可以被自己拿走?您定不了了😎,我定,这里有两个方案,公平的方案每个请求只能拿自己放的令牌,先放进来的令牌可以先被拿走不公平的方案是拿谁放的都可以,大家一起抢,谁抢到算谁的
  • 多长时间内最多可以拿走多少个令牌?‘多长时间’您定😊,‘最多可拿走’您定😊

在想好以上关键点后,我想你已经明白了。

主要步骤

请求打过来

  1. 放令牌成功就执行第2步,但桶满了放不了了,不好意思,服务器拒绝服务
  2. 不断尝试拿令牌(两种方案实现) –不断尝试拿!!不断尝试拿!!!不断尝试拿!!!
实现
-- 参数初始化
local key = KEYS[1]
local capacity = tonumber(ARGV[1]) -- <= 桶有多大
local pass = tonumber(ARGV[2]) -- <= 多长时间内最多可以拿走多少个令牌 -- 最多可以拿走多少个令牌
local duration = tonumber(ARGV[3]) -- <= 多长时间内最多可以拿走多少个令牌 -- 多长时间内
local fair = tonumber(ARGV[4]) -- <= 公平/不公平方案
local queueId = tonumber(ARGV[5]) -- <= 哪些令牌可以被自己拿走
local exceedQueueId = -(capacity + 1)
local legacyPassKey = key..'-legacyPass'
local queueKey = key..'-queue'-- redis初始化
local qMembers = redis.call('lRange', queueKey, 0, -1)
local lpValue = redis.call('get', legacyPassKey)
local lpExpire = redis.call('pTtl', legacyPassKey)local function min(q)if #q == 0 thenreturn 0endlocal mv = tonumber(q[1])local tmp = mvfor i = 2, #q dotmp = tonumber(q[i])if tmp < mv thenmv = tmpendendreturn mv
endlocal function addQueue(qId)if qId ~= exceedQueueId thenreturn qIdendif #qMembers == capacity thenreturn exceedQueueIdendqId = min(qMembers) - 1redis.call('rPush', queueKey, qId)table.insert(qMembers, tostring(qId))return qId
end-- 1 删除成功,0删除失败
local function delQueue()-- >= 队列不为空,队列有元素if #qMembers > 0 then-- 公平if fair > 0 then-- 第一个不是当前请求if tonumber(qMembers[1]) ~= queueId thenreturn 0elseredis.call('lPop', queueKey)table.remove(qMembers, 1)endelse -- 不公平redis.call('lRem', queueKey, 1, queueId)for i = #qMembers, 1, -1 doif tostring(qMembers[i]) == queueId thentable.remove(qMembers, i)endendendend-- 队列无元素,或队列第一个是return 1
end-- 第一次/到刷新时间-刷新pass
if lpExpire <= 0 thenlocal update = delQueue()if update == 1 thenpass = pass - 1elsequeueId = addQueue(queueId)endredis.call('set', legacyPassKey, pass, 'PX', duration)if update == 1 thenreturn passendreturn queueId
endlpValue = tonumber(lpValue)
-- 如果lpValue >= 0 表示当前不需要等待
if lpValue - 1 >= 0 thenif delQueue() == 0 thenreturn queueIdendlpValue = lpValue - 1redis.call('set', legacyPassKey, lpValue, 'PX', lpExpire)return lpValue
elsereturn addQueue(queueId)
end

三、 固定窗口算法

时间窗口,随时间变化,比如1秒10个,即时间窗口大小为1秒,窗口内最多允许10个请求,随时间变化,如0 ~ 1秒有一个时间窗口,1 ~ 2秒内又是一个时间窗口 … 且每个窗口内最多有10个请求,超过的就放弃。

关键点
  • 时间窗口内允许最多多少个请求?您定😊
  • 时间窗口大小是多大?您定😊

在想好以上关键点后,我想你已经明白了。

主要步骤

请求打过来

  1. 看一下还剩下多少个位置,不剩就服务器拒绝服务,否则成功
Lua实现
local key = KEYS[1]
local limit = tonumber(ARGV[1]) -- <= 时间窗口内允许最多多少个请求
local window = tonumber(ARGV[2]) -- <= 时间窗口大小是多大local expire = redis.call('pTtl', key)
local kValue = redis.call('get', key)if expire <= 0 thenredis.call('set', key, 1, 'PX', window)return 1 -- 通过
endkValue = tonumber(kValue)
if kValue == limit thenreturn 0 -- 限流
elseredis.call('set', key, kValue + 1, 'PX', expire)return 1 -- 通过
end

四、滑动窗口

和固定窗口类似,只是这个窗口是动的,怎么动?每次请求过来的时候,就以当前请求为窗口的右端点,而不是固定窗口那样固定。

Lua实现
local key = KEYS[1]
local limit = tonumber(ARGV[1]) -- <= 时间窗口内允许最多多少个请求
local window = tonumber(ARGV[2]) -- <= 时间窗口大小是多大
local now = tonumber(ARGV[3]) -- <= 就以当前请求为窗口的右端点local start = now - window
redis.call('zRemRangeByScore', key, 0, start) -- 移除上一个窗口期之前的数据
local size = redis.call('zCard', key)if size == limit thenreturn 0 -- 限制
elseredis.call('zAdd', key, now, now)redis.call('pExpire', key, window + 1000) -- 窗口期 + 1秒后过期return 1 -- 通过
end

讨论

  • 固定窗口问题:比如窗口是1秒最多10个,当流量在第一个窗口最后100毫秒满10个,且在第二个窗口前100毫秒满10个时,这200毫秒可以看做1秒内,也就是一个窗口,这就有问题(流量激增)…
  • 令牌桶问题:不能平稳的处理请求…
  • 滑动窗口:请求打满后,必须要延迟等到下一个窗口…
  • 漏桶:突发流量会有一堆被抛弃的…

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

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

相关文章

品牌百度百科词条需要什么资料?

品牌百度百科词条是一个品牌的数字化名片&#xff0c;更是品牌历史、文化、实力的全面展现。 作为一个相当拿得出手的镀金名片&#xff0c;品牌百度百科词条创建需要什么资料&#xff0c;今天伯乐网络传媒就来给大家讲解一下。 一、品牌基本信息&#xff1a;品牌身份的明确 品…

【漏洞复现】科达 MTS转码服务器 任意文件读取漏洞

0x01 产品简介 科达 MTS转码服务器是一款专业的视频转码设备&#xff0c;采用了高性能的硬件配置和先进的转码技术&#xff0c;能够实现高效、高质量的视频转码。 0x02 漏洞概述 科达 MTS转码服务器存在任意文件读取漏洞&#xff0c;攻击可以通过该漏洞读取服务器任意敏感信…

FSD自动驾驶泛谈

特斯拉的FSD&#xff08;Full-Self Driving&#xff0c;全自动驾驶&#xff09;系统是特斯拉公司研发的一套完全自动驾驶系统。旨在最终实现车辆在多种驾驶环境下无需人类干预的自动驾驶能力。以下是对FSD系统的详细探讨&#xff1a; 系统概述 FSD是特斯拉的自动驾驶技术&…

宠物领养|基于SprinBoot+vue的宠物领养管理系统(源码+数据库+文档)

宠物领养目录 基于Spring Boot的宠物领养系统的设计与实现 一、前言 二、系统设计 三、系统功能设计 1前台 1.1 宠物领养 1.2 宠物认领 1.3 教学视频 2后台 2.1宠物领养管理 2.2 宠物领养审核管理 2.3 宠物认领管理 2.4 宠物认领审核管理 2.5 教学视频管理 四、…

英语学习笔记--发音-元音和辅音

以下所有内容来自“AI豆包”。 需要注意的是&#xff0c;以下发音类似仅为帮助你理解的中文发音示例&#xff0c;与英语元音的实际发音可能存在一定差异。如果你想更准确地学习英语元音的发音&#xff0c;建议你多听、多读、多模仿英语原声材料&#xff0c;并寻求专业英语教师…

零基础自学前端到达到什么水平才能找工作?

零基础自学前端到达到什么水平才能找工作&#xff1f; 零基础自学前端到达到什么水平才能找工作&#xff1f;从这个字眼的表面上来回答这个问题&#xff0c;但是是前端水平越高越好咯。前端技术人才只有不断通过学习、项目的事件来不断充实提高自己的技术&#xff0c;随之而来&…

【Jenkins】持续集成与交付 (四):修改Jenkins插件下载地址、汉化

🟣【Jenkins】持续集成与交付 (四):修改Jenkins插件下载地址、汉化 一、修改Jenkins插件下载地址二、汉化Jenkins三、关于Jenkins💖The Begin💖点点关注,收藏不迷路💖 一、修改Jenkins插件下载地址 由于Jenkins官方插件地址下载速度较慢,我们可以通过修改下载地址…

Jupyter 容器环境: Debian 11离线部署SSH Server总结

在基于continuumio/anaconda3镜像搭建的jupyter环境中,想在pycharm中进行远程debug代码,但容器中没有部署ssh server导致无法进行。 本文讲述如何在anaconda3容器中安装ssh server 安装步骤 修改云源 echo "deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bulls…

Unity中WWWForm与 JsonUtility.ToJson区别

在Unity中&#xff0c;WWWForm 和 JsonUtility.ToJson 是两个用于处理网络请求中数据的工具&#xff0c;但它们的功能和用途有着显著的区别。 WWWForm WWWForm 是Unity早期版本中用于构建HTTP表单数据的类。它主要用于WWW类的POST请求&#xff0c;允许你添加表单字段&#xf…

6、FreeCAD的设计

一、FreeCAD的模块化设计&#xff08;插件系统&#xff09; 模块化设计是成功的软件架构的关键设计原则。FreeCAD采用了与Salome平台相似的模块化结构&#xff0c;后者是一个开源的CAE平台&#xff0c;包含几何建模、网格划分、FEM和CFD求解器模块。FreeCAD拥有生成新模块的基…

vue2迁移到vue3,v-model的调整

项目从vue2迁移到vue3&#xff0c;v-model不能再使用了&#xff0c;需要如何调整&#xff1f; 下面只提示变化最小的迁移&#xff0c;不赘述vue2和vue3中的常规写法。 vue2迁移到vue3&#xff0c;往往不想去调整之前的代码&#xff0c;以下就使用改动较小的方案进行调整。 I…

Vue3 中setup模式下component标签 is属性动态绑定组件空白显示问题

先看官网说明&#xff1a; <!-- 组件会在 currentTabComponent 改变时改变 --> <component v-bind:is"currentTabComponent"></component>在上述示例中&#xff0c;currentTabComponent 可以包括 已注册组件的名字&#xff0c;或 一个组件的选项…

美国洛杉矶服务器托管需要了解什么?

美国洛杉矶拥有多个数据中心提供服务器托管服务&#xff0c;这些服务通常包括弹性云服务器、裸金属服务器等。以下是一些关于美国洛杉矶服务器托管的详细信息&#xff0c;rak部落小编为您整理发布。 1. **数据中心分布**&#xff1a;在洛杉矶有多处数据中心&#xff0c;它们提供…

CVPR 小样本土地覆盖制图 张洪艳教授团队获挑战赛冠军

提出了一个广义的基于少镜头分割的框架&#xff0c;以更新高分辨率土地覆盖制图中的新类&#xff0c;分为三个部分:(a)数据预处理:对基础训练集和新类的少镜头支持集进行分析和扩充;(b)混合分割结构:将多基学习器和改进的投影到正交原型(POP)网络相结合&#xff0c;增强基类识别…

CSS中文本样式(详解网页文本样式)

目录 一、Text介绍 1.概念 2.特点 3.用法 4.应用 二、Text语法 1.文本格式 2.文本颜色 3.文本的对齐方式 4.文本修饰 5.文本转换 6.文本缩进 7.color&#xff1a;设置文本颜色。 8.font-family&#xff1a;设置字体系列。 9.font-size&#xff1a;设置字体大小。…

Laravel5.4 反序列化

文章目录 0x01 环境搭建0x02 POP 链0x03 exp0x04 总结 前言&#xff1a;CC 链复现的头晕&#xff0c;还是从简单的 Laravel 开始吧。 laravel 版本&#xff1a;5.4 0x01 环境搭建 laravel安装包下载地址 安装后配置验证页面。在 /routes/web.php 文件中添加一条路由&#xf…

数据库基础--MySQL简介以及基础MySQL操作

数据库概述 数据库&#xff08;DATABASE&#xff0c;简称DB&#xff09; 定义:是按照数据结构来组织、存储和管理数据的仓库.保存有组织的数据的容器(通常是一个文件或一组文件) 数据库管理系统(Database Management System,简称DBMS) 专门用于管理数据库的计算机系统软件;…

共享旅游卡项目如何做线上运营?分享运营的3个核心点!

疫情三年下来&#xff0c;还能保持活跃的实体行业&#xff0c;可能就是旅游行业。别看很多美食餐饮的&#xff0c;看起来很赚钱&#xff0c;除开成本&#xff0c;其实也是赚点辛苦钱。 从2023年疫情放开&#xff0c;很多人都涌入到旅游行业。但因为绝大多数都是门外汉&#xf…

iBarcoder for Mac:一站式条形码生成软件

在数字化时代&#xff0c;条形码的应用越来越广泛。iBarcoder for Mac作为一款专业的条形码生成软件&#xff0c;为用户提供了一站式的解决方案。无论是零售、出版还是物流等行业&#xff0c;iBarcoder都能轻松应对&#xff0c;助力用户实现高效管理。 iBarcoder for Mac v3.14…

算法人生(14):从“探索平衡策略”看“生活工作的平衡之道”

在强化学习中&#xff0c;有一种策略叫“探索平衡策略Exploration-Exploitation Trade-off&#xff09;”&#xff0c;这种策略的核心是在探索未知领域&#xff08;以获取更多信息&#xff09;和利用已知信息&#xff08;来最大化即时回报&#xff09;之间寻求平衡&#xff0c;…