事务
Redis提供了将多个命令请求打包,然后一次性,按顺序的执行多个命令的机制,这种机制叫事务,并且在事务执行的过程中,服务器不会中断事务去执行其他客户端的请求,而是将事务的所有命令都执行完毕在去执行其他客户端请求
事务命令
- multi:开启事务
- exec:执行事务
- discard:取消事务
- watch:监视
事务的实现
一个事务会经历以下三个阶段:
事务的开始
Redis中multi命令标志着事务的开始
myRedis:0>multi
"OK"
multi命令将执行的客户端从非事务状态切换到事务的状态,通过客户端的flags属性中打开REDIS_MULTI标识来完成切换
命令入队
客户端在非事务状态下,客户端发送的命令会被立即执行,而在事务状态下,客户端会判断不同的命令执行不同的操作
- 如果命令是multi,exec,discard,watch服务器会立即执行
- 如果命令不是multi,exec,discard,watch服务器不会立即执行,是把发送的命令放到一个事务队列里,然后返回queued回复
事务队列
每个Redis客户端都有自己的事务状态,状态保存在mstate属性中,事务状态包含事务的队列和已入命令的计数器(事务队列的长度)
typedef struct multiState {
//存储命令的FIFO队列
multiCmd *commands;
//队列长度
int count;
} multiState;
事务队列是一个multiCmd类型的数组,数组中每个元素都保存了一个入队命令相关的信息,结构如下
typedef struct multiCmd {
//参数数组
robj **argv;
//参数数量
int argc;
//命令的指针
struct redisCommand *cmd;
} multiCmd;
事务队列multiCmd ,以先进先出的方式(FIFO)保存入队命令
图解分析
myRedis:0>multi
"OK"
myRedis:0>set name guohu
"QUEUED"
myRedis:0>set age 28
"QUEUED"
myRedis:0>get name
"QUEUED"
myRedis:0>get age
"QUEUED"
执行事务
客户端处于事务状态时,向服务器发送exec命令,此时exec命令会被立即执行,并且服务器会遍历客户端的事务队列,执行队列中所有的命令,并将所有队列命令执行的结果返回给客户端(注意:命令是按顺序执行的,先进先出规则)
myRedis:0>exec
1) "OK"
2) "OK"
3) "guohu"
4) "28"
WATCH命令
watch命令是一个乐观锁,它可以在exec命令执行之前,监视任意数量的数据库键,并在exec命令执行时,检查是否至少有一个被修改过,如果监视到有修改,则拒绝执行事务,并想客户端发送事务执行失败的回复
执行时间 |
客户端A |
客户端B |
time1 |
watch “name” |
|
time2 |
multi |
|
time3 |
set age 28 |
|
time4 |
|
set 29 |
time5 |
exec |
|
不监视的情况下
客户端A
myRedis:0>multi
"OK"
myRedis:0>set age 28
"QUEUED"
myRedis:0>get age
"QUEUED"
新起一个客户端B
myRedis:0>set age 29
"OK"
myRedis:0>get age
"29"
回到客户端A,执行事务提交
myRedis:0>exec
1) "OK"
2) "28"
按道理事务里面提交的set时29,但是当执行exec的时候,会发现数据被覆盖了
监视的情况下
客户端A执行命令
myRedis:0>flushdb
"OK"
myRedis:0>watch age
"OK"
myRedis:0>multi
"OK"
myRedis:0>set age 28
"QUEUED"
myRedis:0>set name guohu
"QUEUED"
myRedis:0>set address yinshanhu
"QUEUED"
myRedis:0>get name
"QUEUED"
myRedis:0>get address
"QUEUED"
另起一个客户端B执行
myRedis:0>set age 18
"OK"
myRedis:0>get age
"18"
再次回到客户端A执行事务提交
myRedis:0>exec
(nil)
myRedis:0>get age
"18"
myRedis:0>get name
null
myRedis:0>get address
null
监视到age发生变化,整个事务拒绝执行name和address都没有保存
事务的ACID特性
A-原子性
事务中的原子性是指多个操作被打包成一个整体执行,要么全部执行,要么都不执行,就Redis性质来说,符合原子性的描述,事务里面的命令会被一起执行
但是
Redis不支持事务回滚,如果其中有命令出错了,其他命令还会继续执行直到队列命令执行完毕
myRedis:0>multi
"OK"
myRedis:0>set name guohu
"QUEUED"
myRedis:0>incr name
"QUEUED"
myRedis:0>get name
"QUEUED"
myRedis:0>exec
1) "OK"
2) "ERR value is not an integer or out of range"
3) "guohu"
C-一致性
一致性指是指数据库在执行事务之前数据是一致的,在执行事务之后无论事务是否成功数据也是一致的,这符合数据库本身的定义和要求
I-隔离性
隔离性指的是,即使有多个事务并发的执行,各个事务之间页不会相互影响,并且在并发状态下执行的事务和串行执行的事务产生的结果完全相同,而在Redis中事务的所有命令都会按顺序执行,在执行Redis事务的过程中,另一个客户端发出的请求不会被处理,这保证了命令是作为单独的独立操作执行的
D-持久性
数据库持久性是指当一个事务执行完毕之后,所得到的结果已经被保存到永久性的存储介质中(例如:硬盘),事务执行完毕之后即使数据库宕机,也不会影响事务执行的结果,不过Redis中的持久性依赖其持久化机制和是否被开启持久化,如果没开启持久化就是纯内存运行,重启后数据会丢失,而Redis的持久化有两种模式RDB和AOF,下面会介绍具体的持久化机制
持久化
Redis是数据是存在内存里面的,同时Redis也提供了两种持久化机制RDB和AOF,将内存的数据刷到硬盘上,避免数据的意外丢失
RDB持久化(Redis DataBase)
RDB记录的是数据
RDB持久化既可以手动执行,也可以根据服务器的配置来定期执行,RDB文件时一个压缩的二进制文件(dump.rdb),通过RDB文件可以还原生成RDB时间节点的数据库状态,服务在重启的时候会自动加载RDB文件进行数据恢复
,RDB文件时保存再硬盘上的,所以即使redis服务关闭了,仍然可以还原数据库恢复数据
手动执行命令
- save
- 会阻塞redis进程,直到RDB文件生成完毕,再生成期间服务不能处理任何请求
- bgsave
- 会派生出一个子进程,然后由子进程创建RDB文件,父进程继续处理请求
- shutdown
- flushall
- 注意flushdb不会触发RDB的生成
- 不过flushall本身就是清除数据库的命令,生成的文件也是空的,没什么意义
在Redis服务启动时会自动检测是否存在RDB文件,如果存在则自动载入
自动间隔性保存
刚介绍了手动执行的两个命令,save和bgsave,由于bgsave可以不阻塞进程,Redis允许用户设置每隔一段时间自动执行bgsave的命令:
- save 900 1 : 900秒内对数据库进行1次修改
- save 300 10:300秒内对数据库进行10次修改
- save 60 10000:60秒内对数据库进行了10000次修改
满足以上三个条件其中一个,bgsave就会被执行,并且针对不同的类型,RDB会使用不同的方式存储(这里不做扩展了,不然这页博客就太细太长了)
RDB的优缺点分析
优点:
- RDB时一个压缩文件,保存了不同时间点的状态,非常适合做灾备
- RDB的bgsave可以派生子进程保存文件,不影响父级进程的处理,提高了Redis性能
- 灾备恢复速度快
缺点:
- 无法做到试试备份,只能间歇性保存RDB文件,如果非正常关机可能会丢失上次备份到宕机的数据
- 创建子进程,通常父进程来执行fork操作,频繁的fork操作,严重者会导致父进程短时间不可用
AOF持久化(Append Only File)
与RDB不同的是,AOF持久化机制记录的是写命令
开启AOF之后,AOF是采用日志方式将写命令记录到文件中
需要注意的是,AOF默认是关闭的,如果同时使用AOF和RDB,Redis会优先使用AOF作为持久化模式
开启AOF配置
appendonly no //默认是no表示关闭,修改为yes则表示开启
appendfilename "appendonly.aof" //文件名
还可以通过appendfsync参数来控制是否实时写入磁盘
- always:写入缓存的同时进行刷盘操作
- everysec:先写入缓存,然后每秒进行一次刷盘操作(极限状态可能会丢失1s的数据)
- no:只写入缓存,至于刷盘?随缘吧!看操作系统心情
AOF文件重写
AOF文件里包含了所有Redis的写命令,所以只要服务器读到所有命令并重新执行一遍即可还原数据库状态
虽然可以用命令还原数据库状态,但是此时暴漏了一个缺点,假设写命令很多,那文件岂不是越来越大,又或者针对一个key执行了N次操作,实际上有效操作只有最后一次,为了解决这个问题,Redis提供了AOF文件重写功能
重写条件
- 自动触发,(配置控制,可修改)
- auto-aof-rewrite-percentag :文件大小超过上次AOF重写之后的文件的百分比,默认100%(超过,是2倍)
- auto-aof-rewrite-min-size:重写的最小AOF文件大小,默认64M
- 手动触发:bgrewriteaof 命令
重写机制
- 再执行重写文件的过程中,Redis会分析现有的键值对,然后聚合成一条命令记录
- 如果是集合,列表,hash的话在超过元素数量(64个)也会用多条命令记录
备注
:说是重写,但实际上是根据现有的键值对分析出写命令,与原文件没有操作关系
重写缓冲区
写文件一般都是子进程 ,此时父继承是不阻塞的,还可以继续接受处理请求,当AOF重写过程中,父进程又有新的请求,这时就需要保证数据的一致性了,在执行AOF重写的操作时,有个AOF 重写缓冲区的概念,如果在重写期间有新的命令进入,会同时写入AOF缓冲区和AOF重写缓冲区
假设子进程完成了文件重写,会向父进程发送一个信号量,父进程紧接着会进行阻塞,并完成重写的以下剩余工作,之后就可以正常处理请求了
- 将 AOF重写缓冲区的内容刷新到AOF文件上
- 将写好的AOF文件替换掉旧的AOF文件
AOF优缺点分析
优点
- 可以通过修改配置自定义刷盘策略
- 有重写机制,可以防止AOF文件过大的问题
- 追加的方式记录操作日志,在非正常关机的情况下也可以实现数据库灾备
缺点
- RDB是压缩二进制,AOF是写操作日志,相对而言AOF文件更大
- RDB是基于数据的,AOF是基于命令的如果遇到写操作的错误情况,AOF有可能出现不能完全还原数据库状态,RDB是快照的方式,还原方式更加严谨