python使用redis事务时遇到multi、watch和锁的问题

前段时间也读了下python的redis库的源码,看的时候也有些跳跃和粗糙,但是基本大概的过了下…. …. 越来越发现redis很是不简单…    有时间分享下对于redis库的一些理解。 

redis本身应对外部请求的是单任务的,也是多线程安全的,这个大家都应该知道的, 所以才会经常有人用redis做计数服务。 但是对于redis的事务处理,我一开始理解是有问题,先前以为multi  exec是可以保证绝对的原子的,以为他不单单是原子,而且还会加锁….  结果,不是的…  

原文链接是 xiaorui.cc    http://xiaorui.cc/?p=1394


     经过一顿bug带来的痛苦经历之后,才发现,我的理解居然是有问题的….    话说同事朱伟这段时间要分享redis的高级用法,上次他就针对redis经典案例讲了3个小时…. 他是读过redis源码的人…   跟他讨论了下redis事务之后,我自己总结并小范围测试下,总结了这篇文章,如果有不对的地方,请大家拍砖。。。。

multi exec ,他的原子只能是在其中的,没有锁的逻辑。 这个一定要注意,他只能保证,他里面的那堆命令不会被其他客户端的命令参杂在一起。

那么我们这里想一个问题,如果我有一个key是100数字,我们客户端配置了multi之后,要进行incr渐增,但是还没有incr操作的时候,张三也连入了redis,并且把key改成了200。

那么我继续incr的搞了好几次 … … 会发现incr和exec之后的结果是200+ ,按照官方的意思,这是对的,对不对要看你的场景。  要解决别人的干扰咋整?   继续看… 

redis 127.0.0.1:6379> set c 100
OK
redis 127.0.0.1:6379> get c
"100"
redis 127.0.0.1:6379> MULTI
OK
redis 127.0.0.1:6379> INCR c
QUEUED
redis 127.0.0.1:6379> INCR c
QUEUED
redis 127.0.0.1:6379> EXEC
1) (integer) 201
2) (integer) 202
redis 127.0.0.1:6379> get c
"202"
redis 127.0.0.1:6379>

redis在2.2之后加入了watch的功能, 那么watch是啥?  

WATCH 命令提供了在开始事务前监视一个或多个键的能力。如果这些键中的任何一个在执行事务前发生改变,整个事务就会被取消并抛出 WatchError 异常。

watch是监控key的时候,如果数据有变动的话,multi exec会失败的… 如果没有变动,那么就正常推送进去。    看下面的测试,我们注意到了redis的return nil失败。 失败的原因是,在multi或incr之前,我用另外一个客户端做了数据的变动。 你的事务提交给去后,redis会判断watch的那个key,有没有变动的。

redis 127.0.0.1:6379> get c
"202"
redis 127.0.0.1:6379> watch c
OK
redis 127.0.0.1:6379> MULTI
OK
redis 127.0.0.1:6379> INCR c
QUEUED
redis 127.0.0.1:6379> INCR c
QUEUED
redis 127.0.0.1:6379> EXEC
(nil)


加上watch key之后,是可以保证针对key的事物原子性。  multi exec和pipeline做都有一个批次的一次,但是他们之间又有什么区别?   

pipeline批量执行的时候,有可能是会被别的客户端打扰的。但是multi的话,他里面的逻辑是原子的,是一起执行的。

那么问题来了,有时候进行watch key,但有些场景我们需要一个互斥锁,redis实现锁还是比较容易的,有兴趣的朋友可以看看我以前的文章。 


http://xiaorui.cc/2014/12/19/python%E4%BD%BF%E7%94%A8redis%E5%AE%9E%E7%8E%B0%E5%8D%8F%E5%90%8C%E6%8E%A7%E5%88%B6%E7%9A%84%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81/


另外这里提一下,用python实现watch multi的方式,代码如下.

#xiaorui.cc
.. with r.pipeline() as pipe:
...     while 1:
...         try:
...             # 关注一个key
...             pipe.watch('xiaorui.cc')
...             current_value = pipe.get('xiaorui.cc')
...             next_value = int(current_value) + 1
...             #事物开始
...             pipe.multi()
...             pipe.set('xiaorui.cc', next_value)
...             # 事务结束
...             pipe.execute()
...             # 把命令推送过去
...             break
...        except WatchError:
...              #如果客户端有变动的话,那么就会触发这个异常。 
...             continue

python redis的模块封装的不错,里面带了一个pipe.reset(),意思就是重试,一直到watch的key不再被其他的客户端影响变化的时候,才break退出。 

... pipe = r.pipeline()
... while 1:
...     try:
...         pipe.watch('xiaorui.cc')
...         #这里可以尽情的写,自己的逻辑
...         pipe.execute()
...         break
...     except WatchError:
...         continue
...     finally:
...         pipe.reset()



大家觉得文章对你有些作用! 如果想赏钱,可以用微信扫描下面的二维码,感谢!
另外再次标注博客原地址  xiaorui.cc

发表评论

邮箱地址不会被公开。 必填项已用*标注