概述

相信学过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命令来取消监控。

声明

原创文章,转载请注明出处,本文链接:http://qifuguang.me/2015/09/30/Redis事务介绍/
如果你喜欢我的文章,请关注我的微信订阅号,更多干货,第一时间与你分享:

Redis事务介绍的更多相关文章

  1. redis学习(四)redis事务

    redis事务 1.redis事务介绍 redis的事务可以理解为一系列串行命令的集合.redis的事务和单条命令一样,都是redis的最小执行单位,因此一个事务内的命令,要么全部执行,要么全部不执行 ...

  2. 【Redis】Redis 事务

    Redis 事务介绍 Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证: 批量操作在发送 EXEC 命令前被放入队列缓存. 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败 ...

  3. Redis事务与可分布式锁

    1    Redis事务 1.1   Redis事务介绍 l  Redis的事务是通过MULTI,EXEC,DISCARD和WATCH这四个命令来完成的. l  Redis的单个命令都是原子性的,所以 ...

  4. 探索Redis设计与实现14:Redis事务浅析与ACID特性介绍

    本文转自互联网 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial ...

  5. redis事务及相关命令介绍

    redis事务及相关命令介绍 一.概述:和众多其它数据库一样,Redis作为NoSQL数据库也同样提供了事务机制.在Redis中,MULTI/EXEC/DISCARD/WATCH这四个命令是我们实现事 ...

  6. Lind.DDD.Repositories.Redis层介绍

    回到目录 之前已经发生了 大叔之前介绍过关于redis的文章,有缓存,队列,分布式pub/sub,数据集缓存以及仓储redis的实现等等,而今天在Lind.DDD的持久化组件里,redis当然也有一席 ...

  7. redis 事务

    概述 相信学过MySQL等其他数据库的同学对事务这个词都不陌生,事务表示的是一组动作,这组动作要么全部执行,要么全部不执行.为什么会有这样的需求呢?看看下面的场景: 微博是一个弱关系型社交网络,用户之 ...

  8. Redis事务和分布式锁

    Redis事务 Redis中的事务(transaction)是一组命令的集合.事务同命令一样都是Redis最小的执行单位,一个事务中的命令要么都执行,要么都不执行.Redis事务的实现需要用到 MUL ...

  9. Redis学习——Redis事务

    Redis和传统的关系型数据库一样,因为具有持久化的功能,所以也有事务的功能! 有关事务相关的概念和介绍,这里就不做介绍. 在学习Redis的事务之前,首先抛出一个面试的问题. 面试官:请问Redis ...

随机推荐

  1. 开源ITIL管理软件iTop 2.5-2.6安装

    环境说明 : 操作系统centos 7.itop版本 iTop-2.5.0-3935.数据库:mariadb iTop 2.5只支持PHP5.6以上版本,本例安装的是php72w版本 1.下载链接: ...

  2. docker 安装vim

    执行以下命令 apt-get update apt-get install vim

  3. 2、Ansible在使用过程中出现的错误解决方法

    1.安装完成后允许命令出错 Traceback (most recent call last): File "/usr/bin/ansible", line 197, in (ru ...

  4. java 转载

    这几天忙于其他的事情,编程的习惯没有继续下去,偶然间看到了这篇文章,感觉收益颇丰,言归正传,下面即入主题 java基础知识小总结 在一个独立的原始程序里,只能有一个 public 类,却可以有许多 n ...

  5. arcgis 10.3中文版安装教程、配置及常见问题(百度的有些错误)

    参考的: 1.http://wenku.baidu.com/link?url=W-wo_lEMvzHxF19w91X7H0WDjyCQ16DjGu4ViaZ4-eVPr0NTU-LrZTPK1oyzT ...

  6. 【Coursera】支持向量机

    一.最大间隔分类器 1. 函数间隔:\(γ^{i} = y^{i}(w^{T} x + b)\), 改变w和b的量级,对分类结果不会产生任何影响,但是会改变函数间隔的大小.因此,直接对函数间隔求最大值 ...

  7. 2018软工实践第八次作业-团队项目UML设计

    团队信息 队员姓名与学号 学号 姓名 博客链接 124 王彬(组长) 点击这里 206 赵畅 点击这里 215 胡展瑞 点击这里 320 李恒达 点击这里 131 佘岳昕 点击这里 431 王源 点击 ...

  8. 树莓派与Arduino Leonardo使用NRF24L01无线模块通信之基于RF24库 (三) 全双工通信

    设计思路 Arduino Leonardo初始化为发送模式,发送完成后,立即切换为接收模式,不停的监听,收到数据后立即切换为发送模式,若超过一定时间还为接收到数据,则切换为发送模式. 树莓派初始化为接 ...

  9. C++编译与链接(2)-浅谈内部链接与外部链接

    发现每次写技术博客时,都会在文章开头处花费一番功夫 ...从前,有一个程序员....他的名字叫magicsoar 为什么有时会出现aaa已在bbb中重定义的错误? 为什么有时会出现无法解析的外部符号? ...

  10. OSG学习:裁剪变换(2)

    接着上一篇博客说. 还有一种裁剪的方法:osg::Scissor类. 这个类封装了OpenGL中的glScissor()函数. 该类主要用于设置一个视口裁剪平面举行.设置裁剪平面举行的函数如下: vo ...