Redis的持久化与性能管理

202 阅读20分钟

一、Redis的高可用

1.1 Redis高可用概述

在web服务器中,高可用是指服务器可以正常访问的时间,衡量的标准是在多长时间内可以提供正常服务(99.9%、99.99%、99.999%等等)。

但是在Redis语境中,高可用的含义似乎要宽泛一些,除了保证提供正常服务( 如主从分离、快速容灾技术),还需要考虑数据容量的扩展、数据安全不会丢失等。

1.2 实现Redis高可用的技术

在Redis中,实现高可用的技术主要包括持久化、主从复制、哨兵和cluster集群,下面分别说明它们的作用,以及解决了什么样的问题。

  • 持久化: 持久化是最简单的高可用方法(有时甚至不被归为高可用的手段),主要作用是数据备份,即将数据存储在硬盘,保证数据不会因进程退出而丢失。

  • 主从复制: 主从复制是高可用Redis的基础,哨兵和集群都是在主从复制基础上实现高可用的。主从复制主要实现了数据的多机备份(和同步),以及对于读操作的负载均衡和简单的故障恢复。

    • 缺陷:故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制。
  • 哨兵: 在主从复制的基础上,哨兵实现了自动化的故障恢复。(主挂了,找一个从成为新的主,哨兵节点进行监控)

    • 缺陷:写操作无法负载均衡;存储能力受到单机的限制。
  • Cluster集群: 通过集群,Redis解决了写操作无法负载均衡,以及存储能力受到单机限制的问题,实现了较为完善的高可用方案。(6台起步,成双成对,3主3从)

二、Redis的持久化

2.1 Redis的持久化功能

持久化的功能:Redis是内存数据库,数据都是存储在内存中,为了避免服务器断电等原因导致Redis进程异常退出后数据的永久丢失,需要定期将Redis中的数据以某种形式(数据或命令)从内存保存到硬盘;当下次Redis重启时,利用持久化文件实现数据恢复。除此之外,为了进行灾难备份,可以将持久化文件拷贝到一个远程位置。

2.2 Redis的持久化方式

Redis提供两种方式进行持久化:

  • RDB持久化:原理是将Redis在内存中的数据库记录定时保存到磁盘上。
  • AOF持久化(append only file):原理是将Redis的操作日志以追加的方式写入文件,类似于MySQL的binlog(二进制日志存储文件)

由于AOF持久化的实时性更好,即当进程以外退出时丢失的数据更少,因此AOF是目前主流的持久化,不过RDB持久化仍然有其用武之地。

三、RDB持久化

3.1 RDB持久化介绍

RDB 持久化.png

RDB持久化是指在指定的时间间隔内将内存中当前进程中的数据生成快照保存到硬盘(因此也称作快照持久化),用二进制压缩存储,保存的文件后缀是rdb;当Redis重新启动时,可以读取快照文件恢复数据。

3.2 RDB持久化触发条件

RDB持久化的触发分为手动触发和自动触发两种。

① 手动触发

  • save命令和bgsave都可以生成RDB文件
  • save命令会阻塞Redis服务器进程,查看RDB文件创建完毕为止,在Redis服务器阻塞期间,服务器不能处理任何命令请求。
  • bgsave命令会创建一个子进程,由子进程来负责创建RDB文件,父进程(即Redis主进程)则继续处理请求。

bgsave命令执行过程中,只有fork子进程时会阻塞服务器,而对于save命令,整个过程都会阻塞服务器,因此save已基本被废弃,线上环境要杜绝save的使用。

② 自动触发

  • 在自动触发RDB持久化时,Redis也会选择bgsave而不是save来进行持久化。

  • 自动触发最常见的情况是在配置文件中通过save [秒数] [变化次数] 的格式,指定当前第多少秒内发生了多少次变化时,会触发bgsave。

      vim /etc/redis/6379.conf   #redis的主配置文件
      
      
      -- 第219行至第221行 --
      save 900 1       #当时间到900秒时,如果redis数据发生了至少1次变化,则执行bgsave
      save 300 10      #当时间到300秒时,如果redis数据发生了至少10次变化,则执行bgsave
      save 60 10000    #当时间到60秒时,如果redis数据发生了至少10000次变化, 则执行bgsave
      #主配置文件中这三行save条件只要满足其中任意一个,都会引起bgsave的调用
      
      -- 第242行--
      rdbcompression yes    #表示开启RDB文件压缩
      
      -- 第254行 --
      dbfilename dump.rdb   #指定RDB文件名
      
      -- 第264行 --
       dir /var/lib/redis/6379   #指定RDB文件和AOF文件所在的目录
       
    

