Lua语言模型 与 Redis应用
Lua语言模型 与 Redis应用
标签: Java与NoSQL
从 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
--
-- Created by IntelliJ IDEA.
-- User: jifang
-- Date: 16/8/24
-- Time: 下午6:11
--
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
- Java
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 中, 其他客户端可继续使用.
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和空串(‘’/‘\0’)都是true; - 字符串
- 用户自定义类型
- 函数(function)
- 表(table)
- 数值型:
变量如果没有特殊说明为全局变量(那怕是语句块 or 函数内), 局部变量前需加
local关键字.
2. 关键字

3. 操作符

- Tips:
- 数学操作符的操作数如果是字符串会自动转换成数字;
- 连接
..自动将数值转换成字符串; - 比较操作符的结果一定是布尔类型, 且会严格判断数据类型(
'1' != 1);
函数(function)
在 Lua 中, 函数是和字符串、数值和表并列的基本数据结构, 属于第一类对象( first-class-object /一等公民), 可以和数值等其他类型一样赋给变量、作为参数传递, 以及作为返回值接收(闭包):
- 使用方式类似JavaScript:
-- 全局函数: 求阶乘
function fact(n)
if n == 1 then
return 1
else
return n * fact(n - 1)
end
end
-- 1. 赋给变量
local func = fact
print("func type: " .. type(func), "fact type: " .. type(fact), "result: " .. func(4))
-- 2. 闭包
local function new_counter()
local value = 0;
return function()
value = value + 1
return value
end
end
local counter = new_counter()
print(counter(), counter(), counter())
-- 3. 返回值类似Go/Python
local random_func = function(param)
return 9, 'a', true, "ƒ∂π", param
end
local var1, var2, var3, var4, var5 = random_func("no param is nil")
print(var1, var2, var3, var4, var5)
-- 4. 变数形参
local function square(...)
local argv = { ... }
for i = 1, #argv do
argv[i] = argv[i] * argv[i]
end
return table.unpack(argv)
end
print(square(1, 2, 3))
表(table)
Lua最具特色的数据类型就是表(Table), 可以实现数组、Hash、对象所有功能的万能数据类型:
-- array
local array = { 1, 2, 3 }
print(array[1], #array)
-- hash
local hash = { x = 1, y = 2, z = 3 }
print(hash.x, hash['y'], hash["z"], #hash)
-- array & hash
array['x'] = 8
print(array.x, #array)
- Tips:
- 数组索引从
1开始; - 获取数组长度操作符
#其’长度’只包括以(正)整数为索引的数组元素. - Lua用表管理全局变量, 将其放入一个叫
_G的table内:
- 数组索引从
-- pairs会遍历所有值不为nil的索引, 与此类似的ipairs只会从索引1开始递遍历到最后一个值不为nil的整数索引.
for k, v in pairs(_G) do
print(k, " -> ", v, " type: " .. type(v))
end
用
Hash实现对象的还有JavaScript, 将数组和Hash合二为一的还有PHP.
元表
Every value in Lua can have a metatable/元表. This metatable is an ordinary Lua table that defines the behavior of the original value under certain special operations. You can change several aspects of the behavior of operations over a value by setting specific fields in its metatable. For instance, when a non-numeric value is the operand of an addition, Lua checks for a function in the field “__add” of the value’s metatable. If it finds one, Lua calls this function to perform the addition.
The key for each event in a metatable is a string with the event name prefixed by two underscores__; the corresponding values are called metamethods. In the previous example, the key is “__add” and the metamethod is the function that performs the addition.
metatable中的键名称为事件/event, 值称为元方法/metamethod, 我们可通过getmetatable()来获取任一值的metatable, 也可通过setmetatable()来替换table的metatable. Lua 事件一览表:
对于这些操作, Lua 都将其关联到 metatable 的事件Key, 当 Lua 需要对一个值发起这些操作时, 首先会去检查其metatable中是否有对应的事件Key, 如果有则调用之以控制Lua解释器作出响应.
MetaMethods
MetaMethods主要用作一些类似C++中的运算符重载操作, 如重载+运算符:
local frac_a = { numerator = 2, denominator = 3 }
local frac_b = { numerator = 4, denominator = 8 }
local operator = {
__add = function(f1, f2)
local ret = {}
ret.numerator = f1.numerator * f2.denominator + f1.denominator * f2.numerator
ret.denominator = f1.denominator * f2.denominator
return ret
end,
__tostring = function(self)
return "{ " .. self.numerator .. " ," .. self.denominator .. " }"
end
}
setmetatable(frac_a, operator)
setmetatable(frac_b, operator)
local frac_res = frac_a + frac_b
setmetatable(frac_res, operator) -- 使tostring()方法生效
print(tostring(frac_res))
关于更多Lua事件处理可参考文档: Metamethods.
MetaTables 与 面向对象
Lua本来就不是设计为一种面向对象语言, 因此其面向对象功能需要通过元表(metatable)这种非常怪异的方式实现, Lua并不直接支持面向对象语言中常见的类、对象和方法: 其
对象和类通过表实现, 而方法是通过函数来实现.
上面的Event一览表内我们看到有__index这个事件重载,这个东西主要是重载了find key操作, 该操作可以让Lua变得有点面向对象的感觉(类似JavaScript中的prototype). 通过Lua代码模拟:
local function gettable_event(t, key)
local h
if type(t) == "table" then
local value = rawget(t, key)
if value ~= nil then
return value
end
h = getmetatable(t).__index
if h == nil then
return nil
end
else
h = getmetatable(t).__index
if h == nil then
error("error")
end
end
if type(h) == "function" then
-- call the handler
return (h(t, key))
else
-- or repeat opration on it
return h[key]
end
end
-- 测试
obj = { 1, 2, 3 }
op = {
x = function()
return "xx"
end
}
setmetatable(obj, { __index = op['x'] })
print(gettable_event(obj, x))
- 对于任何事件, Lua的处理都可以归结为以下逻辑:
- 如果存在规定的操作则执行它;
- 否则从元表中取出各事件对应的
__开头的元素, 如果该元素为函数, 则调用; - 如果该元素不为函数, 则用该元素代替
table来执行事件所对应的处理逻辑.
这里的代码仅作模拟, 实际的行为已经嵌入Lua解释器, 执行效率要远高于这些模拟代码.
方法调用的实现
面向对象的基础是创建对象和调用方法. Lua中, 表作为对象使用, 因此创建对象没有问题, 关于调用方法, 如果表元素为函数的话, 则可直接调用:
-- 从obj取键为x的值, 将之视为function进行调用
obj.x(foo)
不过这种实现方法调用的方式, 从面向对象角度来说还有2个问题:
- 首先:
obj.x这种调用方式, 只是将表obj的属性x这个函数对象取出而已, 而在大多数面向对象语言中, 方法的实体位于类中, 而非单独的对象中. 在JavaScript等基于原型的语言中, 是以原型对象来代替类进行方法的搜索, 因此每个单独的对象也并不拥有方法实体. 在Lua中, 为了实现基于原型的方法搜索, 需要使用元表的__index事件:
如果我们有两个对象a和b,想让b作为a的prototype需要setmetatable(a, {__index = b}), 如下例: 为obj设置__index加上proto模板来创建另一个实例:
proto = {
x = function()
print("x")
end
}
local obj = {}
setmetatable(obj, { __index = proto })
obj.x()
proto变成了原型对象, 当obj中不存在的属性被引用时, 就会去搜索proto.
- 其次: 通过方法搜索得到的函数对象只是单纯的函数, 而无法获得最初调用方法的表(接收器)相关信息. 于是, 过程和数据就发生了分离.JavaScript中, 关于接收器的信息可由关键字
this获得, 而在Python中通过方法调用形式获得的并非单纯的函数对象, 而是一个“方法对象” –其接收器会在内部作为第一参数附在函数的调用过程中.
而Lua准备了支持方法调用的语法糖:obj:x(). 表示obj.x(obj), 也就是: 通过冒号记法调用的函数, 其接收器会被作为第一参数添加进来(obj的求值只会进行一次, 即使有副作用也只生效一次).
-- 这个语法糖对定义也有效
function proto:y(param)
print(self, param)
end
- Tips: 用冒号记法定义的方法, 调用时最好也用冒号记法, 避免参数错乱
obj:y("parameter")
更多MetaTable介绍可参考文档Metatable与博客metatable和metamethod.
基于原型的编程
Lua虽然能够进行面向对象编程, 但用元表来实现, 仿佛把对象剖开看到五脏六腑一样.
<代码的未来>中松本行弘老师向我们展示了一个基于原型编程的Lua库, 通过该库, 即使没有深入解Lua原始机制, 也可以实现面向对象:
--
-- Author: Matz
-- Date: 16/9/24
-- Time: 下午5:13
--
-- Object为所有对象的上级
Object = {}
-- 创建现有对象副本
function Object:clone()
local object = {}
-- 复制表元素
for k, v in pairs(self) do
object[k] = v
end
-- 设定元表: 指定向自身`转发`
setmetatable(object, { __index = self })
return object
end
-- 基于类的编程
function Object:new(...)
local object = {}
-- 设定元表: 指定向自身`转发`
setmetatable(object, { __index = self })
-- 初始化
object:init(...)
return object
end
-- 初始化实例
function Object:init(...)
-- 默认不进行任何操作
end
Class = Object:new()
另存为prototype.lua, 使用时只需require()引入即可:
require("prototype")
-- Point类定义
Point = Class:new()
function Point:init(x, y)
self.x = x
self.y = y
end
function Point:magnitude()
return math.sqrt(self.x ^ 2 + self.y ^ 2)
end
-- 对象定义
point = Point:new(3, 4)
print(point:magnitude())
-- 继承: Point3D定义
Point3D = Point:clone()
function Point3D:init(x, y, z)
self.x = x
self.y = y
self.z = z
end
function Point3D:magnitude()
return math.sqrt(self.x ^ 2 + self.y ^ 2 + self.z ^ 2)
end
p3 = Point3D:new(1, 2, 3)
print(p3:magnitude())
-- 创建p3副本
ap3 = p3:clone()
print(ap3.x, ap3.y, ap3.z)
Redis - Lua
在传入到Redis的Lua脚本中可使用redis.call()/redis.pcall()函数调用Reids命令:
redis.call("set", "foo", "bar")
local value = redis.call("get", "foo")
redis.call()返回值就是Reids命令的执行结果, Redis回复与Lua数据类型的对应关系如下:
| Reids返回值类型 | Lua数据类型 |
|---|---|
| 整数 | 数值 |
| 字符串 | 字符串 |
| 多行字符串 | 表(数组) |
| 状态回复 | 表(只有一个ok字段存储状态信息) |
| 错误回复 | 表(只有一个err字段存储错误信息) |
注: Lua 的
false会转化为空结果.
redis-cli提供了EVAL与EVALSHA命令执行Lua脚本:
- EVAL
EVAL script numkeys key [key ...] arg [arg ...]
key和arg两类参数用于向脚本传递数据, 他们的值可在脚本中使用KEYS和ARGV两个table访问:KEYS表示要操作的键名,ARGV表示非键名参数(并非强制). - EVALSHA
EVALSHA命令允许通过脚本的SHA1来执行(节省带宽), Redis在执行EVAL/SCRIPT LOAD后会计算脚本SHA1缓存,EVALSHA根据SHA1取出缓存脚本执行.
创建Lua环境
为了在 Redis 服务器中执行 Lua 脚本, Redis 内嵌了一个 Lua 环境, 并对该环境进行了一系列修改, 从而确保满足 Redis 的需要. 其创建步骤如下:
- 创建基础 Lua 环境, 之后所有的修改都基于该环境进行;
- 载入函数库到 Lua 环境, 使 Lua 脚本可以使用这些函数库进行数据操作: 如基础库(删除了
loadfile()函数)、Table、String、Math、Debug等标准库, 以及CJSON、 Struct(用于Lua值与C结构体转换)、 cmsgpack等扩展库(Redis 禁用Lua标准库中与文件或系统调用相关函数, 只允许对 Redis 数据处理). - 创建全局表
redis, 其包含了对 Redis 操作的函数, 如redis.call()、redis.pcall()等; - 替换随机函数: 为了确保相同脚本可在不同机器上产生相同结果, Redis 要求所有传入服务器的 Lua 脚本, 以及 Lua 环境中的所有函数, 都必须是无副作用的纯函数, 因此Redis使用自制函数替换了 Math 库中原有的
math.random()和math.randomseed(). - 创建辅助排序函数: 对于 Lua 脚本来说, 另一个可能产生数据不一致的地方是那些带有不确定性质的命令(如: 由于
set集合无序, 因此即使两个集合内元素相同, 其输出结果也并不一样), 这类命令包括SINTER、SUNION、SDIFF、SMEMBERS、HKEYS、HVALS、KEYS 等.
Redis 会创建一个辅助排序函数__redis__compare_helper, 当执行完以上命令后, Redis会调用table.sort()以__redis__compare_helper作为辅助函数对命令返回值排序. - 创建错误处理函数: Redis创建一个
__redis__err__handler错误处理函数, 当调用redis.pcall()执行 Redis 命令出错时, 该函数将打印异常详细信息. - Lua全局环境保护: 确保传入脚本内不会将额外的全局变量导入到 Lua 环境内.
小心: Redis 并未禁止用户修改已存在的全局变量.
- 完成Redis的
lua属性与Lua环境的关联:
整个 Redis 服务器只需创建一个 Lua 环境.
Lua环境协作组件
Redis创建两个用于与Lua环境协作的组件: 伪客户端- 负责执行 Lua 脚本中的 Redis 命令,
lua_scripts字典- 保存 Lua 脚本:- 伪客户端
执行Reids命令必须有对应的客户端状态, 因此执行 Lua 脚本内的 Redis 命令必须为 Lua 环境专门创建一个伪客户端, 由该客户端处理 Lua 内所有命令:redis.call()/redis.pcall()执行一个Redis命令步骤如下:

lua_scripts字典
字典key为脚本 SHA1 校验和, value为 SHA1 对应脚本内容, 所有被EVAL和SCRIPT LOAD载入过的脚本都被记录到lua_scripts中, 便于实现SCRIPT EXISTS命令和脚本复制功能.
- 伪客户端
EVAL命令原理
EVAL命令执行分为以下三个步骤:
定义Lua函数:
在 Lua 环境内定义 Lua函数 : 名为f_前缀+脚本 SHA1 校验和, 体为脚本内容本身. 优势:- 执行脚本步骤简单, 调用函数即可;
- 函数的局部性可保持 Lua 环境清洁, 减少垃圾回收工作量, 且避免使用全局变量;
- 只要记住 SHA1 校验和, 即可在不知脚本内容的情况下, 直接调用 Lua 函数执行脚本(
EVALSHA命令实现).
将脚本保存到
lua_scripts字典;- 执行脚本函数:
执行刚刚在定义的函数, 间接执行 Lua 脚本, 其准备和执行过程如下:
1). 将EVAL传入的键名和参数分别保存到KEYS和ARGV, 然后将这两个数组作为全局变量传入到Lua环境;
2). 为Lua环境装载超时处理hook(handler), 可在脚本出现运行超时时让通过SCRIPT KILL停止脚本, 或SHUTDOWN关闭Redis;
3). 执行脚本函数;
4). 移除超时hook;
5). 将执行结果保存到客户端输出缓冲区, 等待将结果返回客户端;
6). 对Lua环境执行垃圾回收.
对于会产生随机结果但无法排序的命令(如只产生一个元素, 如 SPOP、SRANDMEMBER、RANDOMKEY、TIME), Redis在这类命令执行后将脚本状态置为
lua_random_dirty, 此后只允许脚本调用只读命令, 不允许修改数据库值.
实践
使用Lua脚本重新构建带有过期时间的分布式锁.
案例来源: <Redis实战> 第6、11章, 构建步骤:
- 锁申请
- 首先尝试加锁:
- 成功则为锁设定过期时间; 返回;
- 失败检测锁是否添加了过期时间;
- wait.
- 首先尝试加锁:
- 锁释放
- 检查当前线程是否真的持有了该锁:
- 持有: 则释放; 返回成功;
- 失败: 返回失败.
- 检查当前线程是否真的持有了该锁:
非Lua实现
String acquireLockWithTimeOut(Jedis connection, String lockName, long acquireTimeOut, int lockTimeOut) {
String identifier = UUID.randomUUID().toString();
String key = "lock:" + lockName;
long acquireTimeEnd = System.currentTimeMillis() + acquireTimeOut;
while (System.currentTimeMillis() < acquireTimeEnd) {
// 获取锁并设置过期时间
if (connection.setnx(key, identifier) != 0) {
connection.expire(key, lockTimeOut);
return identifier;
}
// 检查过期时间, 并在必要时对其更新
else if (connection.ttl(key) == -1) {
connection.expire(key, lockTimeOut);
}
try {
Thread.sleep(10);
} catch (InterruptedException ignored) {
}
}
return null;
}
boolean releaseLock(Jedis connection, String lockName, String identifier) {
String key = "lock:" + lockName;
connection.watch(key);
// 确保当前线程还持有锁
if (identifier.equals(connection.get(key))) {
Transaction transaction = connection.multi();
transaction.del(key);
return transaction.exec().isEmpty();
}
connection.unwatch();
return false;
}
Lua脚本实现
- Lua脚本: acquire
local key = KEYS[1]
local identifier = ARGV[1]
local lockTimeOut = ARGV[2]
-- 锁定成功
if redis.call("SETNX", key, identifier) == 1 then
redis.call("EXPIRE", key, lockTimeOut)
return 1
elseif redis.call("TTL", key) == -1 then
redis.call("EXPIRE", key, lockTimeOut)
end
return 0
- Lua脚本: release
local key = KEYS[1]
local identifier = ARGV[1]
if redis.call("GET", key) == identifier then
redis.call("DEL", key)
return 1
end
return 0
- Pre工具: 脚本执行器
/**
* @author jifang
* @since 16/8/25 下午3:35.
*/
public class ScriptCaller {
private static final ConcurrentMap<String, String> SHA_CACHE = new ConcurrentHashMap<>();
private String script;
private ScriptCaller(String script) {
this.script = script;
}
public static ScriptCaller getInstance(String script) {
return new ScriptCaller(script);
}
public Object call(Jedis connection, List<String> keys, List<String> argv, boolean forceEval) {
if (!forceEval) {
String sha = SHA_CACHE.get(this.script);
if (Strings.isNullOrEmpty(sha)) {
// load 脚本得到 sha1 缓存
sha = connection.scriptLoad(this.script);
SHA_CACHE.put(this.script, sha);
}
return connection.evalsha(sha, keys, argv);
}
return connection.eval(script, keys, argv);
}
}
- Client
public class Client {
private ScriptCaller acquireCaller = ScriptCaller.getInstance(
"local key = KEYS[1]\n" +
"local identifier = ARGV[1]\n" +
"local lockTimeOut = ARGV[2]\n" +
"\n" +
"if redis.call(\"SETNX\", key, identifier) == 1 then\n" +
" redis.call(\"EXPIRE\", key, lockTimeOut)\n" +
" return 1\n" +
"elseif redis.call(\"TTL\", key) == -1 then\n" +
" redis.call(\"EXPIRE\", key, lockTimeOut)\n" +
"end\n" +
"return 0"
);
private ScriptCaller releaseCaller = ScriptCaller.getInstance(
"local key = KEYS[1]\n" +
"local identifier = ARGV[1]\n" +
"\n" +
"if redis.call(\"GET\", key) == identifier then\n" +
" redis.call(\"DEL\", key)\n" +
" return 1\n" +
"end\n" +
"return 0"
);
@Test
public void client() {
Jedis jedis = new Jedis("127.0.0.1", 9736);
String identifier = acquireLockWithTimeOut(jedis, "ret1", 200 * 1000, 300);
System.out.println(releaseLock(jedis, "ret1", identifier));
}
String acquireLockWithTimeOut(Jedis connection, String lockName, long acquireTimeOut, int lockTimeOut) {
String identifier = UUID.randomUUID().toString();
List<String> keys = Collections.singletonList("lock:" + lockName);
List<String> argv = Arrays.asList(identifier,
String.valueOf(lockTimeOut));
long acquireTimeEnd = System.currentTimeMillis() + acquireTimeOut;
boolean acquired = false;
while (!acquired && (System.currentTimeMillis() < acquireTimeEnd)) {
if (1 == (long) acquireCaller.call(connection, keys, argv, false)) {
acquired = true;
} else {
try {
Thread.sleep(10);
} catch (InterruptedException ignored) {
}
}
}
return acquired ? identifier : null;
}
boolean releaseLock(Jedis connection, String lockName, String identifier) {
List<String> keys = Collections.singletonList("lock:" + lockName);
List<String> argv = Collections.singletonList(identifier);
return 1 == (long) releaseCaller.call(connection, keys, argv, true);
}
}
- 参考 & 推荐
- 代码的未来
- Redis入门指南
- Redis实战
- Redis设计与实现
- 云风的Blog: Lua与虚拟机
- Lua简明教程- CoolShell
- Lua-newbie
- Lua-Users
- redis.io
Lua语言模型 与 Redis应用的更多相关文章
- Lua 数据类型和 Redis 数据类型之间转换
当 Lua 通过 call() 或 pcall() 函数执行 Redis 命令的时候,命令的返回值会被转换成 Lua 数据结构. 同样地,当 Lua 脚本在 Redis 内置的解释器里运行时,Lua ...
- Lua脚本在redis分布式锁场景的运用
目录 锁和分布式锁 锁是什么? 为什么需要锁? Java中的锁 分布式锁 redis 如何实现加锁 锁超时 retry redis 如何释放锁 不该释放的锁 通过Lua脚本实现锁释放 用redis做分 ...
- 高并发 Nginx+Lua OpenResty系列(5)——Lua开发库Redis
Redis客户端 lua-resty-redis是为基于cosocket API的ngx_lua提供的Lua redis客户端,通过它可以完成Redis的操作.默认安装OpenResty时已经自带了该 ...
- Redis进阶之使用Lua脚本自定义Redis命令
[本文版权归微信公众号"代码艺术"(ID:onblog)所有,若是转载请务必保留本段原创声明,违者必究.若是文章有不足之处,欢迎关注微信公众号私信与我进行交流!] 1.在Redis ...
- 运维实践-最新Nginx二进制构建编译lua-nginx-module动态链接Lua脚本访问Redis数据库读取静态资源隐式展现
关注「WeiyiGeek」公众号 设为「特别关注」每天带你玩转网络安全运维.应用开发.物联网IOT学习! 希望各位看友[关注.点赞.评论.收藏.投币],助力每一个梦想. 本章目录 目录 0x0n 前言 ...
- Lua脚本在Redis事务中的应用实践
使用过Redis事务的应该清楚,Redis事务实现是通过打包多条命令,单独的隔离操作,事务中的所有命令都会按顺序地执行.事务在执行的过程中,不会被其他客户端发送来的命令请求所打断.事务中的命令要么全部 ...
- CentOS6.4 安装OpenResty和Redis 并在Nginx中利用lua简单读取Redis数据
1.下载OpenResty和Redis OpenResty下载地址:wget http://openresty.org/download/ngx_openresty-1.4.3.6.tar.gz Re ...
- nginx插入lua脚本访问redis
目标:收集用户日志 流程: 浏览器端get方法将数据传到nginx服务 nginx收集到数据,执行内嵌lua脚本,访问redis,根据token获得用户id 将日志信息存入文件 1.nginx安装,参 ...
- c#中用lua脚本执行redis命令
直接贴出代码,实现执行lua脚本的方法,用到的第三方类库是 StackExchange.Redis(nuget上有) 注:下面的代码是简化后的,实际使用要修改, using System; using ...
随机推荐
- ACE入门——ACE构建
ACE(ADAPTIVE Communication Environment),ACE入门的第一课就是要学习怎么在自己的系统上构建ACE. ACE是跨平台的,这是它的一个很重要的特性,ACE支持很多的 ...
- HTC Vive 叠影器无法创建设备
今天使用笔记本电脑打开SteamVR发生错误:SteamVR启动失败,"Shared IPC Compositor Connected Fail(306)",然后启动失败,在UI界 ...
- PHP观察者模式与Yii2.0事件
1.先看PHP观察者模式的实现: 想要使用事件.必须实现事件的基类.统一的addObserver和trigger方法 定义统一接口.所有的观察者都要实现此接口 //事件的基类 abstract cla ...
- [POJ 2248]Addition Chains
Description An addition chain for n is an integer sequence with the following four properties: a0 = ...
- [BZOJ 5071]小A的数字
Description 小A成为了一个数学家,他有一串数字A1,A2...An 每次可以进行如下操作,选择一个数字i(1<i<=n),将(Ai-1,Ai,Ai+1) 变为(Ai-1 + A ...
- [NOI2011]道路修建
题目描述 在 W 星球上有 n 个国家.为了各自国家的经济发展,他们决定在各个国家 之间建设双向道路使得国家之间连通.但是每个国家的国王都很吝啬,他们只愿 意修建恰好 n – 1 条双向道路. 每条道 ...
- [Codeforces]852I - Dating
题目大意:给定一棵n个点的树,每个点上有一个汉子或妹子,每人有一个权值,每次询问一条链上选出一对权值相等的男女有多少种选法.(n,q<=10^5) 做法:比较显然的树上莫队,熟悉序列莫队那套理论 ...
- 【网络流】【BZOJ1221】【HNOI2001】软件开发
原题链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1221 题意:你有3种方法进行对毛巾的处理,不同的处理方法有不同的cost,问你要如何规划才 ...
- hdu5651 xiaoxin juju needs help(逆元)
xiaoxin juju needs help Accepts: 150 Submissions: 966 Time Limit: 2000/1000 MS (Java/Others) Mem ...
- IScroll.js 学习笔记
一.css部分1.transform 旋转div { transform:rotate(7deg); -ms-transform:rotate(7deg); /* IE 9 */ -moz-trans ...