Redis事务介绍
2016-08-29 17:38
260 查看
概述
相信学过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> |
再举个例子,在命令行工具中输入如下命令:
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> |
如果客户端在发送EXEC命令之前断线了,则服务器会清空事务队列,事务中的所有命令都不会被执行。而一旦客户端发送了EXEC命令之后,事务中的所有命令都会被执行,即使此后客户端断线也没关系,因为服务器已经保存了事务中的所有命令。
除了保证事务中的所有命令要么全执行要么全不执行外,Redis的事务还能保证一个事务中的命令依次执行而不会被其他命令插入。试想一个客户端A需要执行几条命令,同时客户端B发送了几条命令,如果不使用事务,则客户端B的命令有可能会插入到客户端A的几条命令中,如果想避免这种情况发生,也可以使用事务。
Redis事务错误处理
如果一个事务中的某个命令执行出错,Redis会怎样处理呢?要回答这个问题,首先要搞清楚是什么原因导致命令执行出错:语法错误 就像上面的例子一样,语法错误表示命令不存在或者参数错误
这种情况需要区分Redis的版本,Redis 2.6.5之前的版本会忽略错误的命令,执行其他正确的命令,2.6.5之后的版本会忽略这个事务中的所有命令,都不执行,就比如上面的例子(使用的Redis版本是2.8的)
运行错误 运行错误表示命令在执行过程中出现错误,比如用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数据的键的使用,也是不会出现命令和键不匹配的问题的。
WATCH命令
从上面的例子我们可以看到,事务中的命令要全部执行完之后才能获取每个命令的结果,但是如果一个事务中的命令B依赖于他上一个命令A的结果的话该怎么办呢?就比如说实现类似Java中的i++的功能,先要获取当前值,才能在当前值的基础上做加一操作。这种场合仅仅使用上面介绍的MULTI和EXEC是不能实现的,因为MULTI和EXEC中的命令是一起执行的,并不能将其中一条命令的执行结果作为另一条命令的执行参数,所以这个时候就需要引进Redis事务家族中的另一成员:WATCH命令换个角度思考上面说到的实现i++的方法,可以这样实现:
监控i的值,保证i的值不被修改
获取i的原值
如果过程中i的值没有被修改,则将当前的i值+1,否则不执行
这样就能够避免竞态条件,保证i++能够正确执行。
WATCH命令可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令(事务中的命令是在EXEC之后才执行的,EXEC命令执行完之后被监控的键会自动被UNWATCH)
举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 1415 | 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> |
有了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] |
注意:由于WATCH命令的作用只是当被监控的键被修改后取消之后的事务,并不能保证其他客户端不修改监控的值,所以当EXEC命令执行失败之后需要手动重新执行整个事务。
执行EXEC命令之后会取消监控使用WATCH命令监控的键,如果不想执行事务中的命令,也可以使用UNWATCH命令来取消监控。
相关文章推荐
- 分布式缓存技术redis学习系列(五)——spring-data-redis与JedisPool的区别、使用ShardedJedisPool与spring集成的实现及一致性哈希分析
- Redis常见应用场景介绍
- 简单Redis搭建
- Redis的文档
- Redis 与 Lua 脚本
- 利用redis缓存热门数据,分页的一种思路
- 利用redis + lua解决抢红包高并发的问题
- redis分布式锁
- redis无法调用修改操作
- redis(带联接池)结合关系数据库
- 【先定一个小目标】Windows下Redis的安装使用
- Redis的持久化方式
- Redis快速入门详解
- 分布式缓存技术redis学习系列(四)——redis高级应用(集群搭建、集群分区原理、集群操作)
- redis学习笔记,随便记记
- redis的PHP命令大全
- PHP-redis中文文档
- 《 11. 我的linux虚拟机 之 安装 redis 》
- 城市查询-汉字转拼音、拼音、全拼、简拼、混拼、卷舌音、前后鼻音、兼容查询C#与nodejs+redis应用---笔记
- Redis初识(二)