您的位置:首页 > 数据库 > Redis

Redis 事务

2015-08-01 17:14 603 查看
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事务的实现

乐观锁示例
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: