Redis学习笔记二:持久化和事务

这里是Redis学习笔记的第二篇,主要讲Redis的持久化和事务。

Redis持久化

首先什么是持久化?利用永久性存储介质将数据进行保存,在特定的事件将保存的数据进行恢复的工作机制称为持久化。

为什么要持久化?防止数据的意外丢失,确保数据安全性

持久化的两种方式:

  • 将当前数据状态进行保存,快照形式,存储数据结果,存储而是简单,关注点在数据 -----》 RDB
  • 将数据的操作过程进行保存,日志形式,存储操作过程,存储格式复杂,关注点在数据的操作过程 ------》AOF

RDB(Relational Database)

我们有三种RDB的启动方式:

1,save指令

save

第一种方式就是直接save指令,作用是现在就做一次RDB。

因为我们通常都是使用redis的conf文件来启动redis的,所以我们可以修改conf文件从而修改Redis的相关配置。

dbfilename dump.rdb
设置本地数据库文件名,默认值为dump.rdb。  通常设置成:dump-端口号.rdb
dir
设置存储.rdb文件的路径。  通常设置成存储空间较大的目录中,目录名称data
rdbcompression yes
设置存储至本地数据库时是否压缩数据,默认为yes,采用LZF压缩。  通常默认为开启状态,如果设置成no,可以节省CPU运行时间,但会使存储的文件变大(巨大)
rdbchecksumy yes
设置是否进行RDB文件格式的校验,该校验过程在写文件和读文件过程均进行。  通常默认为开启状态,如果设置为no,可以节约读写性过程约10%时间消耗,但是存储一定的数据损坏风险

2,bgsave指令

我们知道redis是单线程的,那么做save指令时其他指令就是阻塞的,会使得redis效率大大降低,所以我们通常不推荐使用save指令。那么不阻塞版本的save应运而生了:bgsave。

bgsive

bgsive作用和save指令一样都是做一次RDB,但是不同在于RDB是在后台执行的,bgsave不会造成阻塞。它的原理是fork一个子进程去做这个RDB的事情,redis的主进程还是干自己的事,不会阻塞。

上面说得save指令的四个配置项在bgsave也是一样这样用的,不过多了一项:  

stop-writes-on-bgsave-error yes  

后台存储过程中如果出现错误线程,是否停止保存操作。  通常默认为开启状态

到这里我们上面说的两种redis持久化方式都是手动操作,手动操作显然会有:不知道上一次什么时候操作,多久手动一次才合适等等问题,于是我们有第三种自动RDB的方式。

3,save配置方式

save second changes

这是自动RDB,在conf文件中进行配置。它的作用是满足限定时间范围内ke的变化数量达到指定数量即进行持久化,这两个参数:

second:监控时间范围      changes:监控key的变化量
表达的意思是如果每second时间内,key的变化量达到了changes次,那么redis就进行RDB。

需要注意的是就像我们上边提到的:不推荐使用save,同样体现在redis的态度,所以save配置启动后执行的是bgsave操作

另外save配置方式的其他配置也有上面save指令方式的配置。

最后是RDB的优缺点

RDB优点

 RDB是一个紧凑压缩的二进制文件,存储效率较高
 RDB内部存储的是redis在某个时间点的数据快照,非常适合用于数据备份,全量复制等场景
 RDB恢复数据的速度要比AOF快很多
 应用:服务器中每X小时执行bgsave备份,并将RDB文件拷贝到远程机器中,用于灾难恢复。
RDB缺点点
 RDB方式无论是执行指令还是利用配置,无法做到实时持久化,具有较大的可能性丢失数据
 bgsave指令每次运行要执行fork操作创建子进程,要牺牲掉一些性能
 Redis的众多版本中未进行RDB文件格式的版本统一,有可能出现各版本服务之间数据格式无法兼容现象

AOF(Append Only File)

我们考虑RDB的缺点:大数据量下的IO性能较低,基于fork创建子进程,内存产生额外消耗,宕机带来的数据丢失风险。针对这些问题,Redis给出了另一种持久化方式:AOF。

AOF持久化:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中命令达到恢复数据的目的。与RDB相比可以简单描述为改记录数据产生的过程。

AOF的工作原理是,把写命令先刷新到AOF写命令缓存中,然后再把命令同步到.aof文件中。

AOF写数据三种策略
always(每次)
  每次写入操作均同步到AOF文件中,数据零误差,性能较低,一般不建议使用
everysec(每秒)
  每秒将缓冲区中的指令同步到AOF文件中,数据准确性高,性能较高,默认配置
  在系统突然宕机的情况下只会丢失1秒内的数据
no(系统控制)
  由操作系统每次同步到AOF文件的周期,整体过程不可控

开启AOF及其配置

appendonly yes|no  是否开启AOF持久化功能,默认为不开启状态

appendfsync always|everysec|no    AOF三种写数据策略,我们上边提到的

appendfilename filename      AOF持久化文件名,默认文件名未appendonly.aof,建议配置为appendonly-端口号.aof

dir      AOF持久化文件保存路径,与RDB持久化文件保持一致即可

AOF重写

随着命令的不断写入AOF,文件会越来越大,为了解决这个问题,Redis引入AOF重写机制压缩文件体积,AOF文件重写是将Redis进程内的数据转换为写命令同步到新AOF文件的过程,简单说就是将同样一个数据的若干个命令执行结果转换为最终结果数据对应的指令进行记录

