redis深入学习:lua编程(内含资料)

Posted 豆仔gogo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了redis深入学习:lua编程(内含资料)相关的知识,希望对你有一定的参考价值。

redis深入学习:lua编程(内含资料)

为什么使用Lua?

  1. 减少网络开销,在 Lua 脚本中可以把多个命令放在同一个脚本中运行

  2. 原子操作,Redis 会将整个脚本作为一个整体执行,中间不会被其他命令插入。换句话说,编写脚本的过程中无需担心竞态条件

  3. 复用性,客户端发送的脚本会永远存储在 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 摘要,如下所示:

redis深入学习:lua编程(内含资料)


这里我们获取了 "return redis.call('set',KEYS[1],ARGV[1])" 这段脚本的 SHA1 摘要,有了摘要,再使用这段脚本就十分简单了:

redis深入学习:lua编程(内含资料)


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编程(内含资料)

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

redis深入学习:lua编程(内含资料)


6、全局变量保护

redis的lua脚本不允许创建全局变量,如果脚本需要在多次执行之间维持某种状态,可以借助外部redis key来保存状态,每次脚本执行前,获取redis相对应的key赋值给局部变量。在lua脚本中创建或访问一个全局变量,都会引起脚本停止,eval命令会返回一个错误:

redis深入学习:lua编程(内含资料)



redis的全局变量保护并不是百分百成功,有时候会在脚本中混入lua全局状态,可能会引发AOF持久化和主从复制都无法得到保证。redis建议不要在脚本中使用全局变量,可以使用local关键字定义脚本中的变量!


redis深入学习:lua编程(内含资料)

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编程(内含资料)的主要内容,如果未能解决你的问题,请参考以下文章

redis Lua学习与坑

关于Redis持久化,你了解多少?(下)-内含整理资料

lua脚本~ Redis调用

学院官方整理2016年备战上半年软考通关秘籍(内含经典押题资料)

985大学的高材生只会写代码片段,丢人吗?

985高校的高材生只会写代码片段,丢人吗?