其他自动触发机制

除了save [秒数] [变化次数]这个配置文件中的触发条件配置以外,还有其他一些情况会触发bgsave:

  • 在主从复制场景下,如果从节点执行全量复制操作,则主节点会执行bgsave命令,并将RDB文件发送给从节点
  • 执行shutdown命令(关闭数据库)时,自动执行RDB持久化

3.3 RDB持久化执行流程

RDB bgsave.png

  1. Redis父进程首先判断:当前是否在执行save,或bgsave/bgrewriteaof的子进程,如果在执行则bgsave命令直接返回。
  2. 父进程执行fork操作创建子进程,这个过程中父进程是阻塞的,Redis不能执行来自客户端的任何命令
  3. 父进程fork后,bgsave 命令返回"Background saving started" 信息并不再阻塞父进程,并可以响应其他命令
  4. 子进程创建RDB文件,根据父进程内存快照生成临时快照文件,完成后对原有文件进行原子替换
  5. 子进程发送信号给父进程表示完成,父进程更新统计信息

总结

  • 在数据库执行bgsave触发RDB持久化时,先查看父进程下是否有其他子进程正在执行,若有子进程正在执行,则直接返回;
  • 若没有子进程则fork创建一个子进程,在创建子进程的过程中会阻塞父进程;子进程创建完毕后,将会返回信息给父进程,父进程将不再阻塞,响应其他命令;
  • 此时子进程单独工作,生成RDB文件,快照内存数据保存到硬盘,并进行压缩,替换原有的RDB文件,RDB文件替换结束后,发送信号通知父进程,RDB的持久化结束,父进程将会更新统计信息。

3.4 启动时加载

  • RDB文件的载入工作是在服务器启动时自动执行的,并没有专门的命令。但是由于AOF的优先级更高,因此当AOF开启时,Redis会优先载入
  • AOF文件来恢复数据:只有当AOF关闭时,才会在Redis服务器启动时检测RDB文件,并自动载入。服务器载入RDB文件期间处于阻塞状态,直到载入完成为止。
  • Redis载入RDB文件时,会对RDB文件进行校验,如果文件损坏,则日志中会打印错误,Redis启动失败。

四、AOF持久化

4.1 AOF持久化介绍

AOF 持久化.png

  • RDB持久化是将进程数据写入文件,而AOF持久化,则是将Redis执行的每次写、删除命令记录到单独的日志文件中,查询操作不会记录;
  • 当Redis重启时会再次执行AOF文件中的命令来恢复数据
  • 与RDB相比,AOF的实时性更好,因此已成为主流的持久化方案

4.2 开启AOF方法

Redis服务器默认开启RDB,关闭AOF,所以要开启AOF,需要在配置文件中配置

vim /etc/redis/6379.conf    # Redis配置文件

--700行--修改
appendonly yes              #开启AOF

--704行--             
appendfilename "appendonly.aof"  #指定AOF文件名称

--796行--
aof-load-truncated yes      #是否忽略最后一条可能存在问题的指令
#开启此指令,若服务器发生宕机,若有命令未执行完则会将此命令忽略

#配置完成后,重启redis
/etc/init.d/redis_6379 restart

4.3 AOF的执行流程

由于需要记录Redis的每条写命令,因此AOF不需要触发,下面介绍AOF的执行流程。

AOF的执行流程包括以下三条:

  • 命令追加(append):将Redis的写命令追加到缓冲区aof buf;
  • 文件写入(write)和文件同步(sync):根据不同的同步策略将aof buf中的内容同步到硬盘;
  • 文件重写(rewrite):定期重写AOF文件,达到压缩的目的

① 命令追加(append)

  • Redis先将写命令追加到缓冲区,而不是直接写入文件,主要是为了避免每次有写命令都直接写入硬盘,导致硬盘IO成为Redis负载的瓶颈。

  • 命令追加的格式是Redis命令请求的协议格式,它是一种纯文本格式,具有兼容性好、可读性强、容易处理、操作简单避免二次开销等优点。在AOF文件中,除了用于指定数据库的select命令(如select 0为选中0号数据库)是由Redis添加的,其他都是客户端发送来的写命令。

