Redis面临的的问题

在单点的redis中,redis经常面临着数据丢失,并发能力弱,故障恢复能力弱,以及存储能力弱,这都使其在微服务的架构中难以立足,接下来,我将以这四方面的问题出发,介绍每一种问题的解决办法

Redis持久化

由于redis时基于内存存储,这会导致一旦服务器重启,存储在服务器中redis的数据将会丢失,即使redis中存储的一般是相对与不那么重要的热点数据,但一旦丢失,仍会造成很大的影响,解决这个问题自然要做到redis数据的持久化,常见的持久化分为两种,分别是RDB和AOF

RDB持久化

RDB持久化通过创建Redis在某一时间点的快照(snapshot)来实现数据的持久化。这个快照文件是一个经过压缩的二进制文件,包含了Redis在某个时间点上的数据库状态。简单来说就是把内存中所有数据记录到磁盘中,当redis重启后,从磁盘中读取快照文件,恢复数据

RDB持久化的工作原理

  1. 触发条件:RDB持久化可以通过两种方式来触发:
    • 手动触发:通过执行SAVEBGSAVE命令。SAVE命令会阻塞Redis服务器进程,直到RDB文件创建完毕为止,因此不推荐在生产环境中使用。而BGSAVE命令会创建一个子进程来负责创建RDB文件,这样Redis服务器进程可以继续处理客户端请求,不会造成阻塞。
    • 自动触发:Redis配置文件中可以设置一些条件,当满足这些条件时,Redis会自动执行BGSAVE命令。例如,可以设置“在一定时间内,如果修改了指定数量的键,则自动执行BGSAVE”,在停机前也会自动触发一次快照。
  2. 快照生成:当BGSAVE命令执行时,Redis会创建一个子进程,这个子进程会遍历当前数据库中的所有键值对,并将它们写入到一个临时文件中。当所有键值对都写入完成后,这个临时文件会被替换为旧的RDB文件,从而实现数据的持久化。
  3. 数据恢复:当Redis服务器启动时,如果检测到RDB文件存在,Redis会自动加载RDB文件中的数据到内存中,从而实现数据的恢复。

RDB持久化的优缺点

优点

  • RDB文件是一个紧凑的二进制文件,它保存了Redis在某个时间点的完整数据快照,非常适合用于数据备份和灾难恢复。
  • RDB文件的生成和加载速度都比较快,因此可以在较短的时间内完成数据的备份和恢复。
  • RDB持久化对Redis服务器的性能影响较小,尤其是在使用BGSAVE命令时。

缺点

  • RDB持久化是基于快照的,因此它可能会丢失最后一次快照之后到Redis停机之间的数据。
  • 如果Redis中的数据量非常大,那么RDB文件的生成可能会消耗较多的CPU和内存资源,并且生成RDB文件的时间也会比较长。

COW(Copy-On-Write)写时复制

在Redis的RDB持久化过程中,COW技术主要通过操作系统的fork系统调用来实现。具体过程如下:

  1. fork子进程:
    • 当Redis执行bgsave命令时,会调用操作系统的fork()函数来创建一个子进程。这个子进程是父进程(即Redis主进程)的一个完整副本,包括内存空间、文件描述符等。
    • fork操作完成后,子进程和父进程共享相同的内存空间,但此时内存空间是被标记为只读的。
  2. 写时复制:
    • 在子进程开始执行RDB持久化任务(即将内存数据写入磁盘文件)的过程中,如果父进程(Redis主进程)需要修改内存中的数据,操作系统会检测到这种写操作,并触发COW机制。
    • 触发COW机制后,操作系统会复制被修改的内存页到一个新的位置,并将父进程的内存页指针指向这个新位置。这样,父进程就可以在不影响子进程的情况下继续执行写操作。
    • 子进程则继续在其原始的内存页(只读)上执行RDB持久化任务,直到完成。
  3. RDB文件生成:
    • 子进程完成所有内存数据的遍历和写入后,会生成一个完整的RDB文件,并将其保存到磁盘上。
    • 父进程(Redis主进程)则继续处理客户端的请求,不受子进程持久化操作的影响。

AOF持久化

AOF(Append Only File)持久化是Redis数据库中的一种数据持久化方式,主要通过保存数据库执行的写命令来记录数据库的状态。以下是对AOF持久化的详细介绍:

AOF持久化的基本原理

AOF持久化以日志的形式记录每个写操作,包括数据的增加、删除、修改等,但不记录读操作。Redis服务重启时,会根据AOF文件中的命令重新执行一遍,以达到恢复数据的目的。AOF文件是一个仅追加的日志文件,即使在写入过程中发生宕机,也不会破坏已存在的内容。

AOF持久化的流程

