一、事务
所谓事务(Transaction) ,是指作为单个逻辑工作单元执行的一系列操作
二、ACID回顾
- Atomicity(原子性):构成事务的的所有操作必须是一个逻辑单元,要么全部执行,要么全部不执行。
- Consistency(一致性):数据库在事务执行前后状态都必须是稳定的或者是一致的。
- Isolation(隔离性):事务之间不会相互影响。
- Durability(持久性):事务执行成功后必须全部写入磁盘。
三、Redis事务
- Redis的事务是通过multi、exec、discard和watch这四个命令来完成的。
- Redis的单个命令都是原子性的,所以这里需要确保事务性的对象是命令集合。
- Redis将命令集合序列化并确保处于同一事务的命令集合连续且不被打断的执行
- Redis不支持回滚操作
四、事务命令
multi:用于标记事务块的开始,Redis会将后续的命令逐个放入队列中,然后使用exec原子化地执行这个命令队列
exec:执行命令队列
discard:清除命令队列
watch:监视key
unwatch:清除监视key
五、事务机制
1. 事务的执行
- 事务开始
在RedisClient中,有属性flags,用来表示是否在事务中,flags=REDIS_MULTI - 命令入队
RedisClient将命令存放在事务队列中(EXEC,DISCARD,WATCH,MULTI除外) - 事务队列
multiCmd *commands 用于存放命令 - 执行事务
RedisClient向服务器端发送exec命令,RedisServer会遍历事务队列,执行队列中的命令,最后将执行的结果一次性返回给客户端。
如果某条命令在入队过程中发生错误,redisClient将flags置为REDIS_DIRTY_EXEC,EXEC命令将会失败返回。
2. Watch的执行
使用WATCH命令监视数据库键,redisDb有一个watched_keys字典,key是某个被监视的数据的key,值是一个链表。记录了所有监视这个数据的客户端。
监视机制的触发,当修改数据后,监视这个数据的客户端的flags置为REDIS_DIRTY_CAS。
事务执行,RedisClient向服务器端发送exec命令,服务器判断RedisClient的flags,如果为REDIS_DIRTY_CAS,则清空事务队列。
3. Redis的弱事务性
- Redis语法错误
整个事务的命令在队列里都清除 - Redis运行错误
在队列里正确的命令可以执行 (弱事务性)
弱事务性 :
1、在队列里正确的命令可以执行 (非原子操作)
2、不支持回滚
Redis不支持事务回滚(为什么呢)
1、大多数事务失败是因为语法错误或者类型错误,这两种错误,在开发阶段都是可以预见的
2、Redis为了性能方面就忽略了事务回滚。 (回滚记录历史版本)
六、Lua脚本
lua是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
Lua应用场景:游戏开发、独立应用脚本、Web应用脚本、扩展和数据库插件。
1. Lua环境协作组件
从Redis2.6.0版本开始,通过内置的lua编译/解释器,可以使用EVAL命令对lua脚本进行求值。
脚本的命令是原子的,RedisServer在执行脚本命令中,不允许插入新的命令
脚本的命令可以复制,RedisServer在获得脚本后不执行,生成标识返回,Client根据标识就可以随时执行。
2. EVAL命令
通过执行redis的eval命令,可以运行一段lua脚本。
EVAL script numkeys key [key ...] arg [arg ...]
命令说明:
- script参数:是一段Lua脚本程序,它会被运行在Redis服务器上下文中,这段脚本不必(也不应该)定义为一个Lua函数。
- numkeys参数:用于指定键名参数的个数。
- key [key …]参数: 从EVAL的第三个参数开始算起,使用了numkeys个键(key),表示在脚本中所用到的那些Redis键(key),这些键名参数可以在Lua中通过全局变量KEYS数组,用1为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。
- arg [arg …]参数:可以在Lua中通过全局变量ARGV数组访问,访问的形式和KEYS变量类似(ARGV[1] 、 ARGV[2] ,诸如此类)。
3. lua脚本中调用Redis命令
- redis.call():
返回值就是redis命令执行的返回值
如果出错,则返回错误信息,不继续执行 - redis.pcall():
返回值就是redis命令执行的返回值
如果出错,则记录错误信息,继续执行 - 注意事项
在脚本中,使用return语句将返回值返回给客户端,如果没有return,则返回nil
4. EVALSHA
EVAL 命令要求你在每次执行脚本的时候都发送一次脚本主体(script body)。
Redis 有一个内部的缓存机制,因此它不会每次都重新编译脚本,不过在很多场合,付出无谓的带宽来传送脚本主体并不是最佳选择。
为了减少带宽的消耗, Redis 实现了 EVALSHA 命令,它的作用和 EVAL 一样,都用于对脚本求值,但它接受的第一个参数不是脚本,而是脚本的 SHA1 校验和(sum)
SCRIPT命令
- SCRIPT FLUSH :清除所有脚本缓存
- SCRIPT EXISTS :根据给定的脚本校验和,检查指定的脚本是否存在于脚本缓存
- SCRIPT LOAD :将一个脚本装入脚本缓存,返回SHA1摘要,但并不立即运行它
5. 脚本管理命令实现
使用redis-cli直接执行lua脚本。
test.lua
return redis.call('set',KEYS[1],ARGV[1])
./redis-cli -h 127.0.0.1 -p 6379 --eval test.lua name:6 , 'caocao' #,两边有空格
利用Redis整合Lua,主要是为了性能以及事务的原子性。因为redis帮我们提供的事务功能太差。
七、管道(pipeline),事务和脚本(lua)三者的区别
三者都可以批量执行命令
管道无原子性,命令都是独立的,属于无状态的操作
事务和脚本是有原子性的,其区别在于脚本可借助Lua语言可在服务器端存储的便利性定制和简化操作
脚本的原子性要强于事务,脚本执行期间,另外的客户端 其它任何脚本或者命令都无法执行,脚本的执行时间应该尽量短,不能太耗时的脚本