黑马程序员技术交流社区

标题: 【合肥校区】Redis事务介绍 [打印本页]

作者: 项老师    时间: 2018-1-17 15:16
标题: 【合肥校区】Redis事务介绍
本帖最后由 合肥就业部 于 2018-2-1 11:01 编辑

【合肥校区】Redis事务介绍
概述
相信学过Mysql等其他数据库的同学对事务这个词都不陌生,事务表示的是一组动作,这组动作要么全部执行,要么全部不执行。为什么会有这样的需求呢?看看下面的场景:
Redis作为一种高效的分布式数据库,同样支持事务。
Redis事务
Redis中的事务(transaction)是一组命令的集合。事务同命令一样都是Redis最小的执行单位,一个事务中的命令要么都执行,要么都不执行。Redis事务的实现需要用到 MULTI EXEC 两个命令,事务开始的时候先向Redis服务器发送 MULTI 命令,然后依次发送需要在本次事务中处理的命令,最后再发送 EXEC 命令表示事务命令结束。
举个例子,使用redis-cli连接redis,然后在命令行工具中输入如下命令:
[AppleScript] 纯文本查看 复制代码
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,这里返回的结果与发送的命令是按顺序一一对应的,这说明这次事务中的命令全都执行成功了。
再举个例子,在命令行工具中输入如下命令:
[AppleScript] 纯文本查看 复制代码
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会怎样处理呢?要回答这个问题,首先要搞清楚是什么原因导致命令执行出错:
回顾上面两种类型的错误,语法错误完全可以在开发的时候发现并作出处理,另外如果能很好地规划Redis数据的键的使用,也是不会出现命令和键不匹配的问题的。
WATCH命令
从上面的例子我们可以看到,事务中的命令要全部执行完之后才能获取每个命令的结果,但是如果一个事务中的命令B依赖于他上一个命令A的结果的话该怎么办呢?就比如说实现类似Java中的i++的功能,先要获取当前值,才能在当前值的基础上做加一操作。这种场合仅仅使用上面介绍的MULTI和EXEC是不能实现的,因为MULTI和EXEC中的命令是一起执行的,并不能将其中一条命令的执行结果作为另一条命令的执行参数,所以这个时候就需要引进Redis事务家族中的另一成员:WATCH命令
换个角度思考上面说到的实现i++的方法,可以这样实现:
这样就能够避免竞态条件,保证i++能够正确执行。
WATCH命令可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令(事务中的命令是在EXEC之后才执行的,EXEC命令执行完之后被监控的键会自动被UNWATCH)
举个例子:
[AppleScript] 纯文本查看 复制代码
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++功能了,伪代码如下:
[AppleScript] 纯文本查看 复制代码
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命令来取消监控。

作者: 美美就是美    时间: 2018-1-17 15:47
写的非常好,虽然我没看懂
作者: 骑着小猪看雪    时间: 2018-1-17 16:37

作者: O-limin    时间: 2018-1-17 22:37
666

作者: 皖哥哥    时间: 2018-1-18 10:10
Redis是个非常好的非关系型数据库,希望大家都能够学有所得
作者: 黑马啸西风    时间: 2018-1-18 10:27
写的非常好,虽然我也没看懂

作者: hguilin    时间: 2018-1-18 10:29
非常感谢文涛老师的总结
作者: hguilin    时间: 2018-1-18 10:30
非常感谢文涛老师的总结

作者: 骑着小猪看雪    时间: 2018-1-18 10:30

作者: 程序员小虾米    时间: 2018-1-18 10:47
给自己一个赞
作者: 奥斯托洛夫斯基    时间: 2018-1-18 13:01

作者: SimpleAndnavie    时间: 2018-1-18 13:23
赞赞赞
作者: 小皖妹妹    时间: 2018-1-18 18:55
[666],点击[ http://pinyin.cn/e122737 ]查看表情
作者: 小皖妹妹    时间: 2018-1-18 18:55





欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2