② 文件写入(write)和文件同步(sync)

  • Redis提供了多种AOF缓存区的同步文件策略,策略涉及到操作系统的write函数和fsync函数,说明如下:
    • 为了提高文件写入效率,在现代操作系统中,当用户调用write函数将数据写入文件时,操作系统通常会将数据暂存到一个内存缓冲区里,当缓冲区被填满或超过了指定时限后,才真正将缓冲区的数据写入到硬盘里。
    • 这样的操作虽然提高了效率,但也带来了安全问题:如果计算机停机,内存缓冲区中的数据会丢失。因此系统同时提供了fsync、fdatasync等同步函数,可以强制操作系统立刻将缓冲区中的数据写入到硬盘里,从而确保数据的安全性。

③ 文件重写(rewrite)

  • 随着时间流逝,Redis服务器执行的写命令越来越多,AOF文件也会越来越大;过大的AOF文件不仅会影响服务器的正常运行,也会导致数据恢复需要的时间过长。
  • 文件重写是指定期重写AOF文件,减小AOF文件的体积。需要注意的是,AOF 重写是把Redis进程内的数据转化为写命令,同步到新的AOF文件;不会对旧的AOF文件进行任何读取、写入操作。
  • 关于文件重写需要注意的另一点是:对于AOF持久化来说,文件重写虽然是强烈推荐的,但并不是必须的;即使没有文件重写,数据也可以被持久化并在Redis启动的时候导入。因此在一些现实中,会关闭自动的文件重写,然后通过定时任务在每天的某一时刻定时执行。

文件重写之所以能够压缩AOF文件,原因在于:

  • 过期的数据不再写入文件。

  • 无效的命令不再写入文件:如有些数据被重复设值(set key1 v1, set key v2)、 有些数据被删除了(set myset vl, del myset)等。

  • 多条命令可以合并为一个:如sadd myset v1, sadd myset v2, sadd myset v3可以合并为sadd myset v1 v2 v3。(sadd命令可以整合多条命令为单独一条命令)

  • 文件重写的触发,分为手动触发和自动触发:

    • 手动触发:直接调用bgrewriteaof命令,该命令的执行与bgsave有些类似,都是fork子进程进行具体的工作,并且都只有在fork子进程时阻塞
    • 自动触发:通过设置auto-aof-rewrite-min-size选项和auto-aof-rewrite-percentage选项来自动执行BGREWRITEAOF。

只有当auto-aof-rewrite-min-size和auto-aof-rewrite-percentage两个选项同时满足时,才会自动触发AOF重写,即bgrewriteaof操作。(具体操作如下)

vim /etc/redis/6379.conf  
--第771行--
auto-aof-rewrite-percentage 100   
#当前AOF文件大小(aof_current_size)是上次日志重写时AOF文件大小(aof_base_size)两倍时,发生BGREWRITEAOF操作
--第772行--
772 auto-aof-rewrite-min-size 64mb
#当前AOF文件执行BGREWRITEAOF命令的最小值,避免刚开始启动Reids时由于文件尺寸较小导致频繁的BGREWRITEAOF

4.4 AOF缓存区的同步文件策略的三种同步方式

vim /etc/redis/6379.conf

----729行至731行三行策略含义----

  • appendfsync always:

命令写入aof_buf后立即调用系统fsync操作同步到AOF文件,fsync完成后线程返回。

这种情况下,每次有写命令都要同步到AOF文件,硬盘IO成为性能瓶颈,Redis只能支持大约几百TPS写入,严重降低了Redis的性能; 即便是使用固态硬盘(SSD) ,每秒大约也只能处理几万个命令,而且会大大降低SSD的寿命。 (该策略不可取,对硬盘消耗大,处理性能低)

  • appendfsync no:

命令写入aof_buf后调用系统write操作,不对AOF文件做fsync同步;

同步由操作系统负责,通常同步周期为30秒。 这种情况下,文件同步的时间不可控,且缓冲区中堆积的数据会很多,数据安全性无法保证。  (缓冲区数据容易堆积,安全性没有保障,堆积后服务器宕机,堆积的数据将会丢失)

  • appendfsync everysec:

命令写入aof_buf后调用系统write操作,write完成后线程返回;

fsync同步文件操作由专门的线程每秒调用一次。 everysec是前述两种策略的折中,是性能和数据安全性的平衡,因此是Redis的默认配置,也是我们推荐的配置。  (相比于前两种策略属于折中策略,将同步时间减少,使得每秒同步一次,保证数据的安全与性能)

4.5 文件重写的流程

bgrewriteaof.png

关于文件重写的流程,有两点需要特别注意:①重写由父进程fork子进程进行。②重写期间Redis执行的写命令,需要追加到新的AOF文件中,为此Redis引入了aof_rewrite_buf缓存。

  • 文件重写的流程如下:
  • Redis父进程首先判断当前是否存在正在执行bgsave/bgrewriteaof的子进程,如果存在则bgrewriteaof命令直接返回,如果存在bgsave命令则等bgsave执行完成后再执行。(正常情况下使用AOF就会使用AOF进行记录,不会使用RDB。主从复制时会自动触发bgsave命令)
  • 父进程执行fork操作创建子进程,这个过程中父进程是阻塞的(无法接受任何客户端的请求)。
  • 父进程fork后,bgrewriteaof 命令返回"Background append only file rewrite started" 信息并不再阻塞父进程,并可以响应其他命令。Redis的所有写命令依然写入AOF缓冲区,并根据appendfsync策略同步到硬盘,保证原有AOF机制的正确。
  • 由于fork操作使用写时复制技术,子进程只能共享fork操作时的内存数据。由于父进程依然在响应命令,因此Redis使用AOF重写缓冲区(aof_rewrite_ buf)保存这部分数据,防止新AOF文件生成期间丢失这部分数据。也就是说,bgrewriteaof执行期间,Redis的写命令同时追加到aof_ buf和aof_rewirte_ buf两个缓冲区。 (保证新写入的数据不丢失)
  • 子进程根据内存快照,按照命令合并规则写入到新的AOF文件。
  • 子进程写完新的AOF文件后,向父进程发信号,父进程更新统计信息,具体可以通过info persistence查看。
  • 父进程把AOF重写缓冲区的数据写入到新的AOF文件,这样就保证了新AOF文件所保存的数据库状态和服务器当前状态一致。
  • 使用新的AOF文件替换老文件,完成AOF重写。(替换是原子性的)

总结

  • 执行bgrewriteaof命令(文件重写)发送到Redis数据库后,Redis父进程不会马上执行,先进行判断是否有子进程正在工作,有则直接返回,若没有,则直接fork创建子进程,fork期间会阻塞父进程,fork子进程后,将不再阻塞父进程,父进程可以继续响应其他请求;
  • 子进程在fork结束后开始产生新的AOF文件(根据重写规则进行命令的合并),此时父进程有可能会接收到其他的请求,他会将其他请求分别写入aof_buf缓存缓冲区和aof_rewrite_buf缓存缓冲区;
  • 在子进程重写新的AOF文件完毕之后,会发送信号给父进程告知重写完毕,父进程会统计信息,在这期间aof_buf缓存缓冲区根据同步策略将新的数据请求写入旧的AOF文件中;aof_rewrite_buf缓存缓冲区则负责同步新数据到新的AOF文件当中,保证新旧两个AOF文件都具有最新数据;
  • 最后使用新AOF文件对旧AOF文件进行替换,完成整个重写文件的流程

4.6 启动时加载

  • 当AOF开启时,Redis启动时会优先载入AOF文件来恢复数据;只有当AOF关闭时,才会载入RDB文件恢复数据。
  • 当AOF开启,但AOF文件不存在时,即使RDB文件存在也不会加载。
  • Redis载入AOF文件时,会对AOF文件进行校验,如果文件损坏,则日志中会打印错误,Redis启动失败。但如果是AOF文件结尾不完整(机器突然宕机等容易导致文件尾部不完整),且aof-load-truncated参数开启,则日志中会输出警告,Redis忽略掉AOF文件的尾部,启动成功。aof-load-truncated参数默认是开启的。

