原创

Redis | Redis 的事务二

本文字数:

4177

,大约阅读2分钟

一、遗留问题

上篇关于整理 Redis 事务的文章中遗留了一个问题,当一个客户端对一个 key 进行修改操作时,另外一个客户端也修改了同一个 key 导致数据产生了问题。上篇文章的地址是:Redis | Redis 的事务一

来回忆一下上次的问题。首先 flushdb 一下我们的 Redis,然后初始化一下我们的演示环境,命令如下:

127.0.0.1:6379> set tshirt 1
OK
127.0.0.1:6379> set zhang:money 1000
OK
127.0.0.1:6379> set zhang:tshirt 0
OK
127.0.0.1:6379> set li:money 1000
OK
127.0.0.1:6379> set li:tshirt 0
OK

首先库存的 tshirt 只有 1 件,然后 zhang 的 money 是 1000,tshirt 是 0;li 的 money 也是 1000,tshirt 也是 0。然后我们先让 zhang 下单,命令如下:

127.0.0.1:6379> multi
OK
127.0.0.1:6379> decr tshirt
QUEUED
127.0.0.1:6379> decrby zhang:money 100
QUEUED
127.0.0.1:6379> incr zhang:tshirt
QUEUED

然后,让 li 下单并支付(支付就是执行 exec 命令),再开启一个命令行窗口,输入如下命令:

127.0.0.1:6379> multi
OK
127.0.0.1:6379> decr tshirt
QUEUED
127.0.0.1:6379> decrby li:money 100
QUEUED
127.0.0.1:6379> incr li:tshirt
QUEUED
127.0.0.1:6379> exec
1) (integer) 0
2) (integer) 900
3) (integer) 1
127.0.0.1:6379>

可以看到,tshirt 的库存为 0,li 的 money 为 900,li 的 tshirt 为 1。最后让 zhang 支付,也就是执行 exec 命令,如下:

127.0.0.1:6379> exec
1) (integer) -1
2) (integer) 900
3) (integer) 1

可以看到,此时 tshirt 的库存为 -1,zhang 的 money 为 900,zhang 的 tshirt 为 1。这样就产生了问题。

二、watch 的使用

解决上面的问题有两种方法,一种方法是使用悲观锁,另外一种方法是使用乐观锁。使用悲观锁,就是在操作 tshirt 之前先上一把锁,让其他的客户端无法操作 tshirt,但是使用锁时会影响效率。而使用乐观锁,就是我先把 tshirt 的值记录一下,当我最后真正执行时,我把当前的值和我记录的值比对一下,如果相同我就执行,如果不同我就不执行。

Redis 提供的 watch 命令就相当于是一个乐观锁,在执行我们的命令时,我们先来 watch 一下 tshirt,就可以解决上面的问题了。

还原我们的环境,也就是执行下面的命令,如下:

127.0.0.1:6379> set tshirt 1
OK
127.0.0.1:6379> set zhang:money 1000
OK
127.0.0.1:6379> set zhang:tshirt 0
OK
127.0.0.1:6379> set li:money 1000
OK
127.0.0.1:6379> set li:tshirt 0
OK

初始化我们的环境后,我们开始让 zhang 进行下单,命令如下:

127.0.0.1:6379> watch tshirt
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decr tshirt
QUEUED
127.0.0.1:6379> decrby zhang:money 100
QUEUED
127.0.0.1:6379> incr zhang:tshirt
QUEUED

接着让 li 执行下单支付操作,命令如下:

127.0.0.1:6379> watch tshirt
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decr tshirt
QUEUED
127.0.0.1:6379> decrby li:money 100
QUEUED
127.0.0.1:6379> incr li:tshirt
QUEUED
127.0.0.1:6379> exec
1) (integer) 0
2) (integer) 900
3) (integer) 1

从上面命令的执行结果来看,没有什么差别,接着,让 zhang 进行支付,命令如下:

127.0.0.1:6379> exec
(nil)

从上面的命令可以看出,执行 exec 命令后,Redis 给我们返回一个 nil 命令。我们来手动看一下几个值吧,命令如下:

127.0.0.1:6379> get tshirt
"0"
127.0.0.1:6379> get zhang:money
"1000"
127.0.0.1:6379> get zhang:tshirt
"0"

可以看到,库存的值仍然为 0,而 zhang 的 money 和 tshirt 都没有改变。这样,当两个或多个客户端同时对一个 key 进行修改操作的时候,就保证了它们的隔离性。

watch 可以监视是一个 key,也可以监视多个 key,且 watch 的使用必须在 multi 前。

三、故障后的一致性

系统因为某些原因宕机或 Redis 发生中断,那么在重启后是否可以保证一致性呢?如果 Redis 没有开启 RDB 和 AOF,那么重启 Redis 之后,其中已经没有数据,也就谈不上一致性了。如果开启了 RDB ,RDB 中只是保存了数据,且在执行事务时,并不会进行 RDB 快照,因此和恢复之前的数据是一致的。如果 Redis 开启了 AOF,那么,使用 Redis 提供的一个 redis-check-aof 工具,使用该工具对 AOF 文件进行检查,该工具可以移除不完整的事务命令,从而保证数据的一致性。

四、持久化

如果 Redis 没有开启 RDB 和 AOF 的话,那么 Redis 就是当作纯粹的缓存进行使用,那么也就没有持久化一说。而如果开启了 RDB,但是在事务执行的时候,Redis 不会进行 RDB 快照,那么事务执行完成后发生了宕机,但是宕机之前 Redis 仍然没有到到达 RDB 的时间,那么此次的修改将不会被持久化。如果 Redis 系统开启了 AOF,那么在 appendfsync 被配置为 always 时,则可以进行持久化,但是这样会影响性能。如果使用了 no 和 everysec 参数,则不能保证数据的持久性。

五、总结

Redis 通过 watch 来保证了事务之间的隔离性,从而避免了多个客户端在修改同一个 key 时产生问题。

当我们执行事务时,发生了宕机,那么数据的一致性无论是否开启了 RDB 和 AOF,一致性都是可以保证的。

关于持久化,在开启了 AOF 后,并将 appendfsync 配置为 always 时,可以保证数据修改后的持久化。其余的,都无法保证数据的持久化。

当多个客户端都开启事务时,哪个客户端的 exec 先到达,就先执行哪个客户端的事务,为了可以让所有的事务命令一次性到达服务器端,可以使用 PipeLine 来完成。关于 PipeLine 的文章请参考:Redis | 管道 —— PipeLine

注:本文虽然是多方参考,并进行了自己的思考而整理,但是仍然可以会存在不准确或错误的地方,希望发现错误后可以进行指正。不甚感激!

Redis
大数据
缓存
分布式缓存
  • 作者:Netor0x86(联系作者)
  • 发表时间:2020-12-03 06:31
  • 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
  • 公众号转载:请在文末添加作者公众号二维码
  • 评论