AOF持久化主要分为以下几个步骤:

  1. 命令追加:
    • 当AOF持久化功能处于开启状态时,服务器在执行完一个写命令后,会将该命令以协议格式追加到AOF文件数据缓冲区(aof_buf)中。
  2. 文件写入:
    • 在每一个事件循环结束前,Redis会调用flushAppendOnlyFile函数,将aof_buf中的内容写入到AOF文件中。
  3. 文件同步:
    • AOF文件会根据appendfsync参数设置的持久化策略同步到磁盘。Redis提供了三种同步策略:
      • Always:每次写命令写入aof_buf后,都同步将aof_buf中的写命令fsync到磁盘。这种方式可靠性最高,但性能影响最大。
      • Everysec:每次写命令写入aof_buf后,每隔一秒将aof_buf中的写命令fsync到磁盘。这是默认策略,性能适中,最多丢失一秒的数据。
      • No:写命令写入aof_buf后,由操作系统决定何时将aof_buf中的写命令fsync到磁盘。这种方式性能最好,但宕机时丢失的数据最多。

AOF文件重写

随着写入AOF内容的增加,AOF文件会变得越来越大,这不仅会占用大量的磁盘空间,还会增加Redis服务重启时的数据恢复时间。为了解决这个问题,Redis提供了AOF文件重写机制。

AOF文件重写的触发机制有两种:

  1. 手动触发:通过执行BGREWRITEAOF命令。
  2. 自动触发:与auto-aof-rewrite-percentage和auto-aof-rewrite-min-size两个参数有关。当AOF文件大小超过上一次重写后AOF文件大小的指定百分比(默认为100%)且AOF文件大小大于指定最小值(默认为64MB)时,会自动触发重写。

重写过程中,Redis会fork出一个子进程来处理重写操作,父进程继续处理其他客户端命令请求。子进程根据内存中的当前数据状态,生成一个新的AOF文件,并替换掉旧的AOF文件。

AOF持久化的优缺点

优点:

  1. 数据安全性高:AOF持久化提供了多种同步策略,可以根据需要选择最适合的同步方式,确保数据的可靠性。
  2. 易于恢复:AOF文件内容易于理解,方便进行手动修复和恢复。
  3. 灵活性高:AOF文件是追加写入的,即使出现写入故障,也不会破坏已存在的数据。

缺点:

  1. 文件体积大:AOF文件通常比相同数据集的RDB文件大,占用更多的磁盘空间。
  2. 恢复速度慢:当AOF文件很大时,Redis服务重启时的数据恢复时间会比较长。
  3. 性能影响:AOF持久化对性能有一定的影响,尤其是在使用Always同步策略时。

两种持久化的对比

RDB AOF
持久化方式 定时对整个内存做快照 记录每一次执行的命令
数据完整性 不完整,两次备份之间会有丢失 相对完整,取决于刷盘策略
文件大小 会有压缩,文件体积小 记录命令,文件体积很大
宕机恢复速度 很快
数据恢复优先级 低,因为数据完整性不如AOF 高,因为数据完整性更高
系统资源占用 高,大量CPU和内存消耗 低,主要时磁盘IO资源 但AOF重写会占用大量CPU和内存资源
使用场景 可以容忍数分钟的数据丢失,追求更快的启动速度 对数据安全性要求较高

Redis主从集群

Redis通过搭建主从集群,主节点负责写操作,将数据同步给从节点,从节点负责读操作,通过这样的读写分离,可显著提高redis处理高并发问题的能力

主从集群的工作原理

  1. 数据复制:
    • 当主节点接收到写操作时,它会先将数据写入本地,并将写操作记录到复制缓冲区中。
    • 从节点通过定期发送SYNC命令或PSYNC命令向主节点请求数据同步。
    • 主节点在接收到同步请求后,会将复制缓冲区中的数据发送给从节点,从而实现数据的同步。
  2. 读写分离:
    • 客户端通过配置或负载均衡器将写请求发送到主节点,将读请求发送到从节点。
    • 这种方式可以显著提高系统的吞吐量和响应速度,因为读操作通常比写操作更频繁且对实时性要求较低。
  3. 故障转移:
    • 在主节点出现故障时,从节点可以通过选举机制或外部管理工具(如Redis Sentinel)自动或手动升级为主节点,以接管写操作。
    • 同时,其他从节点会重新与新的主节点建立同步关系,确保数据的一致性和可用性。

配置主节点

  1. 修改Redis配置文件

    (例如,

    1
    redis.conf

    ):

    • 设置bind指令以监听所有网段(如果需要远程访问)或特定IP地址。
    • 开启守护进程模式(daemonize yes)。
    • 配置日志文件和工作目录。
    • 开启AOF或RDB持久化功能(根据需要)。
  2. 重启Redis服务:

    • 应用配置文件更改后,重启Redis服务以使更改生效。

