【Redis】事务

    在Redis中,事务是以multi/exec/discard进行的, 其中multi表示事务的开始, exec表示事务的执行,discard表示丢弃事务。

 3 127.0.0.1:6379> multi     # 事务的开始
 4 OK
 5 127.0.0.1:6379> set 1 1    # 添加命令
 6 QUEUED
 7 127.0.0.1:6379> set 2 2     
 8 QUEUED
 9 127.0.0.1:6379> exec       # 执行事务
10 1) OK
11 2) OK
12 127.0.0.1:6379> get 1
13 "1"
14 127.0.0.1:6379> get 2
15 "2"
16 127.0.0.1:6379> 
 1 127.0.0.1:6379> multi     # 事务的开始
 2 OK
 3 127.0.0.1:6379> set 3 3     # 添加命令
 4 QUEUED
 5 127.0.0.1:6379> set 4 4
 6 QUEUED
 7 127.0.0.1:6379> discard     #丢弃事务
 8 OK
 9 127.0.0.1:6379> exec        # 此时在执行exec命令会报错。
10 (error) ERR EXEC without MULTI
11 127.0.0.1:6379> 

     上面演示了一个完整的事务过程,所有的指令在 exec 之前不执行,而是缓存在服务器的一个事务队列中,服务器一旦收到 exec 指令,才开执行整个事务队列,执行完毕后一次性返回所有指令的运行结果。因为 Redis 的单线程特性,它不用担心自己在执行队列的时候被其它指令打断,可以保证他们能得到的“原子性”执行。另外QUEUED没什么特别的意味,就像OK一样,他只是申明命令已经放入缓存队列中了。

 redis原子性?


   在mysql中事务会有一个原子执行,如果在事务中一个命令执行时发生错误,其他命令也不会得到执行,但是在redis中是否存在这样的设置? 

 1 127.0.0.1:6379> multi          # 事务的开始
 2 OK
 3 127.0.0.1:6379> set 1 's'
 4 QUEUED
 5 127.0.0.1:6379> INCR 1
 6 QUEUED
 7 127.0.0.1:6379> set 2 'ss'
 8 QUEUED
 9 127.0.0.1:6379> exec
10 1) OK
11 2) (error) ERR value is not an integer or out of range        # 因为键为1的值为字符串,不能对其自增,因此报错。
12 3) OK
13 127.0.0.1:6379> mget 1 2      # 键值对2 ‘ss'还是设置成功。
14 1) "s"
15 2) "ss"

   从上面我们可以看出,即使在Redis事务中存在错误的命令,也不会妨碍后面的命令执行。

redis中管道


  在上面 Redis 事务例子中,在发送每个指令到事务缓存队列时都要经过一次网络读写,当一个事务内部的指令较多时,需要的网络 IO 时间也会线性增长。所以通常 Redis 的客户端在执行事务时都会结合 pipeline 一起使用,这样可以将多次 IO 操作压缩为单次 IO 操作。来增加性能。 

 1  1 import redis
 2  2 
 3  3 re = redis.Redis('localhost', 6379, db =0)
 4  4 
 5  5 p = re.pipeline(transaction=True)
 6  6 p.multi()
 7  7 p.set('test_1', 'test——1')
 8  8 p.set('test_2', 'test_2')
 9  9 p.execute()
10 10 
11 11 
12 12 # redis 客户端中查询结构
13 13 127.0.0.1:6379> get test_1
14 14 "testxe2x80x94xe2x80x941"
15 15 127.0.0.1:6379> get test_2
16 16 "test_2"
17 17 127.0.0.1:6379> 

 watch命令


   假如有多个客户端会并发进行操作。我们可以通过 Redis 的分布式锁来避免冲突,这是一个很好的解决方案。但是分布式锁是一种悲观锁,那是不是可以使用乐观锁的方式来解决冲突呢?

  有这样一种情况,假如多个用户进行并发操作时,需要将值取出然后操作再回写到对应键的值上,这可能会导致最终的值不是正确的值。这里一种解决办法是分布式锁(悲观所),另一种就是Watch(乐观锁)。

  watch命令会在事务开始之前看住 1 个或多个指定变量,当服务器收到了 exec 指令要顺序执行缓存的事务队列时,Redis 会检查关键变量自 watch 之后,是否被修改了 (包括当前事务所在的客户端)。如果关键变量被人动过了,exec 指令就会返回 null 回复告知客户端事务执行失败,这个时候客户端一般会选择重试。(watch必须再事务之前执行,不能再事务中执行)

 1  1 127.0.0.1:6379> set 'test' 1
 2  2 OK
 3  3 127.0.0.1:6379> watch test
 4  4 OK
 5  5 127.0.0.1:6379> INCR test   # watch之后改变test的值
 6  6 (integer) 2
 7  7 127.0.0.1:6379> multi         # 开始事务
 8  8 OK
 9  9 127.0.0.1:6379> INCR test     # 改变test的值
10 10 QUEUED
11 11 127.0.0.1:6379> exec            # 执行事务
12 12 (nil)                                       # 返回空值,事务执行失败。
13 13 
14 14 
15 15 127.0.0.1:6379> set 'test_2' 1    # 设置test-2
16 16 OK
17 17 127.0.0.1:6379> watch test_2        # 监听test_2
18 18 OK
19 19 127.0.0.1:6379> multi
20 20 OK
21 21 127.0.0.1:6379> INCR test_2         #修改test_2的值
22 22 QUEUED
23 23 127.0.0.1:6379> exec                   # 执行成功
24 24 1) (integer) 3 

  在py客户端中对于一个值的修改我们可以采用这样形式

 1 import redis
 2 
 3 def modify_test(client, key):
 4     while True:
 5         client.watch(key)
 6         value = int(client.get(key))
 7         value += 1  # 修改值
 8         pipe = client.pipeline(transaction=True)
 9         pipe.multi()
10         pipe.set(key, value)
11         try:
12             pipe.execute()
13             break  # 修改成功
14         except redis.WatchError:
15             continue  # 值已经被修改,执行失败
16     return int(client.get(key))  # 重新获取值
17 
18 
19 client = redis.StrictRedis()
20 key = "test"
21 client.setnx(key, 5)  # setnx 做初始化
22 print (modify_test(client, key))
原文地址:https://www.cnblogs.com/GoodRnne/p/10588541.html