Redis实战(10)高级特性(2)事务与乐观锁

一、事务

redis 对事务的支持目前还比较简单。 redis 只能保证一个 client 发起的事务中的命令可以连
续的执行,而中间不会插入其他 client 的命令。 

由于 redis 是单线程来处理所有 client 的请求的所以做到这点是很容易的。一般情况下 redis 在接受到一个 client 发来的命令后会立即处理并 返回处理结果,但是当一个 client 在一个连接中发出 multi 命令,这个连接会进入一个事务上下文,该连接后续的命令并不是立即执行,而是先放到一个队列中。当从此连接
受到 exec 命令后,redis 会顺序的执行队列中的所有命令。并将所有命令的运行结果打包到一起返回给 client.然后此连接就 结束事务上下文。

下面看一个例子:

Redis实战(10)高级特性(2)事务与乐观锁

从这个例子我们可以看到 2 个 set age 命令发出后并没执行而是被放到了队列中。调用 exec
后 2 个命令才被连续的执行,最后返回的是两条命令执行后的结果。
如何取消一个事务?
我们可以调用 discard 命令来取消一个事务,让事务回滚。接着上面例子
Redis实战(10)高级特性(2)事务与乐观锁

可以发现这次 2 个 set age 命令都没被执行。discard 命令其实就是清空事务的命令队列并退
出事务上下文,也就是我们常说的事务回滚。

二、乐观锁

乐观锁:大多数是基于数据版本(version)的记录机制实现的。何谓数据版本?即为数据增
加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表添加一个
“version”字段来实现读取出数据时,将此版本号一同读出,之后更新时,对此版本号加 1。
此时,将提交数据的版本号与数据库表对应记录的当前版本号进行比对,
如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

操作员 A 
操作员 B
(1)、操作员 A 此时将用户信息读出(此时 version=1) 并准备从其帐户余额中扣除$50,($100-$50) 

(2)、在操作员 A 操作的过程中,操作员 B 也读入此用户信息(此时 version=1),并准备从其帐户余额中扣除$20($100-$20)
(3)、操作员 A 完成了修改工作,将数据版本号加 1(此时 version=2),连同帐户扣除后余额(balance=$50),提交至数据库更新,此时由于提交数据版本大于数据库记录当前版本,数据被更新,数据库记录 version更新为 2



(4) 、 操 作 员 B 完 成 了 操 作 , 也 将 版 本 号 加 1
( version=2 ) 并 试 图 向 数 据 库 提 交 数 据
(balance=$80),但此时比对数据库记录版本时发
现,操作员 B 提交的数据版本号为 2,数据库记录
当前版本也为 2,不满足“提交版本必须大于记录
当前版本才能执行更新”的乐观锁策略,因此,操
作员 B 的提交被驳回

这样,就避免了操作员 B 用基于 version=1 的旧数据修改的结果来覆盖操作员 A 的操作结果
的可能。
即然乐观锁比悲观锁要好很多,redis 是否也支持呢?答案是支持, redis 从 2.1.0 开始就支持
乐观锁了,可以显式的使用 watch 对某个 key 进行加锁,避免悲观锁带来的一系列问题。
Redis 乐观锁实例:假设有一个 age 的 key,我们开 2 个 session 来对 age 进行赋值操作,我
们来看一下结果如何。

session1
session2

第一步:

127.0.0.1:6379> get age 
"20"
127.0.0.1:6379> watch age
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> 



第二步:

127.0.0.1:6379> set age 30
OK
127.0.0.1:6379> get age 
"30"
127.0.0.1:6379> 

第三步:

127.0.0.1:6379> set age 10
QUEUED
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get age
"30"
127.0.0.1:6379> 


从以上实例可以看到在
第一步,Session 1 还没有来得及对 age 的值进行修改
第二步,Session 2 已经将 age 的值设为 30
第三步,Session 1 希望将 age 的值设为 20,但结果一执行返回是 nil,说明执行失败,之后
我们再取一下 age 的值是 30,这是由于 Session 1 中对 age 加了乐观锁导致的。
watch 命令会监视给定的 key,当 exec 时候如果监视的 key 从调用 watch 后发生过变化,则整
个事务会失败。也可以调用 watch 多次监视多个 key.这 样就可以对指定的 key 加乐观锁了。
注意 watch 的 key 是对整个连接有效的,事务也一样。如果连接断开,监视和事务都会被自
动清除。当然了 exec,discard,unwatch 命令都会清除连接中的所有监视。


redis 的事务实现是如此简单,当然会存在一些问题。第一个问题是 redis 只能保证事务的每
个命令连续执行,但是如果事务中的一个命令失败了,并不回滚其他命令,比如使用的命令
类型不匹配。下面将以一个实例的例子来说明这个问题:

Redis实战(10)高级特性(2)事务与乐观锁

从这个例子中可以看到,age 由于是个数字,那么它可以有自增运算,但是 name 是个字符
串,无法对其进行自增运算,所以会报错,如果按传统关系型数据库的思路来讲,整个事务
都会回滚,但是我们看到 redis 却是将可以执行的命令提交了,所以这个现象对于习惯于关
系型数据库操作的朋友来说是很别扭的,这一点也是 redis 今天需要改进的地方。






















本文转自shayang8851CTO博客,原文链接:http://blog.51cto.com/janephp/1339572,如需转载请自行联系原作者

上一篇:NoSql-Redis入门(事务)


下一篇:《Effective C#中文版:改善C#程序的50种方法》“.NET技术”读书笔记