深入浅出Redis(二)高级特性:事务
2015-03-17 09:02
225 查看
第一篇中介绍了Redis是一个强大的键-值仓储,支持五种灵活的数据结构。事实上,Redis还支持其它的一些高级特性:事务、发布与订阅、管道、脚本等,本篇我们来看一下事务。
前一篇中我们提到,在Redis中每个命令都是原子性的,因为Redis内部的实现是单线程的。当然Redis也支持多个命令之间的事务,不过事务在Redis中相对来说很简单,不像数据库事务那样涉及传播级别、隔离级别等特性。
使用multi命令开始一个新的事务,exec命令提交,discard命令回滚。如果把信用卡的可用额度存入balance,欠额存入debt,在消费的时候就必须在一个事务内同时更新这两个键。
在multi命令后的其它命令,返回结果都是"QUEUED“,这些命令不会立即执行,只是简单的在Server端缓存起来了。在发出exec命令后,他们才会被一起执行;或者discard命令回滚事务。
在Redis官方文档有指出事务的两个特点:
事务中的命令都是按着他们进入缓存队列的顺序依次执行的,在事务执行中,Redis不会受理其它客户端的命令(隔离性)
事务中的命令,或者全部执行,或者全部不执行。(原子性)
上面的例子是信用卡扣减,但实际中在做扣减时我们需要检查余额是否足够,所以一般会这么做:
对于普通数据库事务,上面的代码没问题,但对于Redis事务来说行不通,因为在exec命令之前,所有的命令都被Redis缓存起来了,根本就拿不到balance的值。那类似这种需要基于已经存在的某个值的事务在Redis中如何实现呢?答案是Watch命令:
通俗点讲,watch命令就是标记一个键,如果标记了一个键,在提交事务前如果该键被别人修改过,那事务就会失败,这种情况通常可以在程序中重新再尝试一次。像上面的例子,首先标记了键balance,然后检查余额是否足够,不足就取消标记,并不做扣减;足够的话,就启动事务进行更新操作,如果在此期间键balance被其它人修改,那在提交事务(执行exec)时就会报错,程序中通常可以捕获这类错误再重新执行一次,直到成功。
Redis事务失败后不支持回滚
与数据库事务很重要的一个区别是Redis事务在执行过程中出错后不会回滚。在exec命令后,Redis Server开始一个个的执行被缓存的命令,如果其中某个命令执行出错了,那之前的命令并不会被回滚。
exec提交事务后,在执行到incr value2时错误了(数据类型不正确),但事务对value的操作却是生效的,这点可以从后面的get value的返回值看到。
前一篇中我们提到,在Redis中每个命令都是原子性的,因为Redis内部的实现是单线程的。当然Redis也支持多个命令之间的事务,不过事务在Redis中相对来说很简单,不像数据库事务那样涉及传播级别、隔离级别等特性。
使用multi命令开始一个新的事务,exec命令提交,discard命令回滚。如果把信用卡的可用额度存入balance,欠额存入debt,在消费的时候就必须在一个事务内同时更新这两个键。
127.0.0.1:6379> set balance 100 OK 127.0.0.1:6379> set debt 0 OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> decrby balance 25 QUEUED 127.0.0.1:6379> incrby debt 25 QUEUED 127.0.0.1:6379> exec 1) (integer) 75 2) (integer) 25 127.0.0.1:6379> get balance "75" 127.0.0.1:6379> get debt "25"
在multi命令后的其它命令,返回结果都是"QUEUED“,这些命令不会立即执行,只是简单的在Server端缓存起来了。在发出exec命令后,他们才会被一起执行;或者discard命令回滚事务。
在Redis官方文档有指出事务的两个特点:
事务中的命令都是按着他们进入缓存队列的顺序依次执行的,在事务执行中,Redis不会受理其它客户端的命令(隔离性)
事务中的命令,或者全部执行,或者全部不执行。(原子性)
上面的例子是信用卡扣减,但实际中在做扣减时我们需要检查余额是否足够,所以一般会这么做:
redis.multi() balance = redis.get('balance') if (balance < amtToSubtract) { redis.discard() } else { redis.decrby('balance', amtToSubtract) redis.incrby('debt', amtToSubtract) redis.exec() }
对于普通数据库事务,上面的代码没问题,但对于Redis事务来说行不通,因为在exec命令之前,所有的命令都被Redis缓存起来了,根本就拿不到balance的值。那类似这种需要基于已经存在的某个值的事务在Redis中如何实现呢?答案是Watch命令:
redis.watch('balance') balance = redis.get('balance') if (balance < amtToSubtract) { redis.unwatch() } else { redis.multi() redis.decrby('balance', amtToSubtract) redis.incrby('debt', amtToSubtract) redis.exec() }
通俗点讲,watch命令就是标记一个键,如果标记了一个键,在提交事务前如果该键被别人修改过,那事务就会失败,这种情况通常可以在程序中重新再尝试一次。像上面的例子,首先标记了键balance,然后检查余额是否足够,不足就取消标记,并不做扣减;足够的话,就启动事务进行更新操作,如果在此期间键balance被其它人修改,那在提交事务(执行exec)时就会报错,程序中通常可以捕获这类错误再重新执行一次,直到成功。
Redis事务失败后不支持回滚
与数据库事务很重要的一个区别是Redis事务在执行过程中出错后不会回滚。在exec命令后,Redis Server开始一个个的执行被缓存的命令,如果其中某个命令执行出错了,那之前的命令并不会被回滚。
127.0.0.1:6379> set value 1 OK 127.0.0.1:6379> set value2 abc OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> incr value QUEUED 127.0.0.1:6379> incr value2 QUEUED 127.0.0.1:6379> exec 1) (integer) 2 2) (error) ERR value is not an integer or out of range 127.0.0.1:6379> get value "2" 127.0.0.1:6379>
exec提交事务后,在执行到incr value2时错误了(数据类型不正确),但事务对value的操作却是生效的,这点可以从后面的get value的返回值看到。
相关文章推荐
- Redis基础、高级特性与性能调优-pilelining,事务,scripting
- redis高级特性之事务
- Redis实战(10)高级特性(2)事务与乐观锁
- Redis高级实用特性(安全性、主从复制、事务处理)
- 深入浅出Redis(三)高级特性:管道
- Redis数据库高级实用特性:事务控制
- Redis实战《红丸出品》4.3 Redis高级实用特性之事务控制
- redis高级实用特性
- Redis基础、高级特性与性能调优
- Redis高级特性及应用场景
- Redis --- 其它高级特性
- Redis实战《红丸出品》4.4 Redis高级实用特性之持久化机制
- 小贝_redis 高级应用-事务
- redis高级特性
- JDBC高级特性(三)分布式事务和JTA基本原理
- Redis实战《红丸出品》4.5 Redis高级实用特性之发布及订阅消息
- redis的高级应用之一(Redis安全性\主从复制\事务处理)
- 分布式缓存技术redis学习系列(三)——redis高级应用(主从、事务与锁、持久化)
- Redis高级特性及应用场景
- Redis高级特性:虚拟内存的使用技巧