4.7 RDB与AOF的优缺点

  • RDB持久化

    • 优点
      • RDB文件紧凑,体积小,网络传输快,适合全量复制;恢复速度比AOF快很多。当然,与AOF相比, RDB最重要的优点之一是对性能的影响相对较小。
    • 缺点
      • RDB文件的致命缺点在于其数据快照的持久化方式决定了必然做不到实时持久化,而在数据越来越重要的今天,数据的大量丢失很多时候是无法接受的,因此AOF持久化成为主流。此外,RDB文 件需要满足特定格式,兼容性差(如老版本的Redis不兼容新版本的RDB文件)。
      • 对于RDB持久化,一方面是bgsave在进行fork操作时Redis主进程会阻塞,另一方面,子进程向硬盘写数据也会带来IO压力。
  • AOF持久化

    • 优点
      • 与RDB持久化相对应,AOF的优点在于支持秒级持久化、实时性好、兼容性好
    • 缺点
      • 文件大、恢复速度慢、对性能影响大。
      • 对于AOF持久化,向硬盘写数据的频率大大提高(everysec策略下为秒级),IO 压力更大,甚至可能造成AOF追加阻塞问题。
      • AOF文件的重写与RDB的bgsave类似,会有fork时的阻塞和子进程的 IO 压力问题。相对来说,由于AOF向硬盘中写数据的频率更高,因此对Redis主进程性能的影响会更大。

五、Redis性能管理

5.1 Redis的内存碎片

① 内存碎片如何产生

  • redis内部有自己的内存管理器,为了提高内存使用的效率,来对内存的申请和释放进行管理
  • redis中的值删除的时候,并没有把内存直接释放,交还给操作系统,而是将内存交给内存管理器
  • redis申请内存时,先是看自己的内存管理器中是否有足够的内存可用
  • redis这种禁止,提高了内存使用率,但是会使redis内部有部分内存没有使用却不释放的内存,导致了内存碎片的发生

② 查看Redis内存使用情况

redis-cli               #登录redis数据库
info memory             #查看内存
redis-cli info memory   #执行info memory 命令后退出redis

info memory 中主要字段含义
used_memory         # redis中数据所占用的内存 以字节单位显示
used_memory_humnan  # 人性化方式查看占用内存   单位为K、M、G显示
used_memory_rss     # redis向操作系统申请的内存
used_memory_peak    # redis内存使用的峰值(最大值)

mem_fragmentation_ratio  
# redis内存的碎片率 碎片率=申请的内存(used_memory_rss)/已使用内存(used_memory)

image.png

image.png

③ 跟踪内存碎片率

  • 内存碎片率超过1到1.5之间是正常的,表示内存碎片率比较低,说明redis没有发生交换内存
  • 内存碎片率超过1.5,说明redis消耗了实际需要物理内存的150%,其中50%是内存碎片率
  • 内存碎片率低于1,说明redis内存分配超出了物理内存,操作系统正在进行内存交换,需要增加可用物理内存,或者减少redis内存占用

④ 如何解决碎片率大的问题

  • redis版本4.0以下
redis-cli      #进入redis
shutdown save  
#保存redis操作并关闭redis服务并重启
#重启后,redis会将没用的内存归还给操作系统,降低碎片率
#虚拟机中内存碎片率显示不正确
  • redis版本4.0以上(不重启情况下,线上整理内存碎片)
config set activedefrag yes
#自动碎片清理,内存会自动清理

memory purge
#手动碎片清理

5.2 内存使用率

redis实例的内存使用率超过最大可用内存,操作系统将进行内存与swap空间交换

避免内存交换发生的方法

  • 针对缓存数据大小选择安装redis实例
  • 尽可能的使用hash数据结构存储
  • 设置key的过期时间

5.3 内回收key

内存清理策略,保证合理分配redis有限的内存资源。

当内存使用达到设置的最大阈值时,需选择一种key的回收策略,默认情况下回收策略是禁止删除(noenviction)。

配置文件中修改 maxmemory-policy 属性值:

 vim /etc/redis/6379.conf
 ---598行----
 maxmemory-policy noenviction   #修改max-memory-policy属性值


 volatile-lru
 #使用LRU算法从已设置过期时间的数据集合中淘汰数据
 (移除最近最少使用的key,针对设置了TTL的key)

 volatile-ttl
 #从已设置过期时间的数据集合中挑选即将过期的数据淘汰
 (移除最近过期的key)

 volatile-random
 #从已设置过期时间的数据集合中随机挑选数据淘汰
 (在设置了TTL的key里随机移除)

 allkeys-lru
 #使用LRU算法 从所有数据集合中淘汰数据
 (移除最少使用的key,针对所有的key)

 allkeys-random
 #从数据集合中任意选择数据淘汰(随机移除key)

 noenviction
 #禁止淘汰数据(不删除直到写满时报错)