Redis 事务

来源:互联网 发布:怀化天乙网络 编辑:程序博客网 时间:2024/05/16 23:39

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

eg:        MULTI        OK        INCR foo        QUEUED        INCR bar        QUEUED        EXEC        1) :1        2) :1

事务开始:

MULTI命令标志着事务开始,当客户端发送该命令时,客户端状态的flags属性打开REDIS_MULTI标识。

命令入队:


事务队列:

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

typedef struct redisClient {    ...    // 事务状态    multiState mstate;      /* MULTI/EXEC state */}
事务包含一个事务队列,以及已入队命令的计数器:

/* * 事务状态 */typedef struct multiState {    // 事务队列,FIFO 顺序    multiCmd *commands;     /* Array of MULTI commands */    // 已入队命令计数    int count;              /* Total number of MULTI commands */} multiState;
事务队列是一个multiCmd类型的数组,数组中的每一个multiCmd都保存了一个已入队命令的相关信息:
/* * 事务命令 */typedef struct multiCmd {    // 参数    robj **argv;    // 参数数量    int argc;    // 命令指针    struct redisCommand *cmd;} multiCmd;
/* * Redis 命令 */struct redisCommand {    // 命令名字    char *name;    // 实现函数    redisCommandProc *proc;    // 参数个数    int arity;    // 字符串表示的 FLAG    char *sflags; /* Flags as string representation, one char per flag. */    // 实际 FLAG    int flags;    /* The actual flags, obtained from the 'sflags' field. */    /* Use a function to determine keys arguments in a command line.     * Used for Redis Cluster redirect. */    // 从命令中判断命令的键参数。在 Redis 集群转向时使用。    redisGetKeysProc *getkeys_proc;    /* What keys should be loaded in background when calling this command? */    // 指定哪些参数是 key    int firstkey; /* The first argument that's a key (0 = no keys) */    int lastkey;  /* The last argument that's a key */    int keystep;  /* The step between first and last key */    // 统计信息    // microseconds 记录了命令执行耗费的总毫微秒数    // calls 是命令被执行的总次数    long long microseconds, calls;};
举例:


事务执行:

    创建空白回复队列,
    遍历事务队列中的每个项,
           读取命令参数,参数的个数,以及要执行的命令,
           执行命令,并将命令的返回值追加到回复队列末尾,
    移除REDIS_MULTI标识,让客户端返回非事务状态,
    清空客户端的事务状态,包括入队命令计数器和释放事务队列。

使用事务时可能会遇上以下两种错误:

    1.事务在执行 EXEC 之前,入队的命令可能会出错。比如说,命令可能会产生语法错误(参数数量错误,参数名错误,等等),或者其他更严重的错误,比如内存不足(如果服务器使用 maxmemory 设置了最大内存限制的话)。
    2.命令可能在 EXEC 调用之后失败。举个例子,事务中的命令可能处理了错误类型的键,比如将列表命令用在了字符串键上面,诸如此类。
    对于发生在 EXEC 执行之前的错误,客户端以前的做法是检查命令入队所得的返回值:如果命令入队时返回QUEUED,那么入队成功;否则,就是入队失败。如果有命令在入队时失败,那么大部分客户端都会停止并取消这个事务。

从 Redis 2.6.5 开始,服务器会对命令入队失败的情况进行记录,并在客户端调用 EXEC 命令时,拒绝执行并自动放弃这个事务。至于那些在 EXEC 命令执行之后所产生的错误, 并没有对它们进行特别处理: 即使事务中有某个/某些命令在执行时产生了错误, 事务中的其他命令仍然会继续执行。

WATCH命令:

监视数据库键:
每个redis数据库中都保存一个watched_keys字典,字典的键是被监视的键,字典的值是一个链表,链表中记录了所有监视相应数据库的客户端。

typedef struct redisDb {    ...    // 正在被 WATCH 命令监视的键    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */}
监视机制触发:

所有对数据库进行修改的命令,执行后都会调用touchWatchKey函数对watched_keys字典进行检查,查看客户端正在监视刚被命令修改过的数据库键,如果有,该函数将被修改键的客户端的flags标识REDIS_DIRTY_CAS标识打开,表示事务安全性已经被破坏。

事务安全判断:

Jedis - Redis事务的实现

乐观锁示例


0 0
原创粉丝点击