33

Redis | Redis 的事务二

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzI0MzA2OTc4MQ%3D%3D&%3Bmid=2247484328&%3Bidx=1&%3Bsn=0efd2b2285f47232385066a5327bd222
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

遗留问题

上篇关于整理 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

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

F7zu2uq.jpg!mobile


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK