前段时间也读了下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()