事务
Redis 通过 WATCH、MULTI、EXEC、DISCARD 命令实现事务功能。使用命令队列(FIFO)保存客户端发送的命令,并一次性、按顺序地执行队列里的命令,知道执行完毕。
例如:
// 开始事务
redis> multi
OK
// 命令入队
redis> set name "傅园慧"
QUEUED
// 命令入队
redis> set age 20
QUEUED
// 命令入队
redis> set task "里约奥运会"
QUEUED
// 执行事务
redis> exec
1) OK
2) OK
3) OK
- 立即执行命令:WATCH、MULTI、EXEC、DISCARD
- 入队的命令:除了以上四种命令
MULTI
表示事务的开始,通过设置客户端 flags 属性来完成
伪代码:
client.flags |= REDIS_MULTI
EXEC
执行事务,执行事务队列中的所有命令,并且将所有的结构返回给客户端。
WATCH
监视键的变化,在事务的过程中,如果被监视的一个或者多个键被修改,那么服务拒绝执行事务。
事务队列
每个客户端都有事务状态,保存在客户端的 mstate 属性中。
/**
* 命令对象,包括参数,参数数量,需要执行的命令
*/
typedef struct multiCmd {
// 参数
robj **argv;
// 参数数量
int argc;
// 命令指针
struct redisCommand *cmd;
} multiCmd;
/**
* 事务队列,用于保存客户端发送来的命令,以待执行
*/
typedef struct multiState {
// 事务队列,FIFO
multiCmd *commands;
// 队列元素数量
int count;
} multiState;
/**
* Redis 客户端
*/
typedef struct redisClient {
// ...
// 事务状态
multiState mstate;
// ...
} redisClient;
事务原子性
对于 Redis 的原子性,众说纷纭,有的说是原子性,有的说不是原子性。个人觉得两种观点都有道理。
认为是原子性
事务中的命令要么全部执行,要么一个都不执行。
全部执行:命令正确,入队正常,执行事务成功
全不执行:命令错误,无法入队,执行事务失败
认为不是原子性
Redis 不支持事务会滚(rollback),即使在执行过程中出现错误,无法回滚之前执行的命令,而且还会继续执行, 直到所有命令都执行完毕。
Redis 作者的解释:
会滚这种复杂的功能不符合 Redis 追求简单高效的设计理念,并且作者认为,执行时错通常都是编程错误,只会发生在开发环境,在生产环境很少会出现,所以认为没必要实现回滚功能。
redis> set name "acid"
OK
redis> multi
OK
redis> rpush 1 2 3
QUEUED
// 错误命令(给字符串键执行列表键命令),但能正常入队,却执行时报错
redis> rpush name "a" "b" "c"
QUEUED
redis> set k1 "v1"
QUEUED
redis> exec
1) (integer) 2
2) WRONGTYPE Operation against a key holding the wrong kind of value
3) OK