配置从节点

  1. 复制Redis配置文件:
    • 为每个从节点复制一份Redis配置文件,并进行适当的修改。
  2. 修改从节点配置文件:
    • 设置不同的端口号以避免冲突(例如,6380、6381等)。
    • 配置replicaof(在Redis 5.0之前为slaveof)指令,指定主节点的IP地址和端口号。
    • 配置其他必要的参数,如持久化、日志记录等。
  3. 启动从节点Redis服务:
    • 使用修改后的配置文件启动从节点的Redis服务。

主从集群的第一次数据同步

建立连接与协商

  • 执行命令:在从节点上执行replicaof(Redis 5.0之前使用slaveof)命令,指定主节点的IP地址和端口号。
  • 发送请求:从节点向主节点发送数据同步请求。
  • 判断首次同步:主节点接收到同步请求后,会判断这是否是第一次同步。判断依据通常是从节点发送的replication ID(简称replid)与主节点不一致,如果是第一次同步,则拒绝增量同步而是用全量同步,通过偏移量(offset)判断是否需要同步数据,在判断是第一次同步后,主节点会将自己的replid和offset传给从节点,让从节点继承他的replid。

准备数据快照

  • 生成RDB文件:主节点执行bgsave命令,在后台生成当前数据库的快照(RDB文件)。这个过程中,主节点仍然可以正常处理客户端的请求。
  • 记录新命令:在生成RDB文件的过程中,主节点会将新接收到的写操作命令记录在repl_backlog缓冲区中,以确保这些操作在后续能够同步给从节点。

发送RDB文件

  • 传输文件:RDB文件生成后,主节点会将该文件发送给从节点。
  • 清空并加载数据:从节点接收到RDB文件后,会先清空本地数据库,然后加载RDB文件中的数据。

同步增量数据

  • 发送repl_backlog中的命令:在从节点加载完RDB文件后,主节点会将repl_backlog缓冲区中记录的、在RDB文件生成之后接收到的所有写操作命令发送给从节点。
  • 执行命令:从节点接收到这些命令后,会重新执行它们,以确保与主节点的数据完全一致。

完成同步

  • 建立持续复制:完成上述步骤后,主从节点之间会建立起一个持续复制的连接。主节点会将后续接收到的所有写操作命令实时同步给从节点,以保持数据的一致性。

主从集群的非第一次数据同步

  1. 记录偏移量:
    • 主节点和从节点都会维护一个偏移量(offset),用于记录当前复制的位置。
    • 每次主节点处理完写操作后,都会更新自己的偏移量。
  2. 从节点请求同步:
    • 从节点会定期(或根据配置)向主节点发送自己的偏移量和复制ID(replid),如果是非第一次同步,则从节点的replid和主节点是一致的,则进行增量同步,请求同步。
  3. 主节点判断同步方式:
    • 主节点接收到从节点的请求后,会比较从节点的偏移量和自己的偏移量。
    • 如果从节点的偏移量小于主节点的偏移量,说明从节点有部分数据未同步,此时将执行增量同步,如果从节点的复制偏移量在repl_backlog中找不到对应的数据(即该偏移量对应的数据已经被覆盖),可能是因为repl_backlog的大小达到上限或是从节点断开太久,环形缓冲区中的数据被覆盖,则此时应该改进全量同步。
  4. 发送增量数据:
    • 主节点会从自己的repl_backlog缓冲区中,获取从节点偏移量之后的写操作命令。
    • 将这些增量数据发送给从节点。
  5. 从节点执行命令:
    • 从节点接收到增量数据后,会按顺序执行这些命令,更新自己的数据集。
  6. 更新偏移量:
    • 从节点执行完命令后,会更新自己的偏移量,并上报给主节点。

主从集群优化

  • 在master中配置repl-diskless-sync yes启用无磁盘复制,避免全量同步时的磁盘IO
  • Redis单节点上的内存不要占太大,减少RDB导致的过多磁盘IO
  • 适当提高repl_backlog的大小,发现slave宕机尽快实现故障修复,尽量避免全量同步
  • 限制一个matser的slave的节点数量,如果实在太多slave,尽可能采取主-从-从链式结构,减少matser压力

Redis哨兵集群

Redis的哨兵集群(Redis Sentinel)是一个高可用的解决方案,它主要用于管理多个Redis服务器实例,提供监控、自动故障转移和通知功能。

哨兵集群的组成

  • 哨兵节点:哨兵集群由多个哨兵节点组成,每个哨兵节点都是一个运行在特殊模式下的Redis进程,但它不提供读写服务,主要用于监控和管理Redis服务器。
  • Redis服务器实例:哨兵集群监控的Redis服务器实例包括主节点(master)和从节点(slave)。主节点负责处理客户端的读写请求,而从节点则复制主节点的数据,并在主节点故障时接替其工作。

