Redis 的事务
Redis 的事务和 Mysql 的事务意思都是一样的,都表示把一系列的操作绑成一组,让这一组能够批量执行。
但是,Redis 的事务和 Mysql事务,有一些区别:
① Redis 是“弱化的原子性”,相比于 Mysql 而言,Mysql 的原子性就是保证了一组操作,要么全都执行成功,要么全都执行失败,如果执行失败,还会涉及到回滚操作,而 Redis 的原子性仅仅只是保证了一组操作批量执行,失败或者成功,它并不会关心,就算失败了也没有回滚操作。
② Redis 不保证一致性,因为,在 Redis 中不涉及到“约束” 和 “回滚” 操作,相比于MySQL 而言,MySQL 的一致性体现的就是事务运行前和运行后,结果都是合理有效的。
③ Redis 不具备隔离性,因为 Redis 是单线程模型,所以不会并发事务
④ Redis 的事务不具备持久性,因为 Redis 中的数据是保存在内存中的,虽然 Redis 也有持久性机制,可以通过 开启 AOF 或者 生成 RDB 文件,但是,如果把 AOF 关闭,或者不进行 RDB 持久化操作,那么它就不会进行持久化,而相比较MySQL来说,MySQL 在执行事务时,就直接把数据存储到了硬盘中,所以,Redis 的持久化和事务是没有什么关系的。
所以,Redis 的事务主要的意义主要是将一组操作批量执行,避免被其他客户端进行插队。
Redis 是如何实现事务的?
Redis 中实现事务,是针对每个开启事务的客户端,引入了一个存储事务的队列,此时客户端输入的命令,就会发给服务器并且进入这个队列,但是,此时队列中的任务也不是说立即就执行的,而是在当遇到“执行事务”命令的时候,此时就会把队列中的这些任务都按照顺序依次执行,所以,这就跟“多线程”中的加锁是以这样的,多线程使用加锁避免多个线程插队,Redis 就是使用事务避免了多个客户端插队。
Redis 的事务为啥搞得这么简单,为啥不设计成和MySQL一样强大呢???
如果想把某些功能设计的比较强大,肯定是要付出代价的,Mysql 的事务,在背后就付出了很大的代价,比如,MySQL的回滚操作,就要在额外开辟一块空间,来记录一些日志,记录一下每一步回退,在空间以及时间上都有很大的开销,所以,正因为这些原因,才有了Redis上场的机会。
啥时候使用 Redis 的事务呢???
如果我们只是需要把多个操作打包进行,使用 Redis 的事务是比较合适的,例如 “超卖场景”。
例如:现在有一款手机,只放货了 500 台,但如果让 501 个人下单成功,就构成了超卖。
比如,我现在有一种典型的写法来记录这 500 台手机的个数:
Redis 在集群环境中部署时,就无法使用“事务”
Redis 中事务的操作命令
开启事务
MULTI
从下图可以看到,在开启事务后,set 命令并没有生效,只是返回了一个 queued,这就说明,当前的命令,只是被放入了事务队列中,并没有生效,此时,如果通过另一个客户端获取的话是获取不到的。
执行事务
EXEC
输入 exec 命令之后,队列中的命令就会全部执行。
放弃当前事务
discard
当开启时候后,并且给服务器发起若干个命令之后,此时服务器重启了,此时的这个事务咋办???
此时的效果其实就等同于 discard
watch 命令
watch的作用:监控某个 key 是否在执行事务之前发生了改变
如下图:
有这样一种场景,客户端1开启了事务,并且设置了 key,但是,在执行事务之前,客户端2 修改了key,此时,按照常理来讲,key 应该是 222,但是,因为在客户端1中,只是将命令放到了队列中,并没有实际的去执行,只有在输入 exec 命令之后,才会执行,所以,最终的 key 的 value 是 111
对于上述这种在事务执行完之前,但key 发生了变化,此时就可以使用 watch 进行监控,如下图:
当 watch 监控了 key ,开启事务之后,在 set key 111 执行前,在另一个客户端中对 key 进行了修改,当在客户端1中,输入 exec 命令执行事务的时候,就会执行失败,如果没有使用 watch 进行监控的话,最后key 的value值就是 111
watch 的实现原理
watch 是基于版本号机制实现的,如下图:
例如,在执行 watch key 时,会给 key 设置一个“版本号”,可以理解成是一个整数,每次修改 key ,版本号都会变大。
假设,初始情况下,key 的版本号为 1,如果,从事务开启时到事务执行之前,对 key 进行了修改,此时 key 的版本号就会变大,当执行事务时,也就是 exec 时,就会判断一下,当前 key 的版本号和key watch 时的初始版本号是否一致,如果一致,才会真正的设置,如果不一致,则返回nil