1. 持久化
1.1 概述
持久化简介
-
什么是持久化
利用永久性存储介质将数据进行保存,在特定的事件将保存的数据进行恢复的工作机制称为持久化 -
为什么要持久化
防止数据的意外丢失,确保数据安全性
持久化的过程保存什么
- 将当前数据状态进行保存,快照形式,存储数据结果,存储格式简单,关注点在数据 -----》 RDB
- 将数据的操作过程进行保存,日志形式,存储操作过程,存储格式复杂,关注点在数据的操作过程 ------》AOF
1.2 RDB
1.2.1 save命令
谁,什么事件,干什么事情?
- 谁:redis操作者(用户)
- 什么时间:即时(随时进行)
- 干什么事情:保存数据
save命令
#手动执行一次保存操作
save
save指令相关配置
- dbfilename dump.rdb
说明:设置本地数据库文件名,默认值为dump.rdb
经验:通常设置为dump-端口号.rdb - dir
说明:设置存储.rdb文件的路径
经验:通常设置成存储空间较大的目录中,目录名称data - rdbcompression yes
说明:设置存储至本地数据库时是否压缩数据,默认为yes,采用LZF压缩
经验:通常默认为开启状态,如果设置成no,可以节省CPU运行时间,但会使存储的文件变大(巨大) - rdbchecksumy yes
说明:设置是否进行RDB文件格式的校验,该校验过程在写文件和读文件过程均进行
经验:通常默认为开启状态,如果设置为no,可以节约读写性过程约10%时间消耗,但是存储一定的数据损坏风险
注意:Redis是单线程的,所有命令都会在类似队列中排好队,不建议使用save指令,因为save指令的执行会阻塞当前Redis服务器,直到当前RDB过程完成位置,有可能会造成长时间阻塞,线上环境不建议使用
1.2.2 bgsave命令
由于redis时单线程,数据量过大时,save命令会降低执行效率。后台执行可以缓解这种情况。
- 谁:redis操作者(用户)发起指令;redis服务器控制指令执行
- 什么时间:即时(发起);合理时间(执行)
- 干什么事情:保存数据
bgsave命令
#手动启动后台保存操作,但不是立即执行
bgsave
bgsave指令工作原理
注意:bgsave命令是针对save阻塞问题做的优化。Redis内部所有涉及到RDB操作都采用bgsave的方式,save命令可以放弃使用
bgsave指令相关配置
1.2.3 自动执行
到目前位置,save和bgsave都是手动的保存指令,实际开发中也不可能去手动执行,那么怎么才能自动执行呢?
自动执行相关配置
#满足限定时间范围内key的变化数量达到指定数量即进行持久化
save second changes
注意:
- save配置要根据实际业务情况进行设置,频度过高或过低都会出现性能问题,结果可能是灾难性的
- save配置中对second与changes设置通常具有互补对应关系,尽量不要设置成包含性关系
- save配置启动后执行的是bgsave操作
1.2.4 两种启动方式对比
方式 | save指令 | bgsave指令 |
---|---|---|
读写 | ||
阻塞客户端指令 | 同步 | 异步 |
额外消耗内存 | 否 | 是 |
启动新进程 | 否 | 是 |
rdb特殊启动形式
- 全量复制
在主从复制中会提到 - 服务器运行过程中重启
debug reload - 关闭服务器时指定保存数据
shutdown save
1.2.5 RDB优缺点
RDB优点
- RDB是一个紧凑压缩的二进制文件,存储效率较高
- RDB内部存储的是redis在某个时间点的数据快照,非常适合用于数据备份,全量复制等场景
- RDB恢复数据的速度要比AOF快很多
- 应用:服务器中每X小时执行bgsave备份,并将RDB文件拷贝到远程机器中,用于灾难恢复
RDB缺点
- RDB方式无论是执行指令还是利用配置,无法做到实时持久化,具体较大的可能性丢失数据
- bgsave指令每次运行要执行fork操作创建子进程,要牺牲掉一些性能
- Redis的众多版本中未进行RDB文件格式的版本统一,有可能出现个版本服务之间数据格式无法兼容现象
1.3 AOF
RDB存储的弊端
- 存储数据量较大,效率较低——基于快照思想,每次读写都是全部数据,当数据量巨大时,效率非常低
- 大数据量下的IO性能较低
- 基于fork创建子进程,内存产生额外消耗
- 宕机带来的数据丢失风险
解决思路
- 不写全数据,仅记录部分数据
- 改记录数据未记录操作过程
- 对所有操作均进行记录,排除丢失数据的风险
1.3.1 AOF概念
- AOF持久化:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中命令达到恢复数据的目的。与RDB相比可以简单描述为改记录数据为记录数据产生的过程
- AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式
1.3.2 AOF写数据三种策略
- always(每次)
每次写入操作均同步到AOF文件中,数据零误差,性能较低,不建议使用。 - everysec(每秒)
每秒将缓冲区中的指令同步到AOF文件中,数据准确性较高,性能较高,建议使用,也是默认配置。
在系统突然宕机的情况下丢失1秒内的数据 - no(系统控制)
由操作系统控制每次同步到AOF文件的周期,整体过程不可控
1.3.3 AOF功能开启
- 配置
#是否开启AOF持久化功能,默认为不开启
appendonly yes|no
#AOF写数据策略
appendfsync always|everysec|no
1.3.4 AOF相关配置
#AOF持久化文件名,默认文件名为appendonly.aof,建议配置为appendonly-端口号.aof
appendfilename filename
#AOF持久化文件保存路径,与RDB持久化文件保持一致即可
dir
1.3.5 AOF重写
上图左边的六条指令再写入ADOF文件时,可不可以优化为右侧的两条。答案是可以的,这就是接下来要讲的AOF重写。
概述
随着命令的不断写入AOF,文件会越来越大,为了解决这个问题,Redis引入AOF重写机制压缩文件体积,AOF文件重写是将Redis进程内的数据转换为写命令同步到新AOF文件的过程,简单说就是将同样一个数据的若干个命令执行结果转换为最终结果数据对应的指令进行记录。
AOF重写作用
- 降低磁盘占用量,提高磁盘利用率
- 提高持久化效率,降低持久化写时间,提高IO性能
- 降低数据恢复用时,提高数据恢复效率
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个元素
1.3.5.1 手动重写
bgrewriteaof
1.3.5.2 自动重写
- 自动重写触发条件设置
auto-aof-rewrite-min-size size
auto-aof-rewrite-percentage percentage
- 自动重写触发对比参数(运行指令info Persistence获取具体信息)
aof_current_size
aof_base_size
- 自动重写触发条件
aof_current_size > auto-aof-rewrite-min-size size
(aof_current_size - aof_base_size) / aof_base_size >= auto-aof-rewrite-percentage
1.4 RDB与AOF区别
持久化方式 | 占用存储空间 | 存储速度 | 恢复速度 | 数据安全性 | 资源消耗 | 启动优先级 |
---|---|---|---|---|---|---|
RDB | 小(数据级:压缩) | 慢 | 快 | 会丢失数据 | 高/重量级 | 低 |
AOF | 大(指令级:重写) | 快 | 慢 | 以策略决定 | 低/轻量级 | 高 |
RDB和AOF的选择
- 对数据非常敏感,建议使用默认的AOF持久化方案
- AOF持久化策略使用erverysecond,每秒钟fsync一次。该策略redis仍可以保持很好的处理性能,当出现问题时,最多丢失0-1秒中的数据。
- 注意:AOF文件存储体积较大,且恢复数据较慢
- 数据呈现阶段有效性,建议使用RDB持久化方案
- 数据可以良好的做到阶段内无丢失(该阶段是开发者或运维人员手工维护的),且恢复速度较快,阶段点数据恢复通常采用RDB方案
- 注意:利用RDB实现紧凑的数据持久化会使Redis降得很低
- 综合对比
- RDB与AOF得选择实际上是在做一种权衡,每种都有利弊
- 如不能承受数分钟以内得数据丢失,对业务数据非常敏感,选用AOF
- 如能承受数分钟以内数据丢失,且追求大数据集得恢复速度,选用RDB
- 灾难恢复选用RDB
- 双保险策略,同时开启RDB和AOF,重启后,Redis优先使用AOF来恢复数据,降低丢失数据的量
2.事务与锁
2.1 事务的基本操作
- 开启事务
#设定事务的开启位置,此指令执行后,后续的所有指令均加入到事务中
multi
- 执行事务
#设定事务的结束位置,同时执行事务。与multi成对出现,成对使用
exec
- 取消事务
#终止当前事务定义,发生在multi之后,exec之前
discard
注意:加入事务的命令暂时到任务队列中,并没有立即执行,只有执行exec命令才开始执行
2.2 事务的注意事项
- 语法错误,即命令书写错误
- 如果定义的事务中所包含的命令存在语法错误,整体事务中所有命令均不会被执行,包括那些语法正确的命令。
- 命令格式正确,但是无法正常的执行。例如对list进行incr操作
- 能够正确运行的命令会执行,运行错误的命令不会执行。
- 注意:已经执行完毕的命令对应的数据不会自动回滚,需要程序员自己在代码中实现回滚。
2.3 锁
2.3.1 watch
- 对key添加监控锁,在执行exec前,一旦key发生了变化就终止执行事务
watch key1 [key2…]
- 取消对所有key的监控
unwatch
注意:watch不能写在事务里面。
2.3.2 分布式锁
- 使用setnx设置一个公共锁
setnx lock-key value
利用setnx的特征,有值则设置失败,无值则设置成功。
- 对于设置成功的,拥有控制权限,进行下一步业务操作。
- 对于设置失败的,没有控制权限,排队或等待。
操作完毕通过del释放锁。
注意:以上只是设计概念,在实际开发中需要更详细的设置锁。
2.3.3 死锁
依赖分布式锁机制,某个用户操作时已经获取到了锁,但是对应的客户端宕机了,那其他用户或线程会一直等待该用户释放锁,导致死锁问题,该怎么办?
分析
由于加锁和解锁是由用户自己控制的,那么必然存在加锁后未解锁的风险,那么解锁就不能只依赖用户可,系统级别应给出相应的保险方案。
解决方案
- 为锁添加过期时间,用户为释放的话,过期自动释放
expire lock-key second #过期时间,单位秒
pexpire lock-key milliseconds #过期时间,单位毫秒
由于操作通常都是微秒或者毫秒级,因此该锁设定时间不宜设置过大。具体时间需要业务测试后确认
- 例如:持有锁的操作最长执行时间127ms,最短执行时间7ms
- 测试百万次最长执行时间对应命令的最大消耗时,测试百万次网络延迟平均耗时
- 锁时间设定推荐:最大耗时 * 120% + 平均网络延迟 * 110%
- 如果业务最大耗时<<网络平均延迟,通常为2个数量级,取其中单个耗时较长即可
3.Redis的删除策略
Redis种的数据有三种状态:具有时效性的、永久有效的和已过期的。对于已过期的数据Redis是怎么删除的呢,其实是根据策略决定的。
3.1 定时删除
创建一个定时器,当key设置过期时间,且过期时间到达时,由定时器任务立即执行对键的删除操作。
-
优点
节约内存,到时就删除,快速释放掉不必要的内存占用 -
缺点
CPU压力很大,无论CPU此时负载多高,均占用CPU,会影响redis服务器响应时间和指令吞吐量 -
总结
用处理器性能换取存储空间
3.2 惰性删除
数据到达过期时间,不做处理。等下次访问该数据时,如果未过期,返回数据;发现已经过期,删除,返回不存在。
-
优点
节约CPU性能,发现必须删除的时候才删除 -
缺点
内存压力很大,出现长期占用内存的数据 -
总结
用存储空间换取处理器性能
3.3 定期删除
每隔一段时间执行一次删除操作,并通过限制删除操作执行的时长和频率,来减少删除操作对cpu的影响。另一方面定时删除也有效的减少了因惰性删除带来的内存浪费。
执行逻辑看下图:
- 周期性轮询redis库中时效性数据,采用随机抽取的策略,利用过期数据占比的方式删除频率
- 特点1:CPU性能占用设置有峰值,检测频率可自定义设置。
- 特点2:内存压力不是很大,长期占用内存的冷数据会被持续清理。
- 总结:周期性抽查存储空间。
redis内部用的时惰性删除和定期删除。
3.4 逐出算法
当新数据进入redis时,如果内存不足怎么办?
- Redis使用内存存储数据,在执行每一个命令前,会调用freeMemorylfNeeded()检测内存是否充足。如果内存不满足新加入数据的最低存储要求,redis要临时删除一些数据为当前指令清理存储空间。清理数据的策略称为逐出算法。
- 逐出数据的过程不是100%能够清理出足够的可使用的内存空间,如果不成功则反复执行。当对所有数据尝试完毕后,如果不能达到内存清理的要求,将出现错误信息。
影响数据逐出的相关配置
- 最大可使用内存
#占用物理内存的比例,默认为0,表示不限制。生产环境中根据需求设定,通常设置在50%以上
maxmemory
- 每次选取待删除数据的个数
#选取数据时并不会全库扫描,导致严重的性能消耗,降低读写性能。因此采用随机获取数据的方式作为待检测删除数据
maxmemory-samples
- 指定删除策略(不是定时、定期、惰性策略)
#达到最大内存后的,对被挑选出来的数据进行删除的策略
maxmemory-policy volatile-lru
策略如下:
检查易失数据(可能会过期的数据集server.db[i].expires)
- volatile-lru:挑选最近最少使用的数据淘汰
- volatile-lfu:挑选最近使用次数最少的数据淘汰
- volatile-ttl :挑选将要过期的数据淘汰
- volatile-random:任意选择数据淘汰
检测全库数据(所有数据集server.db[i].dict)
- allkeys-lru:挑选最近最少使用的数据态太
- allkeys-lfu:挑选最近使用次数最少的数据淘汰
- allkeys-random:任意选择数据淘汰
- no-enviction(驱逐):禁止驱逐数据(redis4.0默认策略),会引发错误OOM(OutOfMemory)
4.Redis服务器基础配置redis.conf
这里只讲一些基础配置,其他的配置都在各自的业务中已有体现。
服务器端设定
- 设置服务器以守护进程的方式运行
deamonize yes|no
- 绑定主机地址
bind 127.0.0.1
- 设置服务器端口号
port 6379
- 设置数据库数量
databases 16
日志配置
- 设置服务器以指定日志记录级别
loglevel debug|verbose|notice|warning
注意:日志级别开发期设置为verbose即可,生产环境中配置为notice,简化日志输出量,降低写日志IO的频度
- 日志记录文件名
logfile 端口号.log
客户端配置
- 设置同一时间最大客户链接数,默认无限制。当客户端连接到达上线,Redis会关闭新的链接
maxclients 0
- 客户端限制等待最大时常,达到最大之后关闭连接。如需关闭该功能,设置为0
timeout 300
多服务器快捷配置
- 导入并加载指定配置文件信息,用于快速创建redis公共配置较多的redis实例配置文件,便于维护
includ /path/server-端口号.conf