哨兵集群的工作原理

  1. 监控:哨兵节点会周期性地向所有被监控的Redis服务器实例发送PING命令,以检测它们是否仍然在线运行。如果某个Redis服务器实例在规定时间内没有响应哨兵的PING命令,哨兵就会将其标记为“主观下线”。
  2. 客观下线:当多个哨兵节点都将同一个Redis服务器实例标记为“主观下线”时,该实例会被标记为“客观下线”,即确认该实例已经故障。
  3. 自动故障转移:一旦主节点被标记为“客观下线”,哨兵集群就会开始自动故障转移流程。首先,哨兵集群会通过投票选举出哨兵中的matser,由他做下面的操作,然后,哨兵集群会从从节点中选择一个作为新的主节点(选主过程涉及多个规则,如从库优先级、复制进度等)。然后,哨兵会更新所有从节点和新主节点的配置,让它们开始复制新的主节点。最后,哨兵会通知客户端新的主节点地址,以便客户端可以重新连接到Redis服务。
  4. 通知:在整个故障转移过程中,哨兵节点会通过API向管理员或其他应用程序发送通知,以便及时了解Redis服务的状态变化。

选举master的规则

  • 首先会判断slave节点与matser节点断开时间的长短,如果超过指定值(down-after-milliseconds*10)则会排除该slave节点,因为此时该从节点与原主节点的数据严重不匹配
  • 再判断slave节点的slave-priority值,越小优先级越高,如果时0则永远不参加选举
  • 如果slave-prority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高
  • 最后判断slave节点的运行id大小,越小优先级越高

实现故障转移

当选举了其中一个slave为新的matser后,会有一下三部

  • sentinel给备选的slave节点发送slaveof no one命令,让该节点成为matser
  • sentinel给所有其它slave发送slaveof 备选节点的ip 命令,让这些slave成为新matser的从节点,开始从新的matser上同步数据
  • 最后,sentinel将故障节点标记为slave,当故障节点恢复后会自动成为新的matser的slave节点

哨兵集群的优势

  • 高可用性:哨兵集群可以自动处理Redis主节点的故障,确保Redis服务的高可用性。
  • 自动故障转移:无需人工干预,哨兵集群可以自动完成故障转移流程,减少服务中断时间。
  • 灵活配置:哨兵集群支持多种配置选项,可以根据实际需求进行灵活配置。

注意事项

  • 节点数量:为了确保哨兵集群的可靠性和稳定性,建议部署奇数个哨兵节点(如3个、5个等),以满足多数投票机制的需求。
  • 网络分区:在部署哨兵集群时,需要考虑网络分区的问题。为了避免因网络分区导致的误判和故障转移,建议将哨兵节点分布在不同的网络区域中。
  • 监控和日志:定期监控哨兵集群的运行状态和日志信息,以便及时发现和解决问题。

Redis分片集群

以上的集群以经解决了Redis的持久化,高并发能力,故障转移,但由于主节点只有一个,而仅有主节点可以存储数据,所以我们接下来将搭建分片集群,横向扩展主节点,以提高redis的存储能力,在分片集群中有多个主节点,每个主节点都可以存储数据,每个主节点下也会有多个从节点,主节点间通过ping检测彼此健康状态,彼此承担哨兵的的职责,由于主节点间不会做数据的拷贝同步,所以主节点间的数据是不同的,但客户端访问任意节点,最终都会通过心跳转发到正确节点

工作原理

  1. 数据分配:在Redis分片集群中,数据被分为多个片段,每个片段存储在不同的节点上。这些节点可以是物理服务器或虚拟服务器。
  2. 请求处理
    • 客户端发送命令到Redis分片集群中的任意一个节点。
    • 节点根据命令涉及的键的哈希值计算出对应的槽号。
    • 节点根据槽号确定该槽所在的节点,节点之间会通过心跳机制进行重定向,并将命令路由到该节点。
    • 目标节点处理命令并返回结果给客户端。
  3. 数据同步与故障恢复
    • 主节点与从节点之间通过异步复制进行数据同步。
    • 当主节点发生故障时,集群会自动进行故障转移,选举一个从节点作为新的主节点,以保证系统的可用性。

优势与特点

  1. 高可用性:通过数据冗余和自动故障转移机制,即使某个节点发生故障,集群仍然可以对外提供服务。
  2. 高性能:通过将数据分散到多个节点上,可以实现并行处理,从而提高读写吞吐量和响应速度。
  3. 可伸缩性:集群的节点数量可以根据需要进行动态调整,以适应不同的负载和存储需求。
  4. 负载均衡:通过智能路由算法将请求分配到不同的节点上,以实现负载均衡,提高系统整体性能。