Redis 的并发竞争问题,主要是发生在并发写竞争。
考虑到 redis 没有像 db 中的 sql 语句,update val = val + 10 where ...,无法使用这种方式进行对数据的更新。
假如有某个 key = "price", value 值为 10,现在想把 value 值进行 + 10 操作。正常逻辑下,就是先把数据 key 为 price 的值读回来,加上 10,再把值给设置回去。如果只有一个连接的情况下,这种方式没有问题,可以工作得很好,但如果有两个连接时,两个连接同时想对还 price 进行 + 10 操作,就可能会出现问题了。
例如:两个连接同时对 price 进行写操作,同时加 10,最终结果我们知道,应该为 30 才是正确。
考虑到一种情况:
T1 时刻,连接 1 将 price 读出,目标设置的数据为 10+10 = 20。
T2 时刻,连接 2 也将数据读出,也是为 10,目标设置为 20。
T3 时刻,连接 1 将 price 设置为 20。
T4 时刻,连接 2 也将 price 设置为 20,则最终结果是一个错误值 20。
如何解决?
方案一:可以使用独占锁的方式,类似操作系统的 mutex 机制。(网上有例子,http://blog.csdn.net/black_ox/article/details/48972085 不过实现相对复杂,成本较高)
方案二:使用乐观锁的方式进行解决(成本较低,非阻塞,性能较高)
如何用乐观锁方式进行解决?
本质上是假设不会进行冲突,使用 redis 的命令 watch 进行构造条件。伪代码如下:
watch price
get price $price
$price = $price + 10
multi
set price $price
exec
解释一下:
watch 这里表示监控该 key 值,后面的事务是有条件的执行,如果从 watch 的 exec 语句执行时,watch 的 key 对应的 value 值被修改了,则事务不会执行。
同样考虑刚刚的场景,
T1 时刻,连接 1 对 price 进行 watch,读出 price 值为 10,目标计算为 20;
T2 时刻,连接 2 对 price 进行 watch,读出 price 值为 10,目标计算为 20;
T3 时刻,连接 2 将目标值为 20 写到 redis 中,执行事务,事务返回成功。
T4 时刻,连接 1 也对 price 进行写操作,执行事务时,由于之前已经 watch 了 price,price 在 T1 至 T4 之间已经被修改过了,所以事务执行失败。
综上,该乐观锁机制可以简单明了的解决了写冲突的问题。
又问:如果多个写操作同时过来,100 个写操作同时 watch,则最终只会有一个成功,99 个执行失败,何解?
如果同时进行有多个请求进行写操作,例如同一时刻有 100 个请求过来,那么只会有一个最终成功,其余 99 个全部会失败,效率不高。
而且从业务层面,有些是不可接受的场景。例如:大家同时去抢一个红包,如果背后也是用乐观锁的机制去处理,那每个请求后都只有一个人成功打开红包,这对业务是不可忍受的。
在这种情况下,如果想让总体效率最大化,
方案三:可以采用排队的机制进行。
将所有需要对同一个 key 的请求进行入队操作,然后用一个消费者线程从队头依次读出请求,并对相应的 key 进行操作。
这样对于同一个 key 的所有请求就都是顺序访问,正常逻辑下则不会有写失败的情况下产生 。从而最大化写逻辑的总体效率。