Redis事务---那鸡肋一样的存在

弃之如可惜,食之无所得

一.Redis事务简述

Redis的事务是下面4个命令来实现
1.multi,开启Redis的事务,置客户端为事务态。
2.exec,提交事务,执行从multi到此命令前的命令队列,置客户端为非事务态。
3.discard,取消事务,置客户端为非事务态。
4.watch,监视键值对,作用时如果事务提交exec时发现监视的监视对发生变化,事务将被取消。

将是否有watch命令分为普通类型事务和CAS(Check And Set)类型事务,无watch命令的为普通类型事务,有watch命令的为CAS类型事务,下面为普通类型事务演示

[img]http://dl2.iteye.com/upload/attachment/0111/1223/7b570d1e-8fc4-3f12-9b91-d85e2ea10e78.png[/img]

事务失败的原因可以分为静态错误(如不存在的命令)和运行时错误(如cas错误、对string用lpop操作)。静态错误会在提交exec时返回错误信息,使事务不能执行,而除cas以外的运行时错误不会阻止事务继续执行。

为方便后面的理解,可以勉强将Redis状态分为四种,普通状态、正常事务状态、CAS错误事务状态和静态错误事务状态,这四种状态在Redis源码中用3个状态位(REDIS_MULTI、REDIS_DIRTY_CAS、REDIS_DIRTY_EXEC)来标识,下面是个状态转换图。


[img]http://dl2.iteye.com/upload/attachment/0111/1225/a49162ac-bb87-34a0-8872-465393a38130.png[/img]

当Redis处于事务态时,除multi、exec、discard和watch以外的所有命令皆会放到事务队列,向客户端返回QUEUED。


二.Redis事务实现

1.普通类型事务

Redis的事务实现比较简单,真的可以理解为"一个打包的批量执行脚本"。
当用户输入multi命令时,用户将本客户端标识位REDIS_MULTI置为1,所有的命令将会检查这个标识位,如果为1则将命令放到本客户端的命令队列中,如果非1则将命令直接执行。

每个客户端redisClient中皆有一个结构体存储着本客户端命令队列的状态,结构体如下:
typedef struct multiState {
/*命令组成的数组*/
multiCmd *commands;
/*命令的个数*/
int count;
} multiState;

每个命令有下面的结构体标识
typedef struct multiCmd {
/*命令的参数*/
robj **argv;
/*命令的参数个数*/
int argc;
/*命令的指针*/
struct redisCommand *cmd;
} multiCmd;

我们上面演示事务的例子在Redis中的示例

[img]http://dl2.iteye.com/upload/attachment/0111/1227/86dd241e-5cc9-33da-b326-a4b837e48e3a.png[/img]

当客户端输入exec,将commands数组执行一边,整个事务就完成了,是不是简单的快哭了。
如果用户在正常事务态输入错误的命令,Redis会将对应客户端置为静态错误事务状态,在代码中表现为将用户端redisClient的REDIS_DIRTY_EXEC标识位置为1;如果用户watch的key变化,将变为CAS错误事务状态,在代码中表现为将用户端redisClient的REDIS_DIRTY_CAS标识位置为1。当用户exec提交事务时,Redis首先检测用户这两个标志位,任何一个被置为1,则不执行队列中的命令,直接给用户返回错误。
exec提交事务以后,会调用discard进行清理,包括重置标志位,释放所有事务状态相关的资源(释放命令队列、参数等)。

2.CAS类型事务
CAS类型事务整个的事务执行逻辑和普通类型事务一样,只不过在普通类型事务增加watch功能而已。
watch功能相当于乐观锁,用户在进入事务态之前,监控任意数量的数据库键,当提交事务时,检测这些键是否被更改。如果被更改则拒绝用户的事务,并返回用户空数据。

在代码中实现即为事务提交前会检测用户端redisClient的REDIS_DIRTY_CAS标识位。
每个RedisDB都保存一个watched_keys的字典,key为数据库键,value为用户端redisClient,当“触碰”一个key时,此key对应的value(即redisClient)的REDIS_DIRTY_CAS将置为1,value中存储的这个/这些客户端在执行 exec 时事务将失败,下图为watched_keys在Redis中的示例。

[img]http://dl2.iteye.com/upload/attachment/0111/1231/706bcb6c-2e7c-3d97-bb5a-c54384f462f4.png[/img]

当其他客户端更新此数据库的year时将导致客户端clientA和clientC的事务提交失败,代码实现即为遍历year的value链表,将链表中的redisClient的REDIS_DIRTY_CAS将置为1。


三.总结
Redis的事务还比较原始,加之Redis的持久化容忍数据丢失,阻挡利用事务来达到精确数据操作的业务。
Redis的不支持回滚更是硬伤,这也导致很多人认为Redis的事务并不满足事务原子性。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值