读书笔记-Redis设计与实现-主从复制,哨兵和集群

1.主从复制

  • 通过SLAVEOF命令让一个服务器复制另一个服务器,我们称呼被复制的服务器为主服务器(master),而对主服务器进行复制的服务器则被称为从服务器(slave)
1
2
## 服务器127.0.0.1:12345将成为127.0.0.1:6379的从服务器
127.0.0.1:123456> SLAVEOF 127.0.0.1 6379
  • 进行复制中的主从服务器双方的数据库将保存相同的数据,概念上将这种现象称作“数据库状态一致”,或者简称“一致”
  • 旧版复制(2.8版本之前)
    • 同步操作用于将从服务器的数据库状态更新至主服务器当前所处的数据库状态
      • 从服务器向主服务器发送SYNC命令。
      • 收到SYNC命令的主服务器执行BGSAVE命令,在后台生成一个RDB文件,并使用一个缓冲区记录从现在开始执行的所有写命令。
      • 当主服务器的BGSAVE命令执行完毕时,主服务器会将BGSAVE命令生成的RDB文件发送给从服务器,从服务器接收并载入这个RDB文件,将自己的数据库状态更新至主服务器执行BGSAVE命令时的数据库状态。
      • 主服务器将记录在缓冲区里面的所有写命令发送给从服务器,从服务器执行这些写命令,将自己的数据库状态更新至主服务器数据库当前所处的状态
    • 命令传播操作则用于在主服务器的数据库状态被修改,导致主从服务器的数据库状态出现不一致时,让主从服务器的数据库重新回到一致状态
      • 主服务器会将自己执行的写命令,也即是造成主从服务器不一致的那条写命令,发送给从服务器执行,当从服务器执行了相同的写命令之后,主从服务器将再次回到一致状态
    • 缺点
      • 断线后复制会重新执行sync命令,但是传输的rdb文件有可能大部分都是曾经同步过的,这会造成效率低下
      • 本身sync命令就是非常耗费资源
        • 主服务器需要执行BGSAVE命令会耗费主服务器大量的CPU、内存和磁盘I/O资源。
        • 主服务器需要将自己生成的RDB文件发送给从服务器会耗费主从服务器大量的网络资源(带宽和流量)
        • 接收到RDB文件的从服务器需要载入主服务器发来的RDB文件,并且在载入期间,从服务器会因为阻塞而没办法处理命令请求。
  • 新版复制(2.8版本开始)
    • PSYNC命令代替SYNC命令来执行复制时的同步操作
      • PSYNC命令具有完整重同步(full resynchronization)和部分重同步(partial resynchronization)
      • 完整重同步用于处理初次复制情况,和sync命令一样
      • 部分重同步则用于处理断线后重复制情况:当从服务器在断线后重新连接主服务器时,如果条件允许,主服务器可以将主从服务器连接断开期间执行的写命令发送给从服务器,从服务器只要接收并执行这些写命令,就可以将数据库更新至主服务器当前所处的状态
    • 部分重同步的实现
      • 主服务器的复制偏移量(replication offset)和从服务器的复制偏移量。
        • 主服务器每次向从服务器传播N个字节的数据时,就将自己的复制偏移量的值加上N
        • 从服务器每次收到主服务器传播来的N个字节的数据时,就将自己的复制偏移量的值加上N
        • 如果主从服务器处于一致状态,那么主从服务器两者的偏移量总是相同的
      • 主服务器的复制积压缓冲区(replication backlog)。
        • 主服务器的复制积压缓冲区里面会保存着一部分最近传播的写命令,并且复制积压缓冲区会为队列中的每个字节记录相应的复制偏移量
        • 为了安全起见,可以将复制积压缓冲区的大小设为2secondwrite_size_per_second,这样可以保证绝大部分断线情况都能用部分重同步来处理
      • 服务器的运行ID(run ID)
        • 每个Redis服务器,不论主服务器还是从服务,都会有自己的运行ID
        • 根据运行ID是否相同,从服务器决定进行部分还是完整重同步操作
  • PSYNC命令的执行逻辑
  • 主服务器通过向从服务器传播命令来更新从服务器的状态,保持主从服务器一致,而从服务器则通过向主服务器发送命令来进行心跳检测,以及命令丢失检测

