Lua限流器的3种写法
学而不思则罔,思而不学则殆
引言
上篇文章讲解了Lua脚本,事务和Pipline之间的使用方式和性能差距,本篇文章将聚焦Lua脚本,我将用三种写法来展现如何实现一个Redis限流器
固定窗口限流
固定窗口限流也是最简单的限流算法,它将时间划分为固定大小的窗口,每个窗口内允许的请求次数是固定的,也是最好理解的lua脚本
-- 获取限流的键名,通常为一个唯一标识,如用户 ID、IP 等
local key = KEYS[1]
-- 获取该窗口内允许的最大请求次数
local limit = tonumber(ARGV[1])
-- 获取当前窗口的时间长度(秒)
local window = tonumber(ARGV[2])-- 获取当前键对应的请求次数
local current = tonumber(redis.call('get', key) or "0")-- 如果当前请求次数超过了限制,返回 0 表示限流
if current + 1 > limit thenreturn 0
else-- 否则请求次数加 1local count = redis.call('incr', key)-- 如果是第一次请求,设置过期时间,保证窗口的有效性if count == 1 thenredis.call('expire', key, window)end-- 返回 1 表示允许请求return 1
end
本质是一个时间长度也就是窗口中我只允许固定次数通过,所以由此我们可以延伸出滑动窗口
滑动窗口限流
滑动窗口限流算法在固定窗口的基础上进行了改进,它将时间窗口划分为更小的时间片,每个时间片记录该时间段内的请求次数,通过滑动窗口的方式统计一定时间内的总请求次数
-- 获取限流的键名
local key = KEYS[1]
-- 获取该窗口内允许的最大请求次数
local limit = tonumber(ARGV[1])
-- 获取当前时间戳(毫秒)
local now = tonumber(ARGV[2])
-- 获取滑动窗口的时间长度(毫秒)
local window = tonumber(ARGV[3])-- 移除窗口外的请求记录
redis.call('zremrangebyscore', key, 0, now - window)
-- 获取当前窗口内的请求数量
local current = tonumber(redis.call('zcard', key))-- 如果当前请求次数超过了限制,返回 0 表示限流
if current + 1 > limit thenreturn 0
else-- 否则将当前请求的时间戳添加到有序集合中redis.call('zadd', key, now, now)-- 返回 1 表示允许请求return 1
end
原本的维度是一个窗口,而现在是将窗口放大成时间单位,然后用颗粒度更小的窗口去获取次数,最后统计这个大窗口的流量不变就行
令牌桶限流
令牌桶算法可以说是一种比较常用的限流算法,它以固定的速率向桶中添加令牌,每个请求需要从桶中获取一个或多个令牌才能被处理
-- 获取令牌桶的键名
local key = KEYS[1]
-- 获取桶的容量
local capacity = tonumber(ARGV[1])
-- 获取令牌生成的速率(每秒生成的令牌数)
local rate = tonumber(ARGV[2])
-- 获取当前时间戳(秒)
local now = tonumber(ARGV[3])
-- 获取每个请求需要的令牌数
local tokens_needed = tonumber(ARGV[4])-- 获取上次更新的时间和剩余的令牌数
local last_update = tonumber(redis.call('hget', key, 'last_update') or now)
local tokens = tonumber(redis.call('hget', key, 'tokens') or capacity)-- 计算从上次更新到现在应该生成的令牌数
local new_tokens = math.min(capacity, tokens + (now - last_update) * rate)-- 如果剩余的令牌数足够,处理请求
if new_tokens >= tokens_needed then-- 更新剩余的令牌数local remaining_tokens = new_tokens - tokens_needed-- 更新上次更新的时间和剩余的令牌数redis.call('hset', key, 'last_update', now)redis.call('hset', key, 'tokens', remaining_tokens)-- 返回 1 表示允许请求return 1
else-- 否则返回 0 表示限流return 0
end
实战Java
一般我们的Lua脚本都是放在配置文件中管理,无论是阅读性还是维护性都是最优解
-
例如把上面的任一脚本创建于rate_limiter.lua文件中(自定义名称)
-
配合Jedis我们常用的redis命令框架
import redis.clients.jedis.Jedis; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.util.Arrays; import java.util.List;public class RedisRateLimiterFromConfigJedis {public static void main(String[] args) {// 连接 RedisJedis jedis = new Jedis("localhost", 6379);// 限流键名String key = "rate_limit:user:1";// 窗口内允许的最大请求次数int limit = 10;// 窗口时间长度(秒)int window = 60;// 读取 Lua 脚本文件String script = readScriptFromFile("rate_limiter.lua");// 键名参数列表List<String> keys = Arrays.asList(key);// 其他参数列表List<String> argsList = Arrays.asList(String.valueOf(limit), String.valueOf(window));// 执行 Lua 脚本Object result = jedis.eval(script, keys, argsList);if (result instanceof Long && (Long) result == 1) {System.out.println("允许请求");} else {System.out.println("请求被限流");}// 关闭连接jedis.close();}private static String readScriptFromFile(String filePath) {StringBuilder script = new StringBuilder();try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {String line;while ((line = reader.readLine()) != null) {script.append(line).append("\n");}} catch (IOException e) {e.printStackTrace();}return script.toString();} }
其中端口和文件名可以自定义
相关文章:
Lua限流器的3种写法
学而不思则罔,思而不学则殆 引言 上篇文章讲解了Lua脚本,事务和Pipline之间的使用方式和性能差距,本篇文章将聚焦Lua脚本,我将用三种写法来展现如何实现一个Redis限流器 固定窗口限流 固定窗口限流也是最简单的限流算法&#x…...

基于 GEE 利用插值方法填补缺失影像
目录 1 完整代码 2 运行结果 利用GEE合成NDVI时,如果研究区较大,一个月的影像覆盖不了整个研究区,就会有缺失的地方,还有就是去云之后,有云量的地区变成空值。 所以今天来用一种插值的方法来填补缺失的影像…...

linux部署ollama+deepseek+dify
Ollama 下载源码 curl -L https://ollama.com/download/ollama-linux-amd64.tgz -o ollama-linux-amd64.tgz sudo tar -C /usr -xzf ollama-linux-amd64.tgz启动 export OLLAMA_HOST0.0.0.0:11434 ollama serve访问ip:11434看到即成功 Ollama is running 手动安装deepseek…...
在微服务中,如何使用feign在各个微服务中进行远程调用
在微服务中,如何使用feign在不同微服务中进行远程调用 在微服务中,如何使用feign在不同微服务中进行远程调用 步骤: 第一步: 引入feign依赖 <dependency><groupId>org.springframework.cloud</groupId><…...

Kafka中的KRaft算法
我们之前的Kafka值依赖于Zookeeper注册中心来启动的,往里面注册我们节点信息 Kafka是什么时候不依赖Zookeeper节点了 在Kafka2.8.0开始就可以不依赖Zookeeper了 可以用KRaft模式代替Zookeeper管理Kafka集群 KRaft Controller和KRaft Leader的关系 两者关系 Lea…...

vue3 -- 集成 amap(高德地图)
🍍效果 本文介绍了如何在 Vue 3 项目中集成高德地图(AMap),并使用 PoiPicker 实现地点搜索功能。 文章首先通过 AMapLoader 异步加载高德地图 API,并初始化 Map 实例。同时,借助 AMapUI 组件库引入 PoiPicker,绑定搜索输入框,实现地点选择功能。PoiPicker 监听用户的 …...
基于用户的协同过滤算法推荐
import numpy as np 计算用户之间的相似度(这里使用余弦相似度) def cosine_similarity(user1, user2): numerator np.dot(user1, user2) denominator np.linalg.norm(user1) * np.linalg.norm(user2) return numerator / denominator if denominato…...

4.python+flask+SQLAlchemy+达梦数据库
前提 1.liunx Centos7上通过docker部署了达梦数据库。从达梦官网下载的docker镜像。(可以参考前面的博文) 2.windows上通过下载x86,win64位的达梦数据库,只安装客户端,不安装服务端。从达梦官网下载达梦数据库windows版。(可以参考前面的博文) 这样就可以用windows的达…...

神经网络常见激活函数 4-LeakyReLU函数
文章目录 LeakyReLU函数导函数函数和导函数图像优缺点pytorch中的LeakyReLU函数tensorflow 中的LeakyReLU函数 LeakyReLU LeakyReLU: Leaky Rectified Linear Unit 函数导函数 LeakyReLU函数 L e a k y R e L U { x x > 0 p x x < 0 p ∈ ( 0 , 1 ) \rm …...

PHP盲盒商城系统源码 晒图+免签+短信验证+在线回收 thinkphp框架
源码介绍 PHP盲盒商城系统源码 晒图免签短信验证在线回收 thinkphp框架 源码前端uniapp开发,可以打包成APP(非H5封壳)H5,接其他平台支付通道,前后端全开源 H5盲盒首页可以直接开盒新UI 修复优化BUG,修复无…...
单例模式详解(Java)
单例模式详解(Java) 一、引言 1.1 概述单例模式的基本概念和重要性 单例模式是一种常用的软件设计模式,它确保一个类在整个应用程序中只有一个实例,并提供一个全局访问点来访问这个唯一实例。这种模式在资源管理、配置设置和日志记录等方面非常有用,因为它们通常只需要…...
2025年度Python最新整理的免费股票数据API接口
在2025年这个充满变革与机遇的年份,随着金融市场的蓬勃发展,量化交易逐渐成为了投资者们追求高效、精准交易的重要手段。而在这个领域中,一个实时、准确、稳定的股票API无疑是每位交易者梦寐以求的工具。 现将200多个实测可用且免费的专业股票…...

2.10学习总结
今天接着看了数据结构,但是跟指针有关的看不懂(万恶的指针),写了考试的补题。 #include <stdio.h> #include <stdlib.h> int a[1000005]; int main() {int n,i,x0;scanf("%d",&n);for(i1;i<n;i){x;i…...

原生鸿蒙版小艺APP接入DeepSeek-R1,为HarmonyOS应用开发注入新活力
原生鸿蒙版小艺APP接入DeepSeek-R1,为HarmonyOS应用开发注入新活力 在科技飞速发展的当下,人工智能与操作系统的融合正深刻改变着我们的数字生活。近日,原生鸿蒙版小艺APP成功接入DeepSeek-R1,这一突破性进展不仅为用户带来了更智…...

从Word里面用VBA调用NVIDIA的免费DeepSeekR1
看上去能用而已。 选中的文字作为输入,运行对应的宏即可;会先MSGBOX提示一下,然后相关内容追加到word文档中。 需要自己注册生成好用的apikey Option ExplicitSub DeepSeek()Dim selectedText As StringDim apiKey As StringDim response A…...

【SpringBoot篇】基于Redis分布式锁的 误删问题 和 原子性问题
文章目录 ??Redis的分布式锁??误删问题 ??解决方法??代码实现 ??原子性问题 ??Lua脚本 ?利用Java代码调用Lua脚本改造分布式锁??代码实现 ??Redis的分布式锁 Redis的分布式锁是通过利用Redis的原子操作和特性来实现的。在分布式环境中,多个应用…...

【JVM详解三】垃圾回收机制
一、对象是否存活 强引用:Object obj new Object(); 只要强引用还在,垃圾收集器永远不会回收掉被引用的对象。在不用对象的时将引用赋值为 null,能够帮助垃圾回收器回收对象。比如 ArrayList 的 clear() 方法实现。软引用(SoftRe…...

MySQL的字符集(Character Set)和排序规则(Collation)
MySQL的字符集(Character Set)和排序规则(Collation) 字符集(Character Set)和排序规则(Collation)是数据库中处理文本数据的两个核心概念,二者紧密相关但作用不同。 1…...

2025影视泛目录站群程序设计_源码二次开发新版本无缓存刷新不变实现原理
1. 引言 本设站群程序计书旨在详细阐述苹果CMS泛目录的创新设计与实现,介绍无缓存刷新技术、数据统一化、局部URL控制及性能优化等核心功能,以提升网站访问速度和用户体验。 2. 技术概述 2.1 无缓存刷新技术 功能特点: 内容不变性&#x…...

常用的python库-安装与使用
常用的python库函数 yield关键字openslide库openslide库的安装-linuxopenslide的使用openslide对象的常用属性 cv2库numpy库ASAP库-multiresolutionimageinterface库ASAP库的安装ASAP库的使用 concurrent.futures.ThreadPoolExecutorxml.etree.ElementTree库skimage库PIL.Image…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...

CTF show Web 红包题第六弹
提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框,很难让人不联想到SQL注入,但提示都说了不是SQL注入,所以就不往这方面想了 先查看一下网页源码,发现一段JavaScript代码,有一个关键类ctfs…...

3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序
一、开发准备 环境搭建: 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 项目创建: File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...

2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...

华为OD机试-食堂供餐-二分法
import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...