Redis的watch和事务实现乐观锁,实现更高性能的FIFO队列

2018/04/03 Server

Redis Watch命令

client1

127.0.0.1:6379> watch lc
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set lc 5
QUEUED
127.0.0.1:6379> exec
1) OK
127.0.0.1:6379> get lc
"5"
127.0.0.1:6379> watch lc
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set lc 99
QUEUED
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get lc
"111"

client2

127.0.0.1:6379> get lc
"5"
127.0.0.1:6379> set lc 111
OK

在client1执行watch lc 这个key,在multi中对lc进行写操作

在exec之前,在client2中,对lc修改:set lc 111

watch监听到lc的变化了, set lc 99 命令,在 exec的时候,执行不成功,返回了nil

最后lc的结果是111

client1

127.0.0.1:6379> watch lc
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set lc 114
QUEUED
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get lc
"115"

client2

127.0.0.1:6379> watch lc
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set lc 115
QUEUED
127.0.0.1:6379> exec
1) OK

假设 client2 先于client 对lc进行了写操作: set lc 115

client1 的exec是执行失败的,返回nil。 这样,最终的lc值为115

当有两个事务同时并发对一个资源进行写操作,先进行操作且事务提交的任务会成功,后进行事务提交的任务通过watch机制,对该资源执行会失败。实际测试,是在multi -> exec 之间代码块执行的命令都会失败,也就是事务中所有执行命令都一起失败。

watch和乐观锁

可以使用watch + multi + exec, 实现 乐观锁(简单的多版本控制)

使用乐观锁的机制,可以利用到FIFO(先进先出)的优先队列中。比如抢购,先抢到的人成功,后抢的人直接失败。如果不希望有失败发送,可以将失败的任务,又重新放回优先队列中

使用悲观锁的缺点很明显,会导致较长时间的阻塞等待。但会一定程度保证所有任务都执行,没有失败情况

如果要追求并发性能,可以接收失败情况,乐观锁的方式是更好的选择

如果对失败没有那么严格的要求,可以乐观锁+失败重试的方式

如果严格要求顺序,并且尽可能减少失败,用悲观锁的方式(给这块资源加锁)

关于队列方面,又有几种加锁方式:

  • 在入队的时候加锁(生产者时)
  • 在出队列的时候加锁(消费者时)
  • 在入队列和出队列的时候都加锁

需要根据具体的需求情况进行。大部分业务,应该是在出队列的时候加锁(消费者时),不用关心入队顺序情况,会是更好的选择

当然,选择用了Redis来实现,出现性能瓶颈很可能就转移到了Redis上

Search

    Table of Contents