Lua语言是在1993年
由巴西一个大学研究小组发明,其设计目标是作为嵌入式程序移植到其他应用程序,它是由C语言实现的,虽然简单小巧但是功能强大。所以许多应用都选用它作为脚本语言:
- 在游戏领域,暴雪公司的"魔兽世界","愤怒的小鸟"
Nginx
可以将Lua
作为 嵌入式脚本语言,用于扩展其处理逻辑,特别是在高性能、高并发的场景下。- Redis 将 Lua 脚本作为一种原子操作机制 ,用于在服务端一次性执行多个命令 、封装复杂逻辑,避免多条命令之间的非原子性问题。。
Redis 2.6 版本通过内嵌支持 Lua 环境。也就是说一般的运用,是不需要单独安装Lua的。
通过使用LUA脚本:
- 减少网络开销,在Lua脚本中可以把多个命令放在同一个脚本中运行;
- 原子操作,redis会将整个脚本作为一个整体执行,中间不会被其他命令插入(Redis执行命令是单线程)。
- 复用性,客户端发送的脚本会永远存储在redis中,这意味着其他客户端可以复用这一脚本来完成同样的逻辑。
不过为了我们方便学习Lua语言,我们还是单独安装一个Lua。
Lua入门
安装Lua
Lua在linux中的安装
bash
1、wget http://www.lua.org/ftp/lua-5.3.6.tar.gz
2、tar -zxvf lua-5.3.6.tar.gz
# 进入解压的目录:
3、cd lua-5.3.6
4、make linux
5、make install(需要在root用户下)
Lua在Windows中的安装
从 Lua 5.3
(发布于 2015
)之后,Lua 官网停止提供 Windows 平台的预编译二进制包(.exe 安装器), 只提供源码,可以用如下方式编译源码:
- mingw32-make mingw(MinGW)
- cl /Fe:lua.exe lua.c(Visual Studio)
也可以到Github 下载5.3之前的版本:https://github.com/rjpcomputing/luaforwindows/releases
Lua基本语法
https://www.cainiaojc.com/lua/lua-basic-syntax.html
lua 教程:https://www.cainiaojc.com/lua/lua-tutorial.html
Lua的数据类型
Lua 中有 8 个基本类型分别为:nil、boolean、number、string、userdata、function、thread 和 table。
数据类型 | 描述 |
---|---|
nil | 这个最简单,只有值nil属于该类,表示一个无效值(在条件表达式中相当于false)。 |
boolean | 包含两个值:false和true。 |
number | 表示双精度类型的实浮点数 |
string | 字符串由一对双引号或单引号来表示 |
function | 由 C 或 Lua 编写的函数 |
userdata | 表示任意存储在变量中的C数据结构 |
thread | 表示执行的独立线路,用于执行协同程序 |
table | Lua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字、字符串或表类型。在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。 |
Lua 中的函数
bash
fuinction
fun1(c1,c2)
return
c1..c2 ## c1+c2
end # 结束
fuinction
fun2(c1,c2)
return
c1+c2 ## c1+c2
end # 结束
#调用函数
> print(fun1("1","2"))
12
> print(fun2("1","2"))
3
Lua 变量
Lua 中的变量全是全局变量,那怕是语句块或是函数里,除非用 local 显式声明为局部变量。局部变量的作用域为从声明位置开始到所在语句块结束。
变量的默认值均为 nil。
bash
> a = 5
> local b = 5
> function fun()
>> c=5
>> local d = 5
>> end
> fun()
> print(a,b,c,d)
5 nil 5 nil
循环控制
for 循环
bash
> a = {1,2,3}
> for i,v in ipairs(a) do
>> print(i,v)
>> end
1 1
2 2
3 3
if 语句
bash
> a =10;
> if(a<20)
>> then
>> print("a less than 20")
>> end
a less than 20
>
if嵌套
bash
> a = 10
> b = 20
> if(a==10)
>> then
>> if(b==20)
>> then
>> print("a=10,b=20")
>> end
>> end
a=10,b=20
垃圾回收
Lua 提供了以下函数collectgarbage ([opt [, arg]])
用来控制自动内存管理:
collectgarbage("collect"):
做一次完整的垃圾收集循环。通过参数 opt 它提供了一组不同的功能:collectgarbage("count"):
以 K 字节数为单位返回 Lua 使用的总内存数。 这个值有小数部分,所以只需要乘上 1024 就能得到 Lua 使用的准确字节数(除非溢出)。collectgarbage("restart"):
重启垃圾收集器的自动运行。collectgarbage("setpause"):
将 arg 设为收集器的 间歇率。 返回 间歇率 的前一个值。collectgarbage("setstepmul"):
返回 步进倍率 的前一个值。collectgarbage("step"):
单步运行垃圾收集器。 步长"大小"由 arg 控制。 传入 0 时,收集器步进(不可分割的)一步。 传入非 0 值, 收集器收集相当于 Lua 分配这些多(K 字节)内存的工作。 如果收集器结束一个循环将返回 true 。collectgarbage("stop"):
停止垃圾收集器的运行。 在调用重启前,收集器只会因显式的调用运行。
连接数据库
Lua 数据库的操作库:LuaSQL
。他是开源的,支持的数据库有:ODBC, ADO, Oracle, MySQL, SQLite 和 PostgreSQL 。
LuaSQL 可以使用 LuaRocks
来安装可以根据需要安装你需要的数据库驱动。
Redis中的Lua
使用LUA脚本的好处
- 减少网络开销,在Lua脚本中可以把多个命令放在同一个脚本中运行
- 原子操作,Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。(Redis执行命令是单线程)
- 复用性,客户端发送的脚本会存储在Redis中,这意味着其他客户端可以复用这一脚本来完成同样的逻辑
eval 命令
bash
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> get key1
(nil)
127.0.0.1:6379> eval "return redis.call('mset',KEYS[1],KEYS[2],ARGV[1],ARGV[2])" 2 key1 key2 first second
OK
127.0.0.1:6379> keys *
1) "key1"
2) "first"
127.0.0.1:6379> get key1
"key2"
127.0.0.1:6379>
evalsha 命令
但是eval命令要求你在每次执行脚本的时候都发送一次脚本,所以Redis 有一个内部的缓存机制,因此它不会每次都重新编译脚本,不过在很多场合,付出无谓的带宽来传送脚本主体并不是最佳选择。
为了减少带宽的消耗, Redis 提供了evalsha
命令,它的作用和 EVAL一样,都用于对脚本求值,但它接受的第一个参数不是脚本,而是脚本的 SHA1 摘要。
bash
127.0.0.1:6379> script load "return redis.call('set',KEYS[1],ARGV[1])"
"c686f316aaf1eb01d5a4de1b0b63cd233010e63d"
127.0.0.1:6379> evalsha "c686f316aaf1eb01d5a4de1b0b63cd233010e63d" 1 key1 keytest
OK
127.0.0.1:6379> get key1
"keytest"
127.0.0.1:6379>
Redis与限流

