目录
01,Redis的持久化之(RDB)
- RDB(Redis DataBase)
1.1,简介
Redis的持久化指的是:
- 在指定的时间间隔内将
内存
中的数据集快照写入磁盘
,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里 - Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到 一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。
- 整个过程中,
主进程是不进行任何IO操作的
,这就确保了极高的性能。 - 如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。
- RDB的缺点是最后一次持久化后的数据可能丢失(原因:
分片保存
的时候最后一次没有来得及保存就退出了才会出现这种情况)
Fork:
Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、
环境变量、程序计数器等) 数值都和原进程一致,但是是一个全新的进程,
并作为原进程的子进程
rdb 保存的是dump.rdb文件
相关配置在配置文件的位置 ,redis.conf中的SNAPSHOTTING
1.2,如何触发RDB
RDB的触发有两种方式:一种是手动(使用命令save),一种是自动(save m n)
- 自动:
默认
的RDB快照触发机制(可以在redis.conf中的SNAPSHOTTING中查看)
1分钟内改动了1万次
5分钟内改动了10万次
15分钟内改动了1次
以上都可以触发RDB快照操作,将内存中的内容持久化到硬盘中
默认快照的位置为,启动redis服务器的当前目录下,如果把redis的服
务器在Linux中配置了环境变量,这时候还是要注意的,如果在任意目录
下启动redis服务器,将会导致RDB快照很乱
默认的名字为:dump.rdb
为了防止出现误操作或者硬件问题,我们一般会把持久化后的快照在重新
备份一份到其他的硬盘
命令:cp dump.rdb dump_new.rdb
- 手动: 命令save或者是bgsave
如果出现某个数据非常重要,无法等到自动备份,这时候我们可以通过这
个命令来进行手动备份
save:save时只管保存,其它不管,全部阻塞
bgsave:Redis会在后台异步进行快照操作, 快照同时还可以响应客户
端请求,可以通过lastsave 命令获取最后一次成功执行快照的时间
- 注意:执行flushall命令的时候会产生dump.rdb文件,但是会先清空,后生成,里面是空的,无意义,
前面介绍的多次备份也是为了防止这个问题
1.3,如何恢复RDB
- 将备份文件 (dump.rdb) 移动到启动Redis服务的目录即可
- 通过下面的命令可以获得Redis服务启动的目录
$ CONFIG GET dir
1.4,优缺点
优势
- 适合大规模的数据恢复
- 对数据完整性和一致性要求不高
劣势
- 在一定间隔时间做一次备份,所以如果Redis服务器意外down掉的话,就会丢失最后一次快照后的所有修改
- Fork的时候,内存中的数据被克隆了一份,会占用更多的内存空间
1.5,如何停止RDB
动态所有停止RDB保存规则的方法,使用下面的命令:
CONFIG SET save ""
1.6,小结
- RDB是一个非常紧凑的文件。
- RDB在保存RDB文件时父进程唯一需要做的就是fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他I0操作,所以RDB持久化方式可以最大化redis的性能。
- 与AOF相比,在恢复大的数据集的时候,RDB方式会更快一一些。
数据丢失风险大。 - RDB需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候fork的过程是非常耗时间的,可能会导致Redis在一些毫秒级不能回应客户端请求
02,Redis持久化之AOF
- AOF(Append Only File)
2.1,简介
- 以日志的形式来
记录每个写操作
,将Redis执行过的所有写指令记录下来(读操作不记录) - 只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据
- 换言之,redis 重启的话就根据日志文件的内容将
写指令从前到后执行一次以完成数据的恢复工作
相关配置在配置文件的位置中的,APPEND ONLY MODE
AOF默认保存的是appendonly.aof
文件(在配置文件可修改文件名)
2.2,如何触发AOF
- AOF的启动
在配置文件的APPEND ONLY MODE中,AOF的开启默认是关闭的:
appendonly on
开启的时候修改为yes即可:
appendonly yes
默认保存的文件形式为:
appendonly.aof
可以修改默认文件名字:
CONFIG SET appendfilename "文件名"
- 有关AOF的配置
执行的保存策略
Appendfsync:
always:同步持久化,每次发生数据变更都会被立即记录到磁盘,
性能较差,但是数据的完整性很好
Everysec:出厂默认推荐,异步操作,每条记录,每秒记录,如果
一秒内出现数据库宕机没可能会丢失部分数据
no:从不同步
- 注意:这个FLASHALL命令,
此命令为一个写命令
,在AOF文件中也会被记录进去,最后恢复的时候还是会清空数据库
2.3,如何恢复AOF
AOF的恢复和RDB的恢复有点区别:
- RDB的恢复,直接把RDB文件里面的内容读进来就可以了
- AOF文件里面存放的都是指令,如果那条指令存入时有问题了,或者被人为修改了,恢复的时候就会出问题
如果AOF和RDB文件共存,恢复的时候会先去读取AOF文件
正常恢复:
- 将备份文件 (appendonly.aof) 移动到启动Redis服务的目录即可
异常恢复:
- 第一步:先备份被写坏的AOF文件
- 第二步:修复
在redis的客户端执行命令:redis-check-aof --fix appendonly.aof
此命令对文件进行修复,将AOF文件中不符合规范的所有命令删除,然后再重
启redis服务器进行加载
2.4、Rewrite
Rewrite:
- AOF采用文件追加方式,文件会越来越大。为避免出现此种情况,新增了重写机制
- 当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩, 只保留可以恢复数据的最小指令集。
- 使用命令开启:
bgrewriteaof
Rewrite原理:
- AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename), 遍历新进程的内存中数据,每条记录有一条的Set语句。
- 重写aof文件的操作,并没有读取旧的aof文件, 而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似
触发机制:
- Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍或者文件大于64M时触发
1.5,优缺点
优势
- 每修改同步:appendfsync always 同步持久化 每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好
- 每秒同步:appendfsync everysec 异步操作,每秒记录 如果一秒内宕机,有数据丢失
- 不同步:appendfsync no 从不同步
劣势
- 相同数据集的数据而言AOF文件要远大于RDB文件,恢复速度慢于RDB
- AOF运行效率要慢于RDB,每秒同步策略效率较好,不同步效率和RDB相同
1.6,小结
AOF文件和RDB文件可以共存,当两者共存的时候,服务器重启也只会读取AOF文件,尽管这样还是建议两者都是用,以防万一
。- AOF文件时一个只进行追加的日志文件
- Redis可以在AOF文件体积变得过大时,自动地在后台对AOF进行重写
- AOF文件有序地保存了对数据库执行的所有写入操作,这些写入操作以Redis协议的格式保存,因此AOF文件的内容非常容易被人读懂,对文件进行分析也很轻松
- 对于相同的数据集来说,AOF文件的体积通常要大于RDB文件的体积
- 根据所使用的
appendfsync
策略,AOF的速度可能会慢于RDB
03,Redis的事务(Transactions)
3.1,简介
事务可以做什么:
- 当一次执行多个命令,本质是一组命令的集合,一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞,类似于synchronsized锁一样。
- 一个队列中,一次性、顺序性、排他性的执行一系列命令
注意:Redis的事务不是原子性
在redis中,对于一个存在问题的命令,如果在
入队的时候就已经出错
,整个事务内的命令将都不会被执行(其后续的命令依然可以入队),如果这个错误命令在入队的时候并没有报错
,而是在执行的时候出错了,那么redis默认跳过这个命令执行后续命令。也就是说,redis只实现了部分事务
。
3.2,常用命令
命令 | 描述 |
---|---|
MULTI | 标记一个事务块的开始。 |
EXEC | 执行所有事务块内的命令 |
DISCARD | 取消事务,放弃执行事务块内的所有命令。 |
UNWATCH | 取消 WATCH 命令对所有 key 的监视。 |
WATCH key [key …] | 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断 |
3.2,操作演示
3.2.1, MULTI,EXEC,DISCARD
MULTI,EXEC,DISCARD 用法:
- 使用MULTI命令输入Redis事务,该命令始终以OK答复
- 此时,用户可以发出多个命令,Redis不会执行这些命令,而是将它们排队,使用QUEUED答复。
- 调用EXEC,将执行所有命令
- 调用DISCARD,将刷新事务队列并退出事务
演示:
- 正常执行:
122.1.0.1:6379> MULTI
OK
122.1.0.1:6379> SET k2 1
QUEUED
122.1.0.1:6379> INCR k2
QUEUED
122.1.0.1:6379> EXEC
1) OK
2) (integer) 2
- 放弃事务:
122.1.0.1:6379> MULTI
OK
122.1.0.1:6379> SET k3 1
QUEUED
122.1.0.1:6379> INCR k3
QUEUED
122.1.0.1:6379> DISCARD
OK
- 事务中出现错误(
入队的时候就出错了
),全体失败:
122.1.0.1:6379> MULTI
OK
122.1.0.1:6379> SET k4 1
QUEUED
122.1.0.1:6379> INCR k4 1
(error) ERR wrong number of arguments for 'incr' command
122.1.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
- 事务中出现错误(
入队的时候并没有报错
),错误语句执行失败,事务继续执行:
122.1.0.1:6379> MULTI
OK
122.1.0.1:6379> SET k5 1
QUEUED
122.1.0.1:6379> INCR k5
QUEUED
122.1.0.1:6379> SET k6 123456@qq.com
QUEUED
122.1.0.1:6379> INCR k6
QUEUED
122.1.0.1:6379> EXEC
1) OK
2) (integer) 2
3) OK
4) (error) ERR value is not an integer or out of range
3.2.2,监控(WATCH ,UNWATCH)
悲观锁:
- 悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁
乐观锁:
- 乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,
可以使用版本号等机制
。乐观锁适用于多读的应用类型,这样可以提高吞吐量 乐观锁策略:提交版本必须大于记录当前版本才能执行更新
CAS(Check And Set):
- 比较然后设置,自旋锁
WATCH ,UNWATCH用法:
- 用于为Redis事务提供检查
- 监视设定被监视的键,以便检测对它们的更改
- 如果在EXEC命令之前修改了监视键,则整个事务将中止,并且EXEC返回NULL答复以通知该事务失败
被WATCH监视的键,就像被加了一把乐观锁
演示(我们使用银行转账的例子来进行说明):
- 初始化转账账户:account_send和收款账户:account_accept
122.1.0.1:6379> SET account_send 1000
OK
122.1.0.1:6379> SET account_accept 1000
OK
- 正常执行情况(先监控,在开事务)
开启监听account_send
122.1.0.1:6379> WATCH account_send
OK
开启事务
122.1.0.1:6379> MULTI
OK
122.1.0.1:6379> DECRBY account_send 100
QUEUED
122.1.0.1:6379> INCRBY account_accept 100
QUEUED
122.1.0.1:6379> EXEC
1) (integer) 900
2) (integer) 1100
- 出现被人篡改的情况
线程A:
开启监听account_send
122.1.0.1:6379> WATCH account_send
OK
开启事务
122.1.0.1:6379> MULTI
OK
122.1.0.1:6379> INCRBY account_accept 100
QUEUED
122.1.0.1:6379> DECRBY account_send 100
QUEUED
122.1.0.1:6379> EXEC
(nil)
线程B:
线程B在线程A开启监听和事务之后,执行EXEC之前,执行下面的操作篡改
account_send的值,导致线程A执行失败
123.1.0.1:6379> DECRBY account_send 100
(integer) 800
123.1.0.1:6379> INCRBY account_accept 100
(integer) 1200
原因分析:
因为在redis中开启监听之后,就相当于给这个key加了一个锁(类似乐观锁),
线程A去修改account_send的时候会拿到一个版本号,假设为1,
但是线程A在事务还没提交的时候,线程B也去修改account_send的值,
此时也会拿到一个版本号,假设为1(因为线程A为提交),线程B执行结束,
修改版本号为2,这是线程A开始执行事务中的代码,发现版本号为2,
不是之前拿到的1,导致失败
- 注意:一旦执行了EXEC,之前加的监控会被取消掉(一次性)
3.2.3,Redis事务的三阶段三特性
三阶段:
- 开启:以MULTI开始一个事务
- 入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面
- 执行:由EXEC命令触发事务
三特性
- 单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。
事务在执行的过程中
,不会被其他客户端发送来的命令请求所打断
。 - 没有隔离级别的概念:队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行, 也就不存在
事务内的查询要看到事务里的更新,在事务外查询不能看到
这个让人万分头痛的问题 - 不保证原子性:redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚。
总结:不遵循传统的ACID中的AI(原子性,隔离性)
3.2.4,小结
- Watch指令,类似乐观锁,事务提交时,如果Key的值已被别的客户端改变, 比如某个list已被别的客户端push/pop过了,整个事务队列都不会被执行
- 通过WATCH命令在事务执行之前监控了多个Keys,倘若在WATCH之后有任何Key的值发生了变化, EXEC命令执行的事务都将被放弃,同时返回Null应答以通知调用者事务执行失败