【Redis】 事务
【Redis】 事务
Metadata
title: 【Redis】 事务
date: 2023-07-09 13:04
tags:
- 行动阶段/完成
- 主题场景/数据存储
- 笔记空间/KnowladgeSpace/ProgramSpace/BasicsSpace
- 细化主题/数据存储/Redis
categories:
- 数据存储
keywords:
- 数据存储/Redis
description: 【Redis】 事务
概述
Redis 通过MULTI
,EXEC
,WATCH
等命令实现事务功能。事务是将多个命令打包,然后原子地按顺序地执行的机制,执行期间服务器不会中断事务执行其他客户端的命令请求。下面展示了一次完整事务的执行命令:
redis> MULTI
OK
redis> SET "name" "The Design and Implementation of Redis"
QUEUED
redis> GET "name"
QUEUED
redis> EXEC
1) OK
2) "The Design and Implementation of Redis"
- 事务提供了一种将多个命令打包,然后一次性、有序地执行的机制。
- 多个命令会被入队到事务队列中,然后按先进先出(FIFO)的顺序执行。
- 事务在执行过程中不会被中断,当事务队列中的所有命令都被执行完毕之后,事务才会结束。
- 带有WATCH命令的事务会将客户端和被监视的键在数据库的watched_keys字典中进行关联,当键被修改时,程序会将所有监视被修改键的客户端的REDIS_DIRTY_CAS标志打开。
- 只有在客户端的REDIS_DIRTY_CAS标志未被打开时,服务器才会执行客户端提交的事务,否则的话,服务器将拒绝执行客户端提交的事务。
- Redis的事务总是具有ACID中的原子性、一致性和隔离性,当服务器运行在AOF持久化模式下,并且appendfsy nc选项的值为alway s时,事务也具有耐久性。
事务的实现
可以看出,事务主要有 3 个阶段:事务开始、命令入队、事务执行。
事务开始
MULTI 命令表示事务的开始,将客户端从非事务状态切换为事务状态,在 flags 属性中打开REDIS_MULTI
标识。
命令入队
当客户端处于事务状态时,命令不会被立即执行(除了 EXEC、DISCARD、WATCH、MULTI),而是加入事务队列。
事务队列
客户端的事务状态保存在 mstate 里:
typedef struct redisCLient{
//事务状态
multiState mstate;
...
} redisClient;
事务状态包括事务队列和入队命令计数器:
typedef struct multiState{
//数组,事务队列
multiCmd *commands;
//入队命令计数器
int count;
} multiState;
事务队列的实例结构:
typedef struct multiCmd {
//参数
robj **argv;
//参数数量
int argc;
//命令指针
struct redisCommand *cmd;
} multiCmd;
先入队的命令先放入数组,后入队的后放入。
执行事务
当收到客户端的 EXEC 命令时,将被立即执行,然后服务器遍历客户端的事务队列,保存命令,执行命令给,返回结果给客户端,移除事务标识。
WATCH 命令的实现
WATCH 命令是一个乐观锁,可以在 EXEC 前监视任意数量的键,如果在 EXEC 执行时,键被修改过,服务器将拒绝执行事务。
使用 WATCH 命令监视数据库键
每个 Redis 数据库都保存着 watched_keys 字典,键是某个被 WATCHED 命令监视的键,值是一个链表,记录所有监视该键的客户端。
typedef struct redisDb{
//正在被WATCHED命令监视的键
dict *watched_keys;
...
} redisDb;
监控机制的触发 & 事务安全
在执行数据库修改命令时,都会调用multi.c/touchWatchKey
函数对 watched_keys 字典进行检查,查看是否有客户端正在监视刚被命令修改过的键,如果有,将 watched_keys 该键对应的值,也就是监听的客户端都打开REDIS_DIRTY_CAS
标识,表示事务的安全性已经被破坏。此时,服务器拒绝执行事务。
事务的 ACID 性质
Redis 的事务有原子性、一致性和隔离性,当 Redis 运行在特定的持久化模式下时,才具有持久性。
原子性
Redis 事务队列中的命令,要么全部都执行,要么一个都不执行,因此,具有原子性。Redis 进行事务命令入队时,如果命令入队出错,会被拒绝执行。但是命令的语法错误(执行错误),不会导致整个命令不被执行,也就是说 Redis 不支持事务的回滚机制。
下面例子表示发生入队错误(一致性时将提到入队错误和执行错误)时,事务中的所有命令都不会被执行:
redis> MULTI
OK
redis> SET msg "he1lo"
QUEUED
redis> GET
(error) ERR wrong number of arguments for 'get' command
redis> GET msg
QUEUED
redis> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
发生执行错误,不影响其他命令的执行:
redis> SET msg "hello" # msg键是一个字符串
OK
redis> MULTI
OK
redis> SADD fruit "apple" "banana" "cherry"
QUEUED
redis> RPUSH msg "good bye" "bye bye" #错误地对字符串键msg执行列表键的命令
QUEUED
redis> SADD alphabet "a" "b" "c"
QUEUED
redis> EXEC
1) (integer) 3
2)(error) WRONGTYPE Operation against a key holding the wrong kind of value
3)(integer) 3
不支持事务回滚是考虑到了复杂性,与其简单高效的理念不符,并且 Redis 的设计者认为,Redis 事务的执行时错误通常都是编程错误产生的,在开发环境中会有,但生产环境不应该出现,因此,没有设计回滚机制。
一致性
一致性表示在事务的执行前后,成功与否,数据库都是一致的,也就是数据符合数据库本身定义和要求,没有非法或无效错误数据。
Redis 通过简单的错误检测来保证一致性。
- 入队错误
在 2.6.5 之后的版本,如果一个事务在入队时出现了命令不存在,Redis 则拒绝执行这个事务。
- 执行错误
对于命令执行期间发现的错误,不会影响其他命令的执行。服务器会识别出错的命令,并进行相应处理,这些命令不会对数据库做修改,不影响一致性。
- 服务器停机
如果 Redis 在执行过程中停机,数据也是一致的。如果没有开启持久化,重启后数据库是空白的。开启持久化后,重启后会还原到一致状态。
隔离性
Redis 是单线程的,并且服务器保证在执行事务期间不会对事务进行中断,因此是具有隔离性的。并且队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行,因此没有隔离级别概念。
持久性
持久性的意思是,事务执行的结果被永久性地保存,执行事务的结果不会丢失。因为 Redis 没有单独为事务队列提供持久化功能,所以取决于持久化模式,只有 AOF 方式持久化并且 appendsync 的值为 always,而且没有打开no-appendfsync-on-rewrite
时,才具有持久性。因为其他方式并不能保证事务的执行结果被第一时间保存到硬盘里。
注:no-appendfsync-on-rewrite
打开后,在执行BGSAVE
和BGREWRITEAOF
时会暂停对 AOF 文件的同步。