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

弃之如可惜,食之无所得

一.Redis事务简述

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

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

Redis事务---那鸡肋一样的存在_第1张图片

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

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


Redis事务---那鸡肋一样的存在_第2张图片

当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中的示例

Redis事务---那鸡肋一样的存在_第3张图片

当客户端输入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中的示例。

Redis事务---那鸡肋一样的存在_第4张图片

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


三.总结
Redis的事务还比较原始,加之Redis的持久化容忍数据丢失,阻挡利用事务来达到精确数据操作的业务。
Redis的不支持回滚更是硬伤,这也导致很多人认为Redis的事务并不满足事务原子性。























你可能感兴趣的:(Redis事务---那鸡肋一样的存在)