redis中实现事务有两种方法:
1.WATCH监视键的变动,然后MULTI开始事务,EXEC提交事务
WATCH key [key…]:监视一个或多个键,如果在事务执行之前被修改,则事务被打断。
MULTI:标记一个事务的开始。
EXEC:执行事务中的所有命令。
DISCARD:取消一个事务,放弃执行事务中的所有命令。
WACTH检测 key 的变动,若在事务执行中,key 变动则取消事务,在事
务开启前调用,乐观锁实现(cas),
若被取消则事务返回 nil 。
例如:
实现加倍操作
WATCH score:10001 val = GET score:10001 MULTI SET score:10001 val*2 EXEC
缺点:乐观锁实现,所以失败需要重试,增加业务逻辑的复杂度,所以一般使用第二种方法。
2.使用lua脚本
lua 脚本实现原子性
redis 中加载了一个 lua 虚拟机;用来执行 redis lua 脚本,redislua 脚本的执行是原子性的,当某个脚本正在执行的时候,不会有其他命令或者脚本被执行。
lua 脚本当中的命令会直接修改数据状态,lua 脚本 mysql 存储区别:MySQL存储过程不具备事务性,所以也不具备原子性。
# 从文件中读取 lua脚本内容 cat test1.lua | redis-cli script load --pipe # 加载 lua脚本字符串 生成 sha1 > script load 'local val = KEYS[1]; return val' "b8059ba43af6ffe8bed3db65bac35d452f8115d8" # 检查脚本缓存中,是否有该 sha1 散列值的lua脚本 > script exists "b8059ba43af6ffe8bed3db65bac35d452f8115d8" 1) (integer) 1 # 清除所有脚本缓存 > script flush OK # 如果当前脚本运行时间过长(死循环),可以通过 script kill 杀死当前运行的脚本 > script kill (error) NOTBUSY No scripts in execution right now.
执行脚本文件
redis-cli --eval 脚本文件路径 参数..
EVAL
EVAL script numkeys key [key ...] arg [arg ...]
例如:
EVALSHA
EVALSHA sha1 numkeys key [key ...] arg [arg ...]
应用
# 1: 项目启动时,建立redis连接并验证后,先加载所有项目中使 用的lua脚本(script load); # 2: 项目中若需要热更新,通过redis-cli script flush;然 后可以通过订阅发布功能通知所有服务器重新加载lua脚本; # 3:若项目中lua脚本发生阻塞,可通过script kill暂停当前阻 塞脚本的执行;
ACID特性分析:
A 原子性;事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败;redis 不支持回滚;即使事务队列中的某个命令在执行期间出现了错误,整个事务也会继续执行下去,直到将事务队列中的所有命令都执行完毕为止。
C 一致性;事务的前后,所有的数据都保持一个一致的状态,不能违反数据的一致性检测。这里的一致性是指预期的一致性,而不是异常后的一致性。所以 redis 也不满足;这个争议很大:
redis 能确保事务执行前后的数据的完整约束。但是并不满足业务功能上的一致性。比如转账功能,一个扣钱一个加钱。可能出现扣钱执行错误,加钱执行正确,那么最终还是会加钱成功,系统凭空多了钱。
I 隔离性;各个事务之间互相影响的程度;redis 是单线程执行,天然具备隔离性。
D 持久性;redis 只有在 aof 持久化策略的时候,并且需要在redis.conf 中 appendfsync=always 才具备持久性。实际项目中几乎不会使用 aof 持久化策略。
lua 脚本满足原子性和隔离性;一致性和持久性不满足。