非常重要的三条AOF重写规则:
① 进程内已超时的数据不再写入文件
② 忽略无效指令,重写时使用进程内数据直接生成,这样新的AOF文件只保留最终数据的写入命令 
  如del key1,hdel key2,srem key3,set key 222等
③ 对统一数据的多条命令合并为一条命令
  如 lpush list1 a ,lpush list1 b,lpush list1 c可以转化为lpush list1 a b c
  为防止数据量过大造成客户端缓冲区溢出,对list,set,hash,set等类型,每条指令最多写入64个元素

AOF重写方式

   手动重写

bgrewriteaof

   自动重写

auto-aof-rewrite-min-size size
auto-aof-rewrite-percentage percentage

手动重写比较简单,但是注意这里的bg,说明这也是后台执行的,原理跟bgsave差不多都是fork子进程进行处理。

我们需要关注自动重写(直接用黑马程序员视频里的图):

 

 可以看到我们设置的两个参数需要和redis的参数作比较,只要这两个自动重写触发条件任意一个满足,就需要执行重写。

RDB和AOP的选择

RDB和AOF的选择之感
对数据非常敏感,建议使用默认的AOF持久化方案
  AOF持久化策略使用erverysecond,每秒钟fsync一次。该策略redis任然可以保持很好的处理性能,当出现问题时,最多丢失0-1秒中的数据。
  注意:由于AOF文件存储体积较大,且恢复数据较慢
数据呈现阶段有效性,建议使用RDB持久化方案
  数据可以良好的做到阶段内无丢失(该阶段是开发者或运维人工手工维护的),且恢复速度较快,阶段点数据恢复通常采用RDB方案
  注意:利用RDB实现紧凑的数据持久化会使Redis降得很低

Redis事务

什么是事务?来学Redis之前估计都学过Mysql,那么对事务定义以及特点应该是比较了解的。简单来说就是一系列操作要么全部成功,要么全部失败。

事务的基本操作

主要是三个指令:multi 和 exec ,discard

multi

multi这个指令是开启事务,即在这不能失败的一系列操作之前写到Redis。

exec

exec这个指令是设定事务的结束位置,同时执行事务。与multi成对出现,成对使用。

multi和exec成对使用,这之中的操作如果不能都完成,那么这次事务就会失败。那么问题来了,如果开启multi之后的指令打错了怎么办?能不能取消,当然可以

discard

这个指令就是用来取消本次事务的,即刚刚multi之后写的指令全部作废。

下面的这张图(来自黑马程序员)很好地说明了Redis事务的执行原理

事务的注意事项

如果事务操作我们的指令都写对了,那自然是没有问题,事务也正常工作其乐融融。但是有两个意外情况我们需要考虑:

定义事务的过程中,命令格式输入错误怎么办?

  语法错误即命令书写格式有误。如果定义的事务中所包含的命令存在语法错误,整体事务中所有命令均不会执行。包括那些语法正确的命令。(即全部指令一起凉凉)

定义事务的过程中,命令执行出现错误怎么办?

  运行错误。指命令格式正确,但是无法正确的执行。例如对list进行incr操作

  那么对于这样,Redis能够正确运行的命令会执行,运行错误的命令不会被执行。(即正确的照样运行,不能正确执行的不执行)

  这也提醒我们一点:已经执行完毕的命令对应的数据不会自动回滚,需要程序员自己在代码中实现回滚。

Redis的锁

上面的multi和exec是能保证事务的原子性,但是并没有保证事务的线程安全(实际不是线程安全,但是意思是这样),即有多个客户端对数据同时就行操作,那么也会造成我们在mysql中学的 脏读幻读 等情况。那么我们怎么去保证不发生这种情况呢?

Redis给我们提供了两种锁:锁 和 分布式锁 。  (博主个人理解其实就是乐观锁和悲观锁?

锁,个人感觉就是乐观锁。在事务之前(multi后)先先watch一下,要用到的key,然后我们写事务的指令,最后在exec执行之前我们看一下之前watch的key是否发生了变化,发生了变化我们就取消本次事务。

具体的有关锁的两条指令

watch key1 [key2……]

作用是对 key 添加监视锁,在执行exec前如果key发生了变化,终止事务执行

unwatch

作用是取消对所有 key 的监视

分布式锁

上面的锁有一个问题,我们考虑这种场景,秒杀:一个商品有数量限制,有大量客户同时购买这个商品,怎么保证这个商品不会被超卖?

如果我们使用上面提到的锁,那么即当一个客户买到了这个商品,其他的同时在买这个商品的全部作废!!

针对这个问题,Redis给我们提供了另一种锁:分布式锁。

setnx lock-key value

利用setnx命令的返回值特征,有值则返回设置失败,无值则返回设置成功
   对于返回设置成功的,拥有控制权,进行下一步的具体业务操作
   对于返回设置失败的,不具有控制权,排队或等待   (个人感觉就是悲观锁
操作完毕通过del操作释放锁

有时间限制的分布式锁

expire lock-key second
pexpire lock-key milliseconds

作用是使用 expire 为锁key添加时间限定,到时不释放,放弃锁。这样防止一个客户拿到锁之后却不干活不释放

当然这个设置的时间得细细考究,得取一次事务时间平均或者最大值。通常是微秒或者纳秒级别,设置太多效率会低。

参考资料:

Bilibili黑马程序员的Redis视频:https://www.bilibili.com/video/BV1CJ411m7Gc

原文地址:https://www.cnblogs.com/clno1/p/12991007.html