redis深入学习:lua编程(内含资料)
Posted 豆仔gogo
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了redis深入学习:lua编程(内含资料)相关的知识,希望对你有一定的参考价值。
为什么使用Lua?
减少网络开销,在 Lua 脚本中可以把多个命令放在同一个脚本中运行
原子操作,Redis 会将整个脚本作为一个整体执行,中间不会被其他命令插入。换句话说,编写脚本的过程中无需担心竞态条件
复用性,客户端发送的脚本会永远存储在 Redis 中,这意味着其他客户端可以复用这一脚本来完成同样的逻辑
Lua安装配置
# 依赖环境安装
yum -y install readline-devel ncurses-devel
# 下载安装
curl -R -O http://www.lua.org/ftp/lua-5.4.0.tar.gz
tar zxf lua-5.4.0.tar.gz
cd lua-5.4.0
make all test
make install
redis-cli中使用Lua
1、eval命令
EVAL script numkeys key [key …] arg [arg …]
实例:
127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 name1 name2 AlanWorker AlanShelby
1) "name1"
2) "name2"
3) "AlanWorker"
4) "AlanShelby"
2、调用redis中的命令:call()函数
eval "return > redis.call ('set',KEYS[1],'AlanShelby')" 1 name
3、evalsha 命令
EVAL 命令要求你在每次执行脚本的时候都发送一次脚本主体(script body)。
Redis 有一个内部的缓存机制,因此它不会每次都重新编译脚本,不过在很多场合,付出无谓的带宽来传送脚本主体并不是最佳选择。
为了减少带宽的消耗, Redis 实现了EVALSHA 命令,它的作用和 EVAL 一样,都用于对脚本求值,但它接受的第一个参数不是脚本,而是脚本的 SHA1 摘要。
EVALSHA 命令的表现如下
如果服务器还记得给定的 SHA1 校验和所指定的脚本,那么执行这个脚本
如果服务器不记得给定的 SHA1 校验和所指定的脚本,那么它返回一个特殊的错误,提醒用户使用 EVAL 代替 EVALSHA
获取脚本SHA1摘要,使用script命令:
(1)SCRIPT FLUSH :清除所有脚本缓存。
(2)SCRIPT EXISTS :根据给定的脚本校验,检查指定的脚本是否存在于脚本缓存。
(3)SCRIPT LOAD :将一个脚本装入脚本缓存,返回SHA1摘要,但并不立即运行它。
(4)SCRIPT KILL :杀死当前正在运行的脚本。
这里的 SCRIPT LOAD 命令就可以用来生成脚本的 SHA1 摘要,如下所示:
这里我们获取了 "return redis.call('set',KEYS[1],ARGV[1])" 这段脚本的 SHA1 摘要,有了摘要,再使用这段脚本就十分简单了:
4、redis-cli --eval 命令
redis-cli --eval myscript.lua key1 key2 , arg1 arg2 arg3
刚刚我们都是进入到redis-cli中执行eval命令,我们也可以把lua脚本保存在本地,然后通过redis-cli --eval
命令进行执行,实例如下:
vim redis.lua,写入如下内容:
local value = redis.call('get','name')
return value
然后执行:
./redis-cli -h 127.0.0.1 -p 6379 --eval redis.lua
python-redis客户端中使用Lua
pool = redis.ConnectionPool(host='xxx',port=6379, decode_responses=True)
conn = redis.Redis(connection_pool=pool)
def lua_test():
lua1 = """
"""
script2 = conn.register_script(lua1)
script2(keys=[],args=[])
Redis-Lua脚本深入探究
1、数据不一致问题
当将 Lua 脚本复制到附属节点, 或者将 Lua 脚本写入 AOF 文件时, Redis 需要解决这样一个问题:如果一段 Lua 脚本带有随机性质或副作用, 那么当这段脚本在附属节点运行时, 或者从** AOF 文件载入**重新运行时, 它得到的结果可能和之前运行的结果完全不同。
例如:
redis> EVAL "return redis.call('set', KEYS[1], get_random_number())" 1 number
OK
redis> GET number
"10086"
2、有害脚本的定义
只有在带有随机性的脚本进行写入时, 随机性才是有害的。
如果一个脚本只是执行只读操作, 那么随机性是无害的。
比如说, 如果脚本只是单纯地执行 RANDOMKEY
命令, 那么它是无害的;但如果在执行 RANDOMKEY
之后, 基于 RANDOMKEY
的结果进行写入操作, 那么这个脚本就是有害的。
3、Redis对Lua脚本的措施
不提供访问系统状态状态的库(比如系统时间库)。
禁止使用 loadfile 函数。
如果脚本在执行带有随机性质的命令(比如 RANDOMKEY ),或者带有副作用的命令(比如 TIME )之后,试图执行一个写入命令(比如 SET ),那么 Redis 将阻止这个脚本继续运行,并返回一个错误。
如果脚本执行了带有随机性质的读命令(比如 SMEMBERS ),那么在脚本的输出返回给 Redis 之前,会先被执行一个自动的字典序排序,从而确保输出结果是有序的。
用 Redis 自己定义的随机生成函数,替换 Lua 环境中
math
表原有的 math.random 函数和 math.randomseed 函数,新的函数具有这样的性质:每次执行 Lua 脚本时,除非显式地调用math.randomseed
,否则math.random
生成的伪随机数序列总是相同的。
经过这一系列的调整之后, Redis 可以保证被执行的脚本:
无副作用。
没有有害的随机性。
对于同样的输入参数和数据集,总是产生相同的写入命令。
4、Redis访问库
Redis的Lua解释器加载七个库:base,table,string, math, debug,cjson和cmsgpack。前几个都是标准库,充许你使用任何语言进行基本的操作。后面两个可以让Redis支持JSON和MessagePack—这是非常有用的功能,同时我也很想知道为什么常常看不到这种用法。
Web应用程序常常使用JSON作为api返回数据,你也许也可以把一堆JSON数据存到Redis的key中。当想访问某些JSON数据时,首先需要保存到一个hash中,使用Redis的JSON支持将非常方便:
if redis.call("EXISTS", KEYS[1]) == 1 then
local payload = redis.call("GET", KEYS[1])
return cjson.decode(payload)[ARGV[1]]
else
return nil
end
在这里我们检查看key是否存在,如不存在则快速返回nil。如存在则从Redis中获取JSON值,用cjson.decode()进行解析,然后返回请求内容。
redis-cli set apple '{ "color": "red", "type": "fruit" }'
=> OK
redis-cli eval "$(cat json-get.lua)" 1 apple type
=> "fruit"
加载这段脚本进你的Redis服务器,将JSON数据保存到Redis中,通常是hash。虽然我们每次访问时都必须解析,但只要你的对象很小,这个操作实际上是非常快的。
如果你的API只是在内部提供,通常需要考虑效率上的问题,MessagePack 是比采用JSON更好的选择,它更小,更快,在Redis(更多场合也是如此),MessagePack是JSON更好的替代品。
if redis.call("EXISTS", KEYS[1]) == 1 then
local payload = redis.call("GET", KEYS[1])
return cmsgpack.unpack(payload)[ARGV[1]]
else
return nil
end
5、Redis与lua数据类型转换
redis -> lua
redis类型 | lua类型 | 描述 |
---|---|---|
redis_integer | lua_number | redis整数转为lua数字 |
redis_bulk | lua_string | redis bulk回复转为lua字符串 |
redis_multi bulk | Lua_table | redis 多条bulk回复转为Lua 表 |
redis_status | lua_table | redis状态回复转为lua表,表内ok域包含状态信息 |
redis_error | lua_table | redis错误回复转为lua表,表内的err域包含错误信息 |
redis_nil、 redis_multi nil |
lua_boolean_false | redis的nil回复和nil多条回复转为lua的布尔值false |
lua -> redis
lua类型 | redis类型 | 描述 |
---|---|---|
lua_number | redis_integer | lua数字转为redis整数 |
lua_string | redis_bluk | lua字符串转为redis bulk回复 |
lua_table、 lua_array |
redis_multi bulk | lua表(数组)转为redis多条bulk回复 |
lua_table_ok | redis status | 一个带单个ok域的lua表,转为redis 状态回复 |
lua_table_err | redis_error | 一个带单个err域的lua表,转为redis 错误回复 |
lua_boolean_false | redis nil | lua布尔值false转为redis的nil回复 |
从lua转换到redis有一条额外的规则,这条规则没有与其相对应的redis转换为lua的规则:
lua_boolean_true -> redis_integer_1,lua布尔值true转为redis整数1
6、全局变量保护
redis的lua脚本不允许创建全局变量,如果脚本需要在多次执行之间维持某种状态,可以借助外部redis key来保存状态,每次脚本执行前,获取redis相对应的key赋值给局部变量。在lua脚本中创建或访问一个全局变量,都会引起脚本停止,eval命令会返回一个错误:
redis的全局变量保护并不是百分百成功,有时候会在脚本中混入lua全局状态,可能会引发AOF持久化和主从复制都无法得到保证。redis建议不要在脚本中使用全局变量,可以使用local关键字定义脚本中的变量!
7、 日志记录
在redis中使用脚本不会自动记录日志,需要我们在脚本使用redis.log()手动保存日志信息。脚本保存的日志,只有那些与redis实例所设置的日志等级相同或更高级才会被记录。语法:redis.log(loglevel,message)。其中,message表示要记录的日志信息,是一个字符串;loglevel表示redis日志等级,有4个取值:
redis.LOG_DEBUG
redis.LOG_VERBOSE
redis.LOG_NOTICE
redis.LOG_WARNING
8、踩坑经验(常见错误)
表是Lua中的表达式,与很多流行语言不同。KEYS中的第一个元素是KEYS[1],第二个是KEYS[2](译注:不是0开始)
nil是表的结束符,[1,2,nil,3]将自动变为[1,2],因此在表中不要使用nil。
redis.call会触发Lua中的异常,redis.pcall将自动捕获所有能检测到的错误并以表的形式返回错误内容。
Lua数字都将被转换为整数,发给Redis的小数点会丢失,返回前把它们转换成字符串类型。
确保在Lua中使用的所有KEY都在KEY表中,否则在将来的Redis版中你的脚本都有不能被很好支持的危险。
Lua脚本和其它Redis操作一样,在脚本执行时,其它的一切都不能运行。考虑用脚本来护展Redis服务器能力,但要保持短小和有用。
参考资料
https://redisbook.readthedocs.io/en/latest/feature/scripting.html
https://zhuanlan.zhihu.com/p/44912922
https://blog.csdn.net/sym542569199/article/details/88746776
https://www.cnblogs.com/-wenli/p/13262877.html
●
●
●
●
●
以上是关于redis深入学习:lua编程(内含资料)的主要内容,如果未能解决你的问题,请参考以下文章