zoukankan      html  css  js  c++  java
  • 【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))
  • 相关阅读:
    TCP的三次握手和四次挥手理解及面试题
    linux网卡配置文件参数
    linux的常用指令和配置文件
    django中的modelform和modelfoemset
    django中的form表单验证
    php快速开发的学习经历
    一个微信支付商场的经历
    https的学习过程
    使用java访问elasticsearch创建索引
    写博客是为什么
  • 原文地址:https://www.cnblogs.com/GoodRnne/p/10588541.html
Copyright © 2011-2022 走看看