最近在研究分布式爬虫,需要用Redis做一些信息统计的工作,其实是为了扩展scrapy原本的StatsCollector。
其中像inc_value有内置的命令可以使用,但是像max_value这样的,需要先获取某个Key的值,然后根据值的大小,决定是否设置新的值,是没有内置的命令可以使用,只能自己用Redis事务去实现,在网上搜索了很多相关的资料,但一直都不是很明白。
大多数的实现都和官方文档一样:
>>> with r.pipeline() as pipe:
... while 1:
... try:
... pipe.watch('OUR-SEQUENCE-KEY')
... current_value = pipe.get('OUR-SEQUENCE-KEY')
... next_value = int(current_value) + 1
... pipe.multi()
... pipe.set('OUR-SEQUENCE-KEY', next_value)
... pipe.execute()
... break
... except WatchError:
... continue
先实例化一个Pipeline对象,然后Watch一个或多个key, 然后获取值并处理值。然后开启事务,添加命令,执行事务。
一直不明白为什么要Watch,为什么Watch后可以获取命令的返回值,而不Watch却无法获取返回值。反正就是无法理解上述的流程。
今天研究了一下python redis客户端的源码,对这个流程感悟很多。
Redis在处理事务的时候,对事务中的每一条命令是不会立即执行,而是会将其放入一个队列中,当执行EXEC命令是,才会执行队列中的命令。这样的话如果我需要在事务中根据某条命令的返回值决定下一步的动作,是无法实现的。Redis给出的解决办法就是Watch命令。
为什么Watch命令可以解决呢?
Watch命令是用来监视一个或者多个Key,其实就是为这些Key加了一个乐观锁(可以理解为记录下此时Key的Value以及一些其他的元数据)。然后当执行事务的时候,会首先检查Watch的Key的状态是否发生变化,如果发生变化,则这个事务就不会被执行。当执行EXEC, UNWATCH, DISCARD命令后,就是取消监控,即释放了乐观锁。
由于在事务中,无法获取命令的返回值。但是执行Watch后,可以获取命令的返回值。这样当我需要根据某个Key的值,做出判断时,可以先Watch这个Key,然后获取这个Key的值。再执行事务。如果在执行事务之前Key的状态发生改变,就不会执行这个事务了,并且会抛出异常,这样我们就知道事务没有被执行成功,一般情况下可以捕获这个异常,并重新执行,直到整个流程是原子的执行完成。