redis持久化之AOF及内存回收策略
一:Redis的AOF是什么?
以日志的形式来记录每个写操作(读操作不记录),将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。RDB可以搞定备份恢复的事情,为什么还会出现AOF?
使用RDB进行保存时候,如果Redis服务器发送故障,那么会丢失最后一次备份的数据!AOF出现是来解决这个问题!同时出现RDB和AOF是冲突呢?还是协作?是协作,但是首先启动找的是aof。当redis服务器挂掉时,重启时将按照以下优先级恢复数据到内存:
- 如果只配置AOF,重启时加载AOF文件恢复数据;
- 如果同时 配置了RBD和AOF,启动是只加载AOF文件恢复数据;
- 如果只配置RBD,启动是加载RDB文件恢复数据。
恢复时需要注意,要是主库挂了不能直接重启主库,否则会直接覆盖掉从库的AOF文件,一定要确保要恢复的文件都正确才能启动,否则会冲掉原来的文件。
二:Redis配置文件redis.conf中关于AOF的相关配置
############################## APPEND ONLY MODE ############################### # By default Redis asynchronously dumps the dataset on disk. This mode is # good enough in many applications, but an issue with the Redis process or # a power outage may result into a few minutes of writes lost (depending on # the configured save points). # # The Append Only File is an alternative persistence mode that provides # much better durability. For instance using the default data fsync policy # (see later in the config file) Redis can lose just one second of writes in a # dramatic event like a server power outage, or a single write if something # wrong with the Redis process itself happens, but the operating system is # still running correctly. # # AOF and RDB persistence can be enabled at the same time without problems. # If the AOF is enabled on startup Redis will load the AOF, that is the file # with the better durability guarantees. # # Please check http://redis.io/topics/persistence for more information. #默认redis使用的是rdb方式持久化,这种方式在许多应用中已经足够用了。 但是redis如果中途宕机,会导致可能有几分钟的数据丢失,根据save来策略进行持久化, Append Only File是另一种持久化方式,可以提供更好的持久化特性。 Redis会把每次写入的数据在接收后都写入 appendonly.aof 文件, 每次启动时Redis都会先把这个文件的数据读入内存里,先忽略RDB文件 appendonly no #如果要开启,改为yes # The name of the append only file (default: "appendonly.aof") # aof文件名 appendfilename "appendonly.aof" # The fsync() call tells the Operating System to actually write data on disk # instead of waiting for more data in the output buffer. Some OS will really flush # data on disk, some other OS will just try to do it ASAP. # # Redis supports three different modes: # # no: don't fsync, just let the OS flush the data when it wants. Faster. # always: fsync after every write to the append only log. Slow, Safest. # everysec: fsync only one time every second. Compromise. # # The default is "everysec", as that's usually the right compromise between # speed and data safety. It's up to you to understand if you can relax this to # "no" that will let the operating system flush the output buffer when # it wants, for better performances (but if you can live with the idea of # some data loss consider the default persistence mode that's snapshotting), # or on the contrary, use "always" that's very slow but a bit safer than # everysec. # # More details please check the following article: # http://antirez.com/post/redis-persistence-demystified.html # # If unsure, use "everysec". # appendfsync always #aof持久化策略的配置 #no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。 #always表示每次写入都执行fsync,以保证数据同步到磁盘。 #everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。 appendfsync everysec # appendfsync no # When the AOF fsync policy is set to always or everysec, and a background # saving process (a background save or AOF log background rewriting) is # performing a lot of I/O against the disk, in some Linux configurations # Redis may block too long on the fsync() call. Note that there is no fix for # this currently, as even performing fsync in a different thread will block # our synchronous write(2) call. # # In order to mitigate this problem it's possible to use the following option # that will prevent fsync() from being called in the main process while a # BGSAVE or BGREWRITEAOF is in progress. # # This means that while another child is saving, the durability of Redis is # the same as "appendfsync none". In practical terms, this means that it is # possible to lose up to 30 seconds of log in the worst scenario (with the # default Linux settings). # # If you have latency problems turn this to "yes". Otherwise leave it as # "no" that is the safest pick from the point of view of durability. # 在aof重写或者写入rdb文件的时候,会执行大量IO,此时对于everysec和always的aof模式来说, 执行fsync会造成阻塞过长时间,no-appendfsync-on-rewrite字段设置为默认设置为no。 如果对延迟要求很高的应用,这个字段可以设置为yes,否则还是设置为no, 这样对持久化特性来说这是更安全的选择。设置为yes表示rewrite期间对新写操作不fsync, 暂时存在内存中,等rewrite完成后再写入,默认为no,建议yes。Linux的默认fsync策略是30秒。 可能丢失30秒数据。随着aof文件持续增大,会fork出一条进程去对aof文件重写 no-appendfsync-on-rewrite no # Automatic rewrite of the append only file. # Redis is able to automatically rewrite the log file implicitly calling # BGREWRITEAOF when the AOF log size grows by the specified percentage. # # This is how it works: Redis remembers the size of the AOF file after the # latest rewrite (if no rewrite has happened since the restart, the size of # the AOF at startup is used). # # This base size is compared to the current size. If the current size is # bigger than the specified percentage, the rewrite is triggered. Also # you need to specify a minimal size for the AOF file to be rewritten, this # is useful to avoid rewriting the AOF file even if the percentage increase # is reached but it is still pretty small. # # Specify a percentage of zero in order to disable the automatic AOF # rewrite feature. #aof自动重写配置。当目前aof文件大小超过上一次重写的aof文件大小的百分之多少进行重写, 即当aof文件增长到一定大小的时候Redis能够调用bgrewriteaof对日志文件进行重写。 当前AOF文件大小是上次日志重写得到AOF文件大小的一倍(设置为100)时, 自动启动新的日志重写过程。 auto-aof-rewrite-percentage 100 #设置允许重写的最小aof文件大小,避免了达到约定百分比但尺寸仍然很小的情况还要重写 auto-aof-rewrite-min-size 64mb # An AOF file may be found to be truncated at the end during the Redis # startup process, when the AOF data gets loaded back into memory. # This may happen when the system where Redis is running # crashes, especially when an ext4 filesystem is mounted without the # data=ordered option (however this can't happen when Redis itself # crashes or aborts but the operating system still works correctly). # # Redis can either exit with an error when this happens, or load as much # data as possible (the default now) and start if the AOF file is found # to be truncated at the end. The following option controls this behavior. # # If aof-load-truncated is set to yes, a truncated AOF file is loaded and # the Redis server starts emitting a log to inform the user of the event. # Otherwise if the option is set to no, the server aborts with an error # and refuses to start. When the option is set to no, the user requires # to fix the AOF file using the "redis-check-aof" utility before to restart # the server. # # Note that if the AOF file will be found to be corrupted in the middle # the server will still exit with an error. This option only applies when # Redis will try to read more data from the AOF file but not enough bytes # will be found. #aof文件可能在尾部是不完整的,当redis启动的时候,aof文件的数据被载入内存。 重启可能发生在redis所在的主机操作系统宕机后, 尤其在ext4文件系统没有加上data=ordered选项(redis宕机或者异常终止不会造成尾部不完整现象。) 出现这种现象,可以选择让redis退出,或者导入尽可能多的数据。如果选择的是yes, 当截断的aof文件被导入的时候,会自动发布一个log给客户端然后load。如果是no, 用户必须手动redis-check-aof修复AOF文件才可以。 aof-load-truncated yes # When rewriting the AOF file, Redis is able to use an RDB preamble in the # AOF file for faster rewrites and recoveries. When this option is turned # on the rewritten AOF file is composed of two different stanzas: # # [RDB file][AOF tail] # # When loading Redis recognizes that the AOF file starts with the "REDIS" # string and loads the prefixed RDB file, and continues loading the AOF # tail. # # This is currently turned off by default in order to avoid the surprise # of a format change, but will at some point be used as the default. #Redis4.0新增RDB-AOF混合持久化格式,在开启了这个功能之后, AOF重写产生的文件将同时包含RDB格式的内容和AOF格式的内容, 其中RDB格式的内容用于记录已有的数据,而AOF格式的内存则用于记录最近发生了变化的数据, 这样Redis就可以同时兼有RDB持久化和AOF持久化的优点(既能够快速地生成重写文件, 也能够在出现问题时,快速地载入数据)。 aof-use-rdb-preamble no
注:当 aof 文件损坏的时候 可以使用修复:使用redis-check-aof –fix xxx.aof 进行修复 同理 rdb文件也有专门的修复redis-check-dump
aof 文件重写: AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似。
重写过程中,AOF文件被更改了怎么办?
Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含 了恢复当前数据集所需的最小命令集合。 重写的流程是这样,
- 主进程会fork一个子进程出来进行AOF重写,这个重写过程并不是基于原有的aof文件来做的,而 是有点类似于快照的方式,全量遍历内存中的数据,然后逐个序列到aof文件中。
- 在fork子进程这个过程中,服务端仍然可以对外提供服务,那这个时候重写的aof文件的数据和 redis内存数据不一致了怎么办?不用担心,这个过程中,主进程的数据更新操作,会缓存到 aof_rewrite_buf中,也就是单独开辟一块缓存来存储重写期间收到的命令,当子进程重写完以后 再把缓存中的数据追加到新的aof文件。
- 当所有的数据全部追加到新的aof文件中后,把新的aof文件重命名正式的文件名字,此后所有的操 作都会被写入新的aof文件。
- 如果在rewrite过程中出现故障,不会影响原来aof文件的正常工作,只有当rewrite完成后才会切 换文件。因此这个rewrite过程是比较可靠的。
Redis允许同时开启AOF和RDB,既保证了数据安全又使得进行备份等操作十分容易。如果同时开启 后,Redis重启会使用AOF文件来恢复数据,因为AOF方式的持久化可能丢失的数据更少。
三:小结
AOF 文件是一个只进行追加的日志文件
Redis可以在AOF文件体积变得过大时,自动地在后台对AOF进行重写,AOF文件有序地保存了对数据库执行所有写入操作,这些写入操作作为redis协议的格式保存,因此AOF文件的内容非常容易被人读懂,对文件进行分析也很轻松对于相同的数据集来说,AOF文件的体积通常大于RDB文件的体积,根据所使用的fsync策略,AOF的速度可能会慢于RDB
1:到底启用哪种持久化策略? :
RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储。AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
只做缓存:如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.
同时开启两种持久化方式 – 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据, 因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整. RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件。那要不要只使用AOF呢? 建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份), 快速重启,而且不会有AOF可能潜在的bug,留着作为一个万一的手段。
2.性能建议:
因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则。如果Enalbe AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了。代价一是带来了持续的IO,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上。默认超过原大小100%大小时重写可以改到适当的数值。如果不Enable AOF ,仅靠Master-Slave Replication 实现高可用性也可以。能省掉一大笔IO也减少了rewrite时带来的系统波动。代价是如果Master/Slave同时挂掉,会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入较新的那个。新浪微博就选用了这种架构。
内存回收:
Reids 所有的数据都是存储在内存中的,在某些情况下需要对占用的内存空间进行回收。内存回收主要分为两类,一类是 key 过期,一类是内存使用达到上限(max_memory)触发内存淘汰。
过期策略:
定时过期(主动 淘汰 ):每个设置过期时间的 key 都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的 CPU 资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
惰性过期(被动 淘汰 ):只有当访问一个 key 时,才会判断该 key 是否已过期,过期则清除。该策略可以最大化地节省 CPU 资源,却对内存非常不友好。极端情况可能出现大量的过期 key 没有再次被访问,从而不会被清除,占用大量内存。例如 String,在 getCommand 里面会调用 expireIfNeeded
第二种情况,每次写入 key 时,发现内存不够,调用 activeExpireCycle 释放一部分内存。
每隔一定的时间,会扫描一定数量的数据库的 expires 字典中一定数量的 key,并清除其中已过期的 key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得 CPU 和内存资源达到最优的平衡效果。
Redis 中同时使用了惰性过期和定期过期两种过期策略j结合
Redis 的内存淘汰策略,是指当内存使用达到最大内存极限时,需要使用淘汰算法来决定清理掉哪些数据,以保证新数据的存入。redis.conf 参数配置:
# maxmemory <bytes>
如果不设置 maxmemory 或者设置为 0,64 位系统不限制内存,32 位系统最多使用 3GB 内存。
Redis内存回收策略:
很多同学了解了Redis的好处之后,于是把任何数据都往Redis中放,如果使用不合理很容易导致数据超 过Redis的内存,这种情况会出现什么问题呢?
- Redis中有很多无效的缓存,这些缓存数据会降低数据IO的性能,因为不同的数据类型时间复杂度 算法不同,数据越多可能会造成性能下降
- 随着系统的运行,redis的数据越来越多,会导致物理内存不足。通过使用虚拟内存(VM),将很 少访问的数据交换到磁盘上,腾出内存空间的方法来解决物理内存不足的情况。虽然能够解决物理 内存不足导致的问题,但是由于这部分数据是存储在磁盘上,如果在高并发场景中,频繁访问虚拟 内存空间会严重降低系统性能。
所以遇到这类问题的时候,我们一般有几种方法。
- 对每个存储到redis中的key设置过期时间,这个根据实际业务场景来决定。否则,再大的内存都会 虽则系统运行被消耗完。 增
- 加内存
- 使用内存淘汰策略。
设置了maxmemory的选项,redis内存使用达到上限。可以通过设置LRU算法来删除部分key,释放空间。默认是按照过期时间的,如果set时候没有加上过期时间就会导致数据写满maxmemory。
Redis中提供了一种内存淘汰策略,当内存不足时,Redis会根据相应的淘汰规则对key数据进行淘汰。 Redis一共提供了8种淘汰策略,默认的策略为noeviction,当内存使用达到阈值的时候, 所有引起申请内存的命令会报错。
- volatile-lru,针对设置了过期时间的key,使用lru算法进行淘汰。
- allkeys-lru,针对所有key使用lru算法进行淘汰。
- volatile-lfu,针对设置了过期时间的key,使用lfu算法进行淘汰。
- allkeys-lfu,针对所有key使用lfu算法进行淘汰。
- volatile-random,从所有设置了过期时间的key中使用随机淘汰的方式进行淘汰。
- allkeys-random,针对所有的key使用随机淘汰机制进行淘汰。
- volatile-ttl,删除生存时间最近的一个键。
- noeviction,不删除键,值返回错误。
前缀为volatile-和allkeys-的区别在于二者选择要清除的键时的字典不同,volatile-前缀的策略代表从 redisDb中的expire字典中选择键进行清除;allkeys-开头的策略代表从dict字典中选择键进行清除。
内存淘汰算法的具体工作原理是:
- 客户端执行一条新命令,导致数据库需要增加数据(比如set key value)
- Redis会检查内存使用,如果内存使用超过 maxmemory,就会按照置换策略删除一些 key
- 新的命令执行成功
了解LRU算法:
LRU是Least Recently Used的缩写,也就是表示最近很少使用,也可以理解成最久没有使用。也就是说 当内存不够的时候,每次添加一条数据,都需要抛弃一条最久时间没有使用的旧数据。
标准的LRU算法为了降低查找和删除元素的时间复杂度,一般采用Hash表和双向链表结合的数据结构, hash表可以赋予链表快速查找到某个key是否存在链表中,同时可以快速删除、添加节点,
如下图所 示。 双向链表的查找时间复杂度是O(n),删除和插入是O(1),借助HashMap结构,可以使得查找的时 间复杂度变成O(1) Hash表用来查询在链表中的数据位置,链表负责数据的插入,当新数据插入到链表头部时有两种情况。
- 链表满了,把链表尾部的数据丢弃掉,新加入的缓存直接加入到链表头中。
- 当链表中的某个缓存被命中时,直接把数据移到链表头部,原本在头节点的缓存就向链表尾部移动
这样,经过多次Cache操作之后,最近被命中的缓存,都会存在链表头部的方向,没有命中的,都会在 链表尾部方向,当需要替换内容时,由于链表尾部是最少被命中的,我们只需要淘汰链表尾部的数据即可。
Redis中的LRU算法:
实际上,Redis使用的LRU算法其实是一种不可靠的LRU算法,它实际淘汰的键并不一定是真正最少使用 的数据,它的工作机制是:
- 随机采集淘汰的key,每次随机选出5个key
- 然后淘汰这5个key中最少使用的key
这5个key是默认的个数,具体的数值可以在redis.conf中配置
maxmemory-samples 5
当近似LRU算法取值越大的时候就会越接近真实的LRU算法,因为取值越大获取的数据越完整,淘汰中 的数据就更加接近最少使用的数据。这里其实涉及一个权衡问题,
如果需要在所有的数据中搜索最符合条件的数据,那么一定会增加系统的开销,Redis是单线程的,所以耗时的操作会谨慎一些。
为了在一定成本内实现相对的LRU,早期的Redis版本是基于采样的LRU,也就是放弃了从所有数据中搜 索解改为采样空间搜索最优解。Redis3.0版本之后,Redis作者对于基于采样的LRU进行了一些优化:
- Redis中维护一个大小为16的候选池,当第一次随机选取采用数据时,会把数据放入到候选池中, 并且候选池中的数据会根据时间进行排序。
- 当第二次以后选取数据时,只有小于候选池内最小时间的才会被放进候选池。 当候选池的数据满了之后,那么时间最大的key就会被挤出候选池。
- 当执行淘汰时,直接从候选池 中选取最近访问时间小的key进行淘汰。
如下图所示,首先从目标字典中采集出maxmemory-samples个键,缓存在一个samples数组中,然 后从samples数组中一个个取出来,和回收池中以后的键进行键的空闲时间 (空闲时间越大,代表越久没有被使用,准备淘汰),从而更新回收池。 在更新过程中,首先利用遍历找到的每个键的实际插入位置x,然后根据不同情况进行处理。
- 回收池满了,并且当前插入的key的空闲时间最小(也就是回收池中的所有key都比当前插入的key 的空闲时间都要大),则不作任何操作。
- 回收池未满,并且插入的位置x没有键,则直接插入即可
- 回收池未满,且插入的位置x原本已经存在要淘汰的键,则把第x个以后的元素都往后挪一个位 置,然后再执行插入操作。
- 回收池满了,将当前第x个以前的元素往前挪一个位置(实际就是淘汰了),然后执行插入操作。
这样做的目的是能够选出最真实的最少被访问的key,能够正确不常使用的key。因为在Redis3.0之前是 随机选取样本,这样的方式很有可能不是真正意义上的最少访问的key。
LRU算法有一个弊端,加入一个key值访问频率很低,但是最近一次被访问到了,那LRU会认为它是热点 数据,不会被淘汰。同样, 经常被访问的数据,最近一段时间没有被访问,这样会导致这些数据被淘汰掉,导致误判而淘汰掉热点 数据,于是在Redis 4.0中,新加了一种LFU算法。
LFU算法:
LFU(Least Frequently Used),表示最近最少使用,它和key的使用次数有关,其思想是:根据key最 近被访问的频率进行淘汰,比较少访问的key优先淘汰,反之则保留。
LRU的原理是使用计数器来对key进行排序,每次key被访问时,计数器会增大,当计数器越大,意味着 当前key的访问越频繁,也就是意味着它是热点数据。 它很好的解决了LRU算法的缺陷:一个很久没有 被访问的key,偶尔被访问一次,导致被误认为是热点数据的问题。
LFU的实现原理如下图所示,LFU维护了两个链表,横向组成的链表用来存储访问频率,每个访问频 率的节点下存储另外一个具有相同访问频率的缓存数据。具体的工作原理是:
- 当添加元素时,找到相同访问频次的节点,然后添加到该节点的数据链表的头部。如果该数据链表 满了,则移除链表尾部的节点
- 当获取元素或者修改元素时,都会增加对应key的访问频次,并把当前节点移动到下一个频次节 点。
- 添加元素时,访问频率默认为1,随着访问次数的增加,频率不断递增。而当前被访问的元素也会 随着频率增加进行移动。