2.哨兵(Sentinel)

  • 哨兵(Sentinel)实例监视主从服务器状态,如果主服务器下线,哨兵会选择一个从服务器升级为主服务器
    • Sentinel系统会挑选其中一个从服务器升级为新的主服务器。
    • Sentinel系统会向所有从服务器发送新的复制指令,当所有从服务器都开始复制新的主服务器时,故障转移操作执行完毕
    • 监视已下线的server1,重新上线时,设置为从服务器
  • 启动命令
    1
    2
    3
    ## 执行以下两条命令之一
    $ redis-sentinel sentinel.conf
    $ redis-server sentinel.conf --sentinel
  • Sentinel通过向主服务器发送INFO命令来获得主服务器属下所有从服务器的地址信息,并为这些从服务器创建相应的实例结构,以及连向这些从服务器的命令连接和订阅连接。
  • 在一般情况下,Sentinel以每十秒一次的频率向被监视的主服务器和从服务器发送INFO命令,当主服务器处于下线状态,或者Sentinel正在对主服务器进行故障转移操作时,Sentinel向从服务器发送INFO命令的频率会改为每秒一次。
  • 对于监视同一个主服务器和从服务器的多个Sentinel来说,它们会以每两秒一次的频率,通过向被监视服务器的__sentinel__:hello频道发送消息来向其他Sentinel宣告自己的存在。
  • 每个Sentinel也会从__sentinel__:hello频道中接收其他Sentinel发来的信息,并根据这些信息为其他Sentinel创建相应的实例结构,以及命令连接。
  • Sentinel只会与主服务器和从服务器创建命令连接和订阅连接,Sentinel与Sentinel之间则只创建命令连接。
  • Sentinel以每秒一次的频率向实例(包括主服务器、从服务器、其他Sentinel)发送PING命令,并根据实例对PING命令的回复来判断实例是否在线,当一个实例在指定的时长中连续向Sentinel发送无效回复时,Sentinel会将这个实例判断为主观下线。
  • 当Sentinel将一个主服务器判断为主观下线时,它会向同样监视这个主服务器的其他Sentinel进行询问,看它们是否同意这个主服务器已经进入主观下线状态。
  • 当Sentinel收集到足够多的主观下线投票之后,它会将主服务器判断为客观下线,并发起一次针对主服务器的故障转移操作。

3.集群

  • 节点通过握手来将其他节点添加到自己所处的集群当中。
  • 集群中的16384个槽可以分别指派给集群中的各个节点,每个节点都会记录哪些槽指派给了自己,而哪些槽又被指派给了其他节点。
  • 节点在接到一个命令请求时,会先检查这个命令请求要处理的键所在的槽是否由自己负责,如果不是的话,节点将向客户端返回一个MOVED错误,MOVED错误携带的信息可以指引客户端转向至正在负责相关槽的节点。
  • 对Redis集群的重新分片工作是由redis-trib负责执行的,重新分片的关键是将属于某个槽的所有键值对从一个节点转移至另一个节点。
  • 如果节点A正在迁移槽i至节点B,那么当节点A没能在自己的数据库中找到命令指定的数据库键时,节点A会向客户端返回一个ASK错误,指引客户端到节点B继续查找指定的数据库键。
  • MOVED错误表示槽的负责权已经从一个节点转移到了另一个节点,而ASK错误只是两个节点在迁移槽的过程中使用的一种临时措施。❑集群里的从节点用于复制主节点,并在主节点下线时,代替主节点继续处理命令请求。
  • 集群中的节点通过发送和接收消息来进行通信,常见的消息包括MEET、PING、PONG、PUBLISH、FAIL五种。

实现方式

要实现 Redis 集群,你可以使用 Redis 官方提供的 Redis Cluster 功能。Redis Cluster 是 Redis 的一种分布式模式,它将数据分散存储在多个节点上,提供了数据的高可用性和横向扩展能力。

下面是实现 Redis 集群的一般步骤:

  1. 安装和配置多个 Redis 实例:在不同的服务器或主机上安装和配置多个 Redis 实例。每个 Redis 实例将成为 Redis 集群中的一个节点。确保每个节点的配置文件中设置了相同的 cluster-enabled yes 参数。

  2. 创建集群:选择一个节点作为集群的首领节点(bootstrap节点),使用 redis-cli 工具连接到该节点,并执行以下命令来创建集群:

    1
    2
    3
    4
    5
    6
    7
    redis-cli --cluster create <node1>:<port1> <node2>:<port2> ... <nodeN>:<portN> --cluster-replicas <replicas>
    ```

    其中,`<node1>:<port1>` 到 `<nodeN>:<portN>` 是 Redis 节点的地址和端口,`<replicas>` 是每个主节点对应的从节点数量。

    例如,如果有 3 个主节点,每个主节点有 1 个从节点,可以执行类似下面的命令:

    redis-cli –cluster create 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 –cluster-replicas 1

    1
    2
    3
    4
    5
    6
    7
    8
    9

    在执行命令后,首领节点将自动与其他节点进行通信,组成一个完整的 Redis 集群。

    3. 验证集群:连接到任意一个节点,使用 `redis-cli` 工具执行一些命令来验证集群的状态和数据分布情况,例如 `CLUSTER INFO`、`CLUSTER NODES`、`GET <key>` 等。

    4. 扩展集群:如果需要扩展集群,可以添加新的 Redis 节点,并将它们加入到集群中。执行以下命令来添加新节点:

    ````
    redis-cli --cluster add-node <new-node>:<port> <existing-node>:<port>

    其中,<new-node>:<port> 是要添加的新节点的地址和端口,<existing-node>:<port> 是现有集群中的任意一个节点的地址和端口。

    添加新节点后,Redis 集群会自动将数据进行重新分片和迁移,以扩展整个集群的容量和性能。

这些是实现 Redis 集群的基本步骤。请注意,为了确保数据的高可用性,建议在每个主节点上设置至少一个从节点。此外,还可以通过 Redis Sentinel 或 Redis Cluster 自身的故障检测和故障转移机制来提供集群的高可用性。