zoukankan      html  css  js  c++  java
  • Redis设计与实现(三)——多机器数据库的实现(主从复制、哨兵、集群)

    主从复制

    通过SLAVEOF命令,可以让两台服务器确认主从关系。以后的过程中,slave会与master保持数据一致性。

    主从复制功能实现

    Redis的复制功能分为同步和命令传播两个操作:

    • 同步是将master的数据全部拷贝到slave上。
    • 命令传播是已经完成了同步,在之后master被修改了,将修改的命令发给slave,让slave也执行一遍,以此保证数据一致。

    SYNC命令实现

    确认了主从关系之后,从服务器首先需要进行初始的数据同步,而这个同步可以通过SYNC命令完成,步骤如下:

    1. 从服务器向主服务器发送SYNC命令;
    2. 主服务器收到命令,自己执行BGSAVE命令,生成RDB文件,并用一个缓冲区记录从现在开始执行的所有命令;
    3. 当BGSAVE执行完毕,主服务器将RDB发送给从服务器,从服务器载入这个RDB文件,将数据库状态更新;
    4. 主服务器将缓冲区里的所有写命令发送给从服务器,从服务器执行这些命令,将数据库更新到最新的状态。

    存在的问题:
    对于服从服务器在运行过程中偶尔断线了,之后的同步,如果采用SYNC命令,又会让主服务器生成RDB文件,而生成和传输RDB的开销是很大的,不论是CPU、IO、内存或者网络带宽。其实没有必要将主从服务器的所有数据进行同步,只需要将断线过程中的那部分进行同步就行了。

    PSYNC命令实现

    PSYNC就是用来代替SYN命令来执行复制时的同步操作的,它就有完整同步和部分同步的两种模式:

    • 完整同步:这里的步骤和SYNC命令基本一样,都是让主服务器生成RDB,然后传送缓冲区里的写命令来同步。
    • 部分重同步:用于处理断线之后重复制的情况。当服务器出现断连,如果条件允许,主服务器可以将与某从服务器断连期间内的写命令发送给从服务器,这样就可以不用RDB实现同步了。

    部分重同步的实现:

    1. 复制偏移量
      主从服务器都会维护一个偏移量,这个偏移量表示当前最新执行了的命令字节位置。对,这里的单位不是命令数量,而是字节。如果两个服务器发生断裂,那么主服务器发送偏移量之间的命令即可;
    2. 复制积压缓冲区
      主服务器保存的命令数量肯定不能时无限的,所以,需要通过一个先进先出的队列,保存最近执行的命令,队列长度是固定的,默认位1MB。如果主从之间的偏移量超出了队列长度,那么就会采用完整同步方式了。
      对于缓冲区的大小,可以参照平均断连修复时间和平均单位时间内命令个数之积来确定。
    3. 服务器运行ID
      每个主从服务器都有自己的ID,从服务器在初次同步的时候,就会保存主服务器的ID;在断连之后的同步中,就会发送之前保存的主服务器ID。如果之后的ID不同,那说明主服务器发生变更,那么就直接执行完整同步。

    PSYNC命令的具体流程入下图所示。

    在这里插入图片描述

    复制的实现

    步骤如下:

    1. 设置主服务器的地址和端口;
    2. 建立套接字;
    3. 发送PING命令;
    4. 身份验证;
    5. 发送端口信息;
    6. 同步;
    7. 命令传播。

    心跳检测

    心跳检测具有三个作用:

    • 检测主从服务器的网络连接状态。
    • 辅助实现min-slaves选项。
      防止主服务器在不安全的情况下执行写命令,例如从服务器数量少于3个或者3个服务器延迟都大于10秒。
    • 检测命令丢失。

    哨兵机制

    哨兵是Redis的高可用性解决方法,由一个或者多个哨兵实例组成的哨兵系统,可以监视多个主从服务器,如果某个主服务器挂了,那么哨兵就会选出一个从服务器作为新的主服务器。

    启动并初始化哨兵

    当一个哨兵启动的时候,它需要执行以下步骤 :

    1. 初始化服务器;
      哨兵本质上就是一个Redis服务器,只是功能不同而已。
    2. 将普通的Redis服务器使用的代码替换为哨兵专用代码;
      哨兵模式下就不能使用数据库相关的命令了。
    3. 初始化哨兵的状态;
      初始化一个数据结构,用于保存服务器重所有和哨兵功能相关的状态。
    4. 根据给定的配置文件,初始化哨兵的监视主服务器列表
      哨兵状态中的masters字典记录了所有被哨兵监视的主服务器相关信息,其中,字典的键就是主服务器的名字,字典的值就是被监视主服务器对于的数据结构。
    5. 创建连向主服务器的网络连接。
      哨兵会为每个被监视的主服务器创建两个异步网络连接,一个是发送命令用的连接,一个是订阅消息用的连接。

    获取主服务器信息

    哨兵会以默认十秒的间隔,向主服务器请求当前信息,这些信息包括:

    • 主服务器本身的信息,包括id,角色。
    • 该主服务器下的从服务器信息,包括ip,端口,角色。

    获取从服务器信息

    当通过主服务器,哨兵发现有新的从服务器出现时,就会与其建立相应的命令和订阅连接。

    在通过命令连接之后确定了订阅信息之后,哨兵同样会以每十秒一次的频率,通过订阅连接向从服务器请求信息,包括:

    • 从服务器的运行ID、角色。
    • 它的主服务器的IP和端口。
    • 主从的连接状态,从服务区的优先级,从服务器的复制偏移量。

    这些信息会被保存在sentinels字典当中。

    与其他哨兵进行连接

    当多个哨兵监视同一个主服务器,他们就会自动发现对方的存在。当一个哨兵发现了一个新的哨兵之后,它不仅会在字典中创建一个性的哨兵实例,相互之间还会建立起一个新的命令连接。但是不会建立订阅连接,因为什么什么信息需要周期知道的。

    检测主观下线状态

    哨兵会像每个与之建立连接的服务器发送PING命令,如果在一定时间内没有接收到回应,就会主观认为该对于服务器已经下线了。

    主观下线时长用于判断监视的所有服务器的状态,不同哨兵的时长是不同的。

    检测客观下线状态

    当一个哨兵将一个主服务器判断为主观下线了的时候,它会询问其他也监视了这个服务器的哨兵们,这个主服务器状态。当判断其他哨兵中得出的主观下线状态达到一个值,该该哨兵会将该服务器标识为客观下线状态。

    对于多少个哨兵认为主观下线了,才能被判断是客观下线了,这个阈值可以在初始配置文件中规定,而且也会随着哨兵的数量而发生改变。

    选举领头哨兵

    当一个主服务器被判断为客观下线了,监视这个主服务器的各个哨兵会进行协商,选举出一个领头哨兵,并由领头哨兵对下线主服务器执行故障转移操作。

    选举流程如下:

    1. 选举由很多个轮投票组成,每轮就是一个纪元,每个纪元都会选出一些头领,直到最后剩下一个头领。
    2. 最开始的纪元,每个哨兵都会要求其他哨兵将自己设置为头领,每个哨兵会把票投给最先接收到请求的哨兵,然后会产生个数大于一个的局部头领。
    3. 在接下来的纪元,所有局部头领之间开始同样的选举,直到最后剩下一个头领。
    4. 最后的头领的得票数必须的大于哨兵总数的一半,所以,哨兵个数最好是个奇数。
    5. 对于在给定时间内没有出现头领,会重新进行一轮选举。

    故障转移

    当产生头领哨兵之后,头领会对已经下线的主服务器做故障转移的操作,包括三个步骤:

    1. 在已经下线的主服务器的所有从服务器中选一个,将其转化为主服务器;
    2. 让其他的从服务器复制这个新的主服务器;
    3. 当旧的主服务器又上线了,它就会成为新主服务器的从服务器。

    集群

    Redis集群是Redis提供分布式数据库方案,集群通过分片来进行数据共享,并提供复制和故障转移的功能。

    分片就是将所有键值对分成一段段的,一个节点处理一段。

    节点

    集群需要由许多节点够成,将独立的节点组建成集群就需要将它们连接在一起。通过CLUSTER MEET <ip> <port>命令即可。

    启动节点
    一个节点就是运行在集群模式下的Redis服务器,服务器在启动的时候会根据cluster-enabled配置来确定是否开始集群模式。集群模式下的服务器,以然会使用单机模式下的大部分功能。

    CLUSTER MEET命令实现
    接收到消息的双方都会为对方建立一个节点的数据对象,然后放到自己的节点字典中以备使用。然后新加入的节点还会和其他的节点进行握手,经过一段时间之后,整个集群都能知道这个节点进来了。
    在这里插入图片描述

    槽指派

    Redis集群通过分片的方式来保存数据库中的键值对,集群的整个数据库被分为16384个槽,数据库中的每个键都会在一个槽中,集群中的每个节点可以处理0到16384个槽。只有在所有的槽都有被某个节点处理的时候,集群才是处于上线的状态。

    通过CLUSTER ADDSLOTS <slot> [slot...]命令,可以将一个或多个槽指派给相应的节点负责。

    当一个节点保存自己需要负责的槽之后,还会将负责的信息告知给其他所有的节点。这样,当其他节点收到不属于自己负责槽的键值请求的时候,就会返回一个MOVED错误i将处理该槽的节点IP发给客户端。

    集群中的命令执行

    当所有槽都被指派了,集群就开始上线了,可以接收外界的读写命令。流程如下:
    在这里插入图片描述
    节点数据库的实现

    与单机数据方面的一个区别是,集群节点只能使用0号数据库。

    集群节点还会用跳跃表来保存槽与键的关系,跳跃表每个节点的分值都是一个槽号,每个节点的成员就是数据库键。

    ASK错误

    当集群中需要将节点负责的槽进行转移到其他节点上,就是重新分片。而在重新分片的过程中,如果出现了对正在迁移的那部分槽中的键值进行操作,而且槽已经被迁移走了,那么就会返回一个ASK错误,让客户端去目标节点再去找。
    在这里插入图片描述
    ASK错误和MOVED错误的异同

    • 二者都会让客户端去别的节点上寻找键值对。
    • 产生的原因是不同的,ASK错误是因为节点正在迁移槽,请求槽被重新分片了,而MOVED错误是因为槽的负责权已经转移到了另一个节点。

    复制与故障转移

    集群中的节点角色可以分为主节点和从节点,集群中可以有很多个主节点和从节点。从节点复制主节点数据库,主节点负责处理槽数据。这样可以避免主几点挂了,数据都没了。

    当主节点挂了之后,它的从节点中就会选出一个新的主节点。选举算法和上面的哨兵头领选举算法差不多。

    故障检测
    集群中每个节点都会定期的向其他节点发送PING消息,来检测对方是否在线。如果发现对方下线,同样会采用服从多数的方式来判断节点是否真的下线。

  • 相关阅读:
    Android 程序员必须知道的 53 个知识点
    2017.8.27 考试
    hdu 3118 Arbiter
    UVA 1575 Factors
    [HNOI2008]Cards
    JSOI2008 小店购物
    hdu 2121 Ice_cream’s world II
    poj 3164 Command Network(最小树形图模板)
    [USACO14MAR] Counting Friends
    UVA 10479 The Hendrie Sequence
  • 原文地址:https://www.cnblogs.com/lippon/p/14166417.html
Copyright © 2011-2022 走看看