java
@Service
public class IsAcquire {
//引入一个Redis的Lua脚本的支持
private DefaultRedisScript<Long> getRedisScript;
//判断限流方法---类似于RateLimiter
public boolean acquire(String limitKey,int limit,int expire) throws Exception{
//连接Redis
Jedis jedis = new Jedis("127.0.0.1",6379);
getRedisScript =new DefaultRedisScript<>();
getRedisScript.setResultType(Long.class);//脚本执行返回值 long
getRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("rateLimiter.lua")));
Long result = (Long)jedis.eval(getRedisScript.getScriptAsString(),
1,limitKey,String.valueOf(limit),String.valueOf(expire));
if(result ==0){
return false;
}
return true;
}
public static void main(String[] args) throws Exception {
IsAcquire isAcquire = new IsAcquire();
for (int i = 0;i<10;i++){
Thread.sleep(1000);
if(isAcquire.acquire("iphone",2,5)){ //10秒只能进行2次
System.out.println("恭喜你,抢到iphone!");
}else{
System.out.println("-----------业务被限流");
}
}
}
}
rateLimiter.lua
bash
--java端送入三个参数(1个key,2个param )string
--limitKey(redis中key的值)
local key =KEYS[1];
--limit(次数)
local times = ARGV[1];
--expire(秒S)
local expire = ARGV[2];
--对key-value中的 value +1的操作 返回一个结果
local afterval= redis.call('incr',key);
if afterval ==1 then --第一次
redis.call('expire',key,tonumber(expire) ) --失效时间(1S) TLL 1S
return 1; --第一次不会进行限制
end
--不是第一次,进行判断
if afterval > tonumber(times) then
--限制了
return 0;
end
return 1;
限流算法
- 固定窗口
简单粗暴,但是有临界值问题
- 滑动窗口

