redis-事务

简述:

 redis 通过
 multi 事务开始
 exec 执行
 watch 乐观锁
 discard 取消事务
等命令来实现事务功能。事务提供了一种将多个命令请求打包,然后一次性,按顺序地执行多个命令的机制。

1 事务的实现

一个事务从开始到结束通常经历三个阶段
  1. 事务开始
  2. 命令入队
 3. 事务执行

1.1 事务开始

multi 命令的执行标志着事务的开始。
multi 可以将执行该命令的客户端从非事务状态切至事务状态。通过客户端状态的 flags 属性中打开 redis_multi 标识来完成的。

1.2 命令入队

当一个客户端切换到事务状态,redis客户端会根据这个客服端发来的不通命令执行不通操作:
立即执行:exec,discard,watch,multi
放到事务队列里面:非exec,discard,watch,multi

redis-事务_第1张图片
1-1服务器判断命令是该入队还是执行的过程

1.3 事务队列

 每个redis客服端都有自己的事务状态,这个事务状态保存在客服端状态的mstate 属性。

typedef struct redisClient {

    //...

    // 事务状态
   multiState mstate; /* MULTI/EXEC state */

  list *watched_keys; /* 正在被WATCH命令监视的键 */

} redisClient;

 事务状态包含一个事务队列,以及一个已入队命令计数器(也可以说事务队列的长度)

typedef struct multiState {

/* 事务队列,FIFO 顺序 */

multiCmd *commands; /* Array of MULTI commands */

    int count; /* 已入队命令计数 */

    int minreplicas; /* MINREPLICAS for synchronous replication */

    time_t minreplicas_timeout; /* MINREPLICAS timeout as unixtime. */

} multiState;

  事务队列是一个multiCmd 类型的数组, 数组中每个multiCmd结构都保存了一个已入队命令的相关信息,包括执行命令实现函数的 指针,命令的参数,以及参数的数量。

/*
* 事务命令
*/
typedef struct multiCmd {
    /* 参数 */
    robj **argv;
    /* 参数数量 */
    int argc;
    /* 命令指针 */
  struct redisCommand *cmd;
} multiCmd;

 事务对了以先进先出 的方式保存入队的命令,较先入队的命令会被放到数组前面。

举例:

redis-事务_第2张图片
1-2 执行结果

redis-事务_第3张图片
1-3 事务状态

1.4 执行事务

发送 exec,立即被服务器执行,服务器会遍历这个客服端的事务队列,执行队列中保存的所有命令,最后将执行命令所得结果全部返回给客服端。
例子返回:

redis-事务_第4张图片
1-4 例子执行结果

redis-事务_第5张图片

2 watch命令实现

  watch 命令是乐观锁,它可以在exec命令执行前,监视任意数量的数据库键,并在exec命令执行时,检查被监视键是否至少有一次已经被修改过,如果是 ,服务器拒绝事务,并向客服端返回代表事务执行失败的空回复。


redis-事务_第6张图片
1-5 两个客服端执行命令的过程

客服端A事务会执行失败。

2.1 使用watch 命令监视数据库键

typedef struct redisDb {

  dict *dict; // 数据库键空间,保存着数据库中的所有键值对

  dict *expires; // 键的过期时间,字典的键为键,字典的值为过期事件 UNIX 时间戳

  dict *watched_keys; // 正在被watch命令监视的键

  int id; // 数据库号码

} redisDb;

  通过 watched_keys 字典,服务器可以清楚知道哪些数据库键正在被监视,以及哪些客服端正在监视这些数据库键。

2.2 监视机制的触发

  所有对数据库进行修改的命令, 比如set,lpush,等等,在执行之后都会调用multi.c、touchWatchKey 函数对 watched_keys 字典进行检查,查看是否有客服端正在监视刚刚修改过的数据库键,如果有的话,那么 touchWatchKey 函数会将监视被修改键的客服端的 redist_dirty_cas 表示打开,表示该客服端的事务安全性已经被破坏。

redis-事务_第7张图片
/*

* “触碰”一个键,如果这个键正在被某个/某些客户端监视着,

* 那么这个/这些客户端在执行 EXEC 时事务将失败。

*/

void touchWatchedKey(redisDb *db, robj *key) {

  list *clients;

  listIter li;

  listNode *ln;

  /* 字典为空,没有任何键被监视 */

  if (dictSize(db->watched_keys) == 0) return;

  /* 获取所有监视这个键的客户端 */

  clients = dictFetchValue(db->watched_keys, key);

  if (!clients) return;

  /* Mark all the clients watching this key as REDIS_DIRTY_CAS */

  /* Check if we are already watching for this key */

  /* 遍历所有客户端,打开他们的 REDIS_DIRTY_CAS 标识 */

  listRewind(clients, &li);

  while ((ln = listNext(&li))) {

  redisClient *c = listNodeValue(ln);

  c->flags |= REDIS_DIRTY_CAS;

  }
  
}

2.3 判断事务是否安全

 当服务器接受到一个客服端发来的exec 命令,服务器会根据这个客服端是否打开了 redis_dirty_cas 标识来决定是否执行了事务。

redis-事务_第8张图片
服务器判断是否执行事务过程

来源 redis 设计与实现

你可能感兴趣的:(redis-事务)