前言
简单介绍一下Lua。
正文
为了保证多条命令组合的原子性,Redis提供了简单的事务功能以及集 成Lua脚本来解决这个问题。
前面提及到pipline,也提及到pipline 并不是原子性的,如果多条命令想达到一定的原子性怎么破呢?
多条语句达到原子性,一般而言,我们想到的会是事务。
简单地说,事务表示一 组动作,要么全部执行,要么全部不执行。
Redis提供了简单的事务功能,将一组需要一起执行的命令放到multi和 exec两个命令之间。multi命令代表事务开始,exec命令代表事务结束,它们之间的命令是原子顺序执行的。
multi命令代表事务开始,exec命令代表事务结束,它们之间的命令是原子顺序执行的。
这里需要解释一下?
前面的pipline是不是原子性的,为什么这么说呢? 因为前面pipline 也是将加入队列,但是不能保证原子性。
如果要停止事务的执行,可以使用discard命令代替exec命令即可。
如果事务中的命令出现错误,Redis的处理机制也不尽相同。
这是我们开发需要关注的地方。
- 命令错误,这是比较幸运的错误。
整个事务是不会去执行的,这种错误还是比较好解决。
- 运行时候错误
就是说运行了第1、2条,然后第3条出错,或者写错了。
可以看到Redis并不支持回滚功能,sadd user:a:follow user:b命令已 经执行成功,开发人员需要自己修复这类问题。
同样即使我们没有发生错误,那么事务也是可能有问题的。
思考一下mysql 有几种情况发生,什么不可重复读等。
这些都是锁的问题,那么就看一下redis里面的锁吧。
有些应用场景需要在事务之前,确保事务中的key没有被其他客户端修 改过,才执行事务,否则不执行(类似乐观锁)。
Redis提供了watch命令来 解决这类问题。
下面执行一下下面的流程,看下变化。
然后你发现该事务没有执行成功。
也就是说一但watch有变化,那么执行就不会进行下去。
Redis提供了简单的事务,之所以说它简单,主要是因为它不支持事务 中的回滚特性,同时无法实现命令之间的逻辑关系计算,当然也体现了 Redis的“keep it simple”的特性.
Lua语言提供了如下几种数据类型:booleans(布尔)、numbers(数 值)、strings(字符串)、tables(表格),和许多高级语言相比,相对简 单。下面将结合例子对Lua的基本数据类型和逻辑处理进行说明
https://www.runoob.com/lua/lua-tutorial.html
上面可以去看看,很快就能学会。
那么看下Lua语言对redis的处理。
- 看下在redis中如何执行Lua语言。
在Redis中执行Lua脚本有两种方法:eval和evalsha。
eval 脚本内容 key个数 key列表 参数列表
除了使用eval,Redis还提供了evalsha命令来执行Lua脚本。
首先要将Lua脚本加载到Redis服务端,得到该脚本的SHA1校验和, evalsha命令使用SHA1作为参数可以直接执行对应Lua脚本,避免每次发送 Lua脚本的开销。这样客户端就不需要每次执行脚本内容,而脚本也会常驻 在服务端,脚本功能得到了复用。
加载脚本:script load命令可以将脚本内容加载到Redis内存中,例如下 面将lua_get.lua加载到Redis中,得到SHA1 为:"7413dc2440db1fea7c0a0bde841fa68eefaf149c"
执行脚本:evalsha的使用方法如下,参数使用SHA1值,执行逻辑和 eval一致。
有点像存储过程。
Lua的Redis API:
Lua可以使用redis.call函数实现对Redis的访问,例如下面代码是Lua使用 redis.call调用了Redis的set和get操作:
redis.call("set", "hello", "world") redis.call("get", "hello")
eval 'return redis.call("get", KEYS[1])' 1 hello "world"
除此之外Lua还可以使用redis.pcall函数实现对Redis的调用,redis.call和 redis.pcall的不同在于,如果redis.call执行失败,那么脚本执行结束会直接返 回错误,而redis.pcall会忽略错误继续执行脚本,所以在实际开发中要根据 具体的应用场景进行函数的选择。
开发提示:
Lua可以使用redis.log函数将Lua脚本的日志输出到Redis的日志文件中, 但是一定要控制日志级别。 Redis3.2提供了Lua Script Debugger功能用来调试复杂的Lua脚本,具体 可以参考:http://redis.io/topics/ldb。
案例
Lua脚本功能为Redis开发和运维人员带来如下三个好处:
·Lua脚本在Redis中是原子执行的,执行过程中间不会插入其他命令。
·Lua脚本可以帮助开发和运维人员创造出自己定制的命令,并可以将这 些命令常驻在Redis内存中,实现复用的效果。
·Lua脚本可以将多条命令一次性打包,有效地减少网络开销。
案例:
实现:
管理Lua 脚本
Redis提供了4个命令实现对Lua脚本的管理,下面分别介绍。
-
script load script
-
script exists
此命令用于判断sha1是否已经加载到Redis内存中.
script exists a5260dd66ce02462c5b5231c727b3f7772c0bcc5
这样就说明没有被加载进去。
- script flush
此命令用于清除Redis内存已经加载的所有Lua脚本
- script kill
此命令用于杀掉正在执行的Lua脚本。如果Lua脚本比较耗时,甚至Lua 脚本存在问题,那么此时Lua脚本的执行会阻塞Redis,直到脚本执行完毕或 者外部进行干预将其结束。
模拟一下。
eval 'while 1==1 do end' 0
Redis提供了一个lua-time-limit参数,默认是5秒,它是Lua脚本的“超时 时间”,但这个超时时间仅仅是当Lua脚本时间超过lua-time-limit后,向其他 命令调用发送BUSY的信号,但是并不会停止掉服务端和客户端的脚本执 行,所以当达到lua-time-limit值之后,其他客户端在执行正常的命令时,将 会收到“Busy Redis is busy running a script”错误,并且提示使用script kill或者 shutdown nosave命令来杀掉这个busy的脚本
此时Redis已经阻塞,无法处理正常的调用,这时可以选择继续等待, 但更多时候需要快速将脚本杀掉。使用shutdown save显然不太合适,所以选 择script kill,当script kill执行之后,客户端调用会恢复.
那么kill 是否一定能成功呢? 不一定。
有一点需要注意,如果当前Lua脚本正在执行写操作,那么script kill将不会生效。
上面提示Lua脚本正在向Redis执行写命令,要么等待脚本执行结束要么 使用shutdown save停掉Redis服务。可见Lua脚本虽然好用,但是使用不当破 坏性也是难以想象的。
这种算是灾难模式。
结
下一节bitmaps,这个还是比较关键的,二进制存储,很多业务上需要。