存在瞬时陡增、资源开销大等问题。
- 漏桶
恒定输出速率,平滑处理请求,实现简单开销小,抗突发能力强。
但是也存在一定的弊端:响应性差,不适合实时性要求高的场景;突发请求会被丢弃或排队,无法及时处理;无法动态适应流量变化。
- 令牌桶
基于漏桶的弊端,出现了令牌桶:支持突发请求(有弹性)、处理速率可控且灵活、实时性更好、更符合真实系统流量模型、适合统计型限流 + 节流。
其实令牌桶也有一定的弊端:实现复杂度略高、可能瞬间处理大量请求、无法精准控制请求间隔、
限流算法对比
特性 / 算法 | 固定窗口 Fixed Window | 滑动窗口 Sliding Window | 漏桶 Leaky Bucket | 令牌桶 Token Bucket |
---|---|---|---|---|
🧠 核心机制 | 时间窗口计数,窗口跳变 | 时间窗口滑动,实时统计 | 固定速率出水 请求排队/丢弃 | 固定速率生成令牌 请求消耗令牌 |
🟢 优点 | ✔ 实现简单 ✔ 性能高 | ✔ 精度高 ✔ 限流更平滑 | ✔ 输出速率恒定 ✔ 防突发、防过载 ✔ 实现简单 | ✔ 支持突发流量 ✔ 响应更快 ✔ 兼顾限流与节流 ✔ 灵活配置 |
🔴 缺点 | ✘ 临界突发可能超限 ✘ 限流不平滑 | ✘ 实现较复杂 ✘ 占用内存高 | ✘ 不支持突发 ✘ 实时性差 ✘ 处理速率固定 ✘ 不能动态调节 | ✘ 实现略复杂 ✘ 突发放行可能冲击系统 |
📈 限流平滑性 | ❌ 差(容易突发) | ✅ 高 | ✅ 极高(匀速) | ✅ 中高(灵活) |
🚀 支持突发流量 | ❌ 不支持 | ❌ 部分支持 | ❌ 不支持 | ✅ 支持(可积累令牌) |
📉 实时响应性 | ✅ 高(立即判断) | ✅ 高 | ❌ 低(需等待出水) | ✅ 高(有令牌立即通过) |
🛠️ 实现复杂度 | ✅ 最简单 | ❌ 中等偏高(需时间队列) | ✅ 简单(计数器+时间戳) | ❌ 中等(需时间计算+桶状态) |
💾 资源占用 | ✅ 低 | ❌ 中/高 | ✅ 低 | ❌ 中等(维护令牌) |
🌐 分布式实现 | ✅ 易实现 | ❌ 较复杂(需队列/窗口同步) | ✅ 易实现(Redis计数器) | ❌ 略复杂(需原子令牌生成) |
🎯 适用场景 | 基础限流,单机 QPS 控制 如简单接口、控制台按钮 | 高精度限流 如频率限制、反爬虫 | 强保护系统,匀速节流 如数据库、第三方服务 | 实际生产高频限流 如 API 网关、用户请求处理 |