zoukankan      html  css  js  c++  java
  • Redis事务介绍

    概述

    相信学过Mysql等其他数据库的同学对事务这个词都不陌生,事务表示的是一组动作,这组动作要么全部执行,要么全部不执行。为什么会有这样的需求呢?看看下面的场景:

    • 微博是一个弱关系型社交网络,用户之间有关注和被关注两种关系,比如两个用户A和B,如果 A关注B,则B的粉丝中就应该有A。关注这个动作需要两个步骤完成:在A的关注者中添加B;在B的粉丝中添加A。 这两个动作要么都执行成功,要么都不执行。否则就可能会出现A关注了B,但是B的粉丝中没有A的不可容忍的情况。
    • 转账汇款,假设现在有两个账户A和B,现在需要将A中的一万块大洋转到B的账户中,这个动作也需要两个步骤完成:从A的账户中划走一万块;在B的账户中增加一万块。这两个动作要么全部执行成功,要么全部不执行,否则自会有人问候你的!!!

    Redis作为一种高效的分布式数据库,同样支持事务。

    Redis事务

    Redis中的事务(transaction)是一组命令的集合。事务同命令一样都是Redis最小的执行单位,一个事务中的命令要么都执行,要么都不执行。Redis事务的实现需要用到 MULTI  EXEC 两个命令,事务开始的时候先向Redis服务器发送 MULTI 命令,然后依次发送需要在本次事务中处理的命令,最后再发送 EXEC 命令表示事务命令结束。

    举个例子,使用redis-cli连接redis,然后在命令行工具中输入如下命令:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    127.0.0.1:6379> MULTI
    OK
    127.0.0.1:6379> set url http://qifuguang.me
    QUEUED
    127.0.0.1:6379> set title winwill2012
    QUEUED
    127.0.0.1:6379> set desc java
    QUEUED
    127.0.0.1:6379> EXEC
    1) OK
    2) OK
    3) OK
    127.0.0.1:6379>
    127.0.0.1:6379> get url
    "http://qifuguang.me"
    127.0.0.1:6379> get title
    "winwill2012"
    127.0.0.1:6379> get desc
    "java"
    127.0.0.1:6379>
    

    从输出中可以看到,当输入MULTI命令后,服务器返回OK表示事务开始成功,然后依次输入需要在本次事务中执行的所有命令,每次输入一个命令服务器并不 会马上执行,而是返回”QUEUED”,这表示命令已经被服务器接受并且暂时保存起来,最后输入EXEC命令后,本次事务中的所有命令才会被依次执行,可 以看到最后服务器一次性返回了三个OK,这里返回的结果与发送的命令是按顺序一一对应的,这说明这次事务中的命令全都执行成功了。

    再举个例子,在命令行工具中输入如下命令:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    127.0.0.1:6379> MULTI
    OK
    127.0.0.1:6379> set a a
    QUEUED
    127.0.0.1:6379> sett b b
    (error) ERR unknown command 'sett'
    127.0.0.1:6379> set c c
    QUEUED
    127.0.0.1:6379> EXEC
    (error) EXECABORT Transaction discarded because of previous errors.
    127.0.0.1:6379> get a
    (nil)
    127.0.0.1:6379> get b
    (nil)
    127.0.0.1:6379> get c
    (nil)
    127.0.0.1:6379>
    

    和前面的例子一样,先输入MULTI最后输入EXEC表示中间的命令属于一个事务,不同的是中间输入的命令有一个错误(set写成了sett),这样因为 有一个错误的命令导致事务中的其他命令都不执行了(通过后续的get命令可以验证),可见事务中的所有命令式同呼吸共命运的。

    如果客户端在发送EXEC命令之前断线了,则服务器会清空事务队列,事务中的所有命令都不会被执行。而一旦客户端发送了EXEC命令之后,事务中的所有命令都会被执行,即使此后客户端断线也没关系,因为服务器已经保存了事务中的所有命令。

    除了保证事务中的所有命令要么全执行要么全不执行外,Redis的事务还能保证一个事务中的命令依次执行而不会被其他命令插入。试想一个客户端A需要执行 几条命令,同时客户端B发送了几条命令,如果不使用事务,则客户端B的命令有可能会插入到客户端A的几条命令中,如果想避免这种情况发生,也可以使用事 务。

    Redis事务错误处理

    如果一个事务中的某个命令执行出错,Redis会怎样处理呢?要回答这个问题,首先要搞清楚是什么原因导致命令执行出错:

    1. 语法错误 就像上面的例子一样,语法错误表示命令不存在或者参数错误
      这种情况需要区分Redis的版本,Redis 2.6.5之前的版本会忽略错误的命令,执行其他正确的命令,2.6.5之后的版本会忽略这个事务中的所有命令,都不执行,就比如上面的例子(使用的Redis版本是2.8的)

    2. 运行错误 运行错误表示命令在执行过程中出现错误,比如用GET命令获取一个散列表类型的键值。
      这种错误在命令执行之前Redis是无法发现的,所以在事务里这样的命令会被Redis接受并执行。如果食物里有一条命令执行错误,其他命令依旧会执行(包括出错之后的命令)。比如下例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      
      127.0.0.1:6379> MULTI
      OK
      127.0.0.1:6379> set key 1
      QUEUED
      127.0.0.1:6379> SADD key 2
      QUEUED
      127.0.0.1:6379> set key 3
      QUEUED
      127.0.0.1:6379> EXEC
      1) OK
      2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
      3) OK
      127.0.0.1:6379> get key
      "3"
      

      Redis中的事务并没有关系型数据库中的事务回滚(rollback)功能,因此使用者必须自己收拾剩下的烂摊子。不过由于Redis不支持事务回滚功能,这也使得Redis的事务简洁快速。

    回顾上面两种类型的错误,语法错误完全可以在开发的时候发现并作出处理,另外如果能很好地规划Redis数据的键的使用,也是不会出现命令和键不匹配的问题的。

    WATCH命令

    从上面的例子我们可以看到,事务中的命令要全部执行完之后才能获取每个命令的结果,但是如果一个事务中的命令B依赖于他上一个命令A的结果的话该怎么办 呢?就比如说实现类似Java中的i++的功能,先要获取当前值,才能在当前值的基础上做加一操作。这种场合仅仅使用上面介绍的MULTI和EXEC是不 能实现的,因为MULTI和EXEC中的命令是一起执行的,并不能将其中一条命令的执行结果作为另一条命令的执行参数,所以这个时候就需要引进Redis 事务家族中的另一成员:WATCH命令

    换个角度思考上面说到的实现i++的方法,可以这样实现:

    1. 监控i的值,保证i的值不被修改
    2. 获取i的原值
    3. 如果过程中i的值没有被修改,则将当前的i值+1,否则不执行

    这样就能够避免竞态条件,保证i++能够正确执行。

    WATCH命令可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令(事务中的命令是在EXEC之后才执行的,EXEC命令执行完之后被监控的键会自动被UNWATCH)

    举个例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    127.0.0.1:6379> set mykey 1
    OK
    127.0.0.1:6379> WATCH mykey
    OK
    127.0.0.1:6379> set mykey 2
    OK
    127.0.0.1:6379> MULTI
    OK
    127.0.0.1:6379> set mykey 3
    QUEUED
    127.0.0.1:6379> EXEC
    (nil)
    127.0.0.1:6379> get mykey
    "2"
    127.0.0.1:6379>
    

    上面的例子中,首先设置mykey的键值为1,然后使用WATCH命令监控mykey,随后更改mykey的值为2,然后进入事务,事务中设置mykey 的值为3,然后执行EXEC运行事务中的命令,最后使用get命令查看mykey的值,发现mykey的值还是2,也就是说事务中的命令根本没有执行(因 为WATCH监控mykey的过程中,mykey被修改了,所以随后的事务便会被取消)。

    有了WATCH命令,我们就可以自己实现i++功能了,伪代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    def incr($key):
        WATCH $key
        $value = GET $key
        if not $value
            $value = 0
        $value = $value + 1
        
        MULTI
        SET $key $value
            result = EXEC
        return result[0]
    

    因为EXEC返回的是多行字符串,使用result[0]表示返回值的第一个字符串。

    注意:由于WATCH命令的作用只是当被监控的键被修改后取消之后的事务,并不能保证其他客户端不修改监控的值,所以当EXEC命令执行失败之后需要手动重新执行整个事务。

    执行EXEC命令之后会取消监控使用WATCH命令监控的键,如果不想执行事务中的命令,也可以使用UNWATCH命令来取消监控。

    这世界不缺善良,缺的是加了理智的善良。
  • 相关阅读:
    JS站点
    1011 World Cup Betting (20分)
    1007 Maximum Subsequence Sum (25分)(动态规划DP)
    1006 Sign In and Sign Out (25分)
    1005 Spell It Right (20分)
    1004 Counting Leaves (30分)(DFS)
    1003 Emergency (25分)(Dijkstra算法)
    1002 A+B for Polynomials (25分)
    1001 A+B Format (20分)
    canvas
  • 原文地址:https://www.cnblogs.com/yixiao21/p/8343044.html
Copyright © 2011-2022 走看看