参考:https://blog.csdn.net/zjf280441589/article/details/52716720
从 2.6版本 起, Redis 开始支持 Lua 脚本 让开发者自己扩展 Redis.
本篇博客主要介绍了 Lua 语言不一样的设计模型(相比于Java/C/C++、JS、PHP), 以及 Redis 对 Lua 的扩展, 最后结合 Lua 与 Redis 实现了一个支持过期时间的分布式锁. 我们希望这篇博客的读者朋友可以在读完这篇文字之后, 体会到 Lua 这门语言不一样的设计哲学, 以及 更加得心应手的使用/扩展 Redis.
案例-实现访问频率限制: 实现访问者 $ip 在一定的时间 $time 内只能访问 $limit 次.
- 非脚本实现
private boolean accessLimit(String ip, int limit, int time, Jedis jedis) {
boolean result = true;
String key = "rate.limit:" + ip;
if (jedis.exists(key)) {
long afterValue = jedis.incr(key);
if (afterValue > limit) {
result = false;
}
} else {
Transaction transaction = jedis.multi();
transaction.incr(key);
transaction.expire(key, time);
transaction.exec();
}
return result;
}
- 以上代码有两点缺陷
- 可能会出现竞态条件: 解决方法是用
WATCH
监控rate.limit:$IP
的变动, 但较为麻烦; - 以上代码在不使用
pipeline
的情况下最多需要向Redis请求5条指令, 传输过多.
- 可能会出现竞态条件: 解决方法是用
-
Lua脚本实现
Redis 允许将 Lua 脚本传到 Redis 服务器中执行, 脚本内可以调用大部分 Redis 命令, 且 Redis 保证脚本的原子性:- 首先需要准备Lua代码: script.lua
local key = "rate.limit:" .. KEYS[1]
local limit = tonumber(ARGV[1])
local expire_time = ARGV[2]
local is_exists = redis.call("EXISTS", key)
if is_exists == 1 then
if redis.call("INCR", key) > limit then
return 0
else
return 1
end
else
redis.call("SET", key, 1)
redis.call("EXPIRE", key, expire_time)
return 1
end
private boolean accessLimit(String ip, int limit, int timeout, Jedis connection) throws IOException {
List<String> keys = Collections.singletonList(ip);
List<String> argv = Arrays.asList(String.valueOf(limit), String.valueOf(timeout));
return 1 == (long) connection.eval(loadScriptString("script.lua"), keys, argv);
}
// 加载Lua代码
private String loadScriptString(String fileName) throws IOException {
Reader reader = new InputStreamReader(Client.class.getClassLoader().getResourceAsStream(fileName));
return CharStreams.toString(reader);
}
- Lua 嵌入 Redis 优势:
- 减少网络开销: 不使用 Lua 的代码需要向 Redis 发送多次请求, 而脚本只需一次即可, 减少网络传输;
- 原子操作: Redis 将整个脚本作为一个原子执行, 无需担心并发, 也就无需事务;
- 复用: 脚本会永久保存 Redis 中, 其他客户端可继续使用.
redisson实现分布式锁的原理:
参考:https://www.jianshu.com/p/de5a69622e49
Lua语言模型
Lua是一种 便于嵌入应用程序 的脚本语言, 具备了作为通用脚本语言的所有功能. 其高速虚拟机实现非常有名(Lua的垃圾回收很有讲究- 增量垃圾回收 ), 在很多虚拟机系性能评分中都取得了优异的成绩. Home lua.org.
以嵌入式为方针设计的Lua, 在默认状态下简洁得吓人. 除了基本的数据类型外, 其他一概没有. 标注库也就 Coroutine、String、Table、Math、 I/O、OS, 再加上Modules包加载而已. 参考: Lua 5.1 Reference Manual - Standard Libraries(中文版: Lua 5.1 参考手册).
注: 本文仅介绍 Lua 与众不同的设计模型(对比 Java/C/C++、JavaScript、Python 与 Go), 语言细节可参考文内和附录推荐的文章以及Lua之父Roberto Ierusalimschy的<Programming in Lua>(中文版: <LUA程序设计(第2版)>)
基础
1. 数据类型
- 作为通用脚本语言, Lua的数据类型如下:
- 数值型:
全部为浮点数型, 没有整型;
只有nil
和false
作为布尔值的false
, 数字0
和空串(‘’
/‘
- 数值型: