Redis多机数据库的实现

来源:互联网 发布:大学生网络借贷 编辑:程序博客网 时间:2024/06/05 15:34

Redis笔记


多机数据库的实现

复制

在redis中,用户可以通过执行SLAVEOF命令或者设置slaveof选项,让一个服务器去复制replicate另一个服务器,我们称呼被复制服务器为主服务器master,对主服务器进行复制的服务器称为从服务器slave。

进行复制中的主从服务器双方的数据库将保存相同的数据,将这种现象称作数据库状态一致。

旧版复制功能的实现

Redis复制功能分为同步sync和命令传播command propagate

  • 同步操作用于将从服务器的数据库状态更新至主服务器当前所处的数据库状态
  • 命令传播操作则用于在主服务器的数据库状态被修改,导致主从服务器的数据库状态出现不一致时,让主从服务器的数据库重新回到一致状态

同步

当客户端向从服务器发送SLAVEOF命令,从服务器需要复制主服务器,从服务器就先执行同步操作。

从服务器对主服务器的同步操作需要通过向主服务器发送SYNC命令来完成,以下SYNC命令的执行步骤:

  1. 从服务器向主服务器发送SYNC命令
  2. 主服务器执行BGSAVE命令,生成一个RDB文件,使用一个缓冲区记录写命令
  3. 主服务器执行完BGSAVE,将生成的RDB发送给从服务器,从服务器接收载入RDB文件,将数据库更新至主服务器数据库状态
  4. 主服务器将记录在缓冲区的写命令发送给从服务器,从服务器执行,更新至主服务器当前状态。

命令传播

主服务器对从服务器发送执行指令

SYNC是很消耗资源的操作:

  1. 主服务器执行BGSAVE命令生成RDB文件,耗费CPU,内存,磁盘IO资源
  2. 主服务器将RDB文件发送给从服务器,耗费网络资源
  3. 从服务器载入RDB文件,从服务器因为阻塞没办法处理命令

新版复制功能的实现

PSYNC命令具有完整同步和部分同步:

  • 完整同步用于处理初次复制
  • 部分重用同步用于处理断线后重复制情况

部分重同步的实现

部分重同步功能有以下三部分构成:

  • 主服务器的复制偏移量和从服务器的复制偏移量
  • 主服务器的复制积压缓冲区
  • 服务器的运行ID

复制偏移量

执行复制的双方-主从服务器分别维护一个复制偏移量:

  • 主服务器每次向从服务器传播N个字节的数据时,就将自己的复制偏移量的值加上N
  • 从服务器每次收到主服务器出来的N字节数据时,就将自己的偏移量加N

复制积压缓冲区

是由主服务器维护的一个固定长度先进先出队列,默认大小为1MB。

主服务器命令传播时也会向复制积压缓冲区传入写命令。复制积压缓冲区也会为队列中的每个字节记录相应的复制偏移量。

当从服务器重新连上主服务器,从服务器通过PSYNC将自己的复制偏移量offset发送给主服务器,主服务器根据肤质偏移量来决定对从服务器执行那种操作。

服务器运行ID

除了复制偏移量和复制积压缓冲区,还需要服务器运行ID:

  • 每个Redis服务器,都由自己的运行ID
  • 运行ID在服务器启动时自动生成,由40个随机十六进制字符组成

当从服务器对主服务器进行初次复制时,主服务器将自己的运行ID传送给从服务器,从服务器会将这个运行ID保存起来。

当从服务器断线重新脸上一个主服务器,从服务器将向当前连接的主服务器发送之前保存的运行ID:

  • 从服务器保存的运行ID和当前的主服务器运行ID相同,说明从服务器断线前后连接的是同一个主服务器,祝福我武器可以尝试执行部分重同步操作
  • 相反,运行ID不同,主服务器将对从服务器进行完整重同步操作

PSYNC命令实现

PSYNC实现

复制的实现

设置主服务器的地址和端口

salveof 192.168.0.1 6379

建立套接字连接

从服务器创建的套接字能成功连接到主服务器,从服务器将为套接字关联一个文件事件处理器。

主服务器在接收从服务器的套接字连接后,为套接字创建相应的客户端状态,并将从服务器看作是一个连接到主服务器的客户端来对待,这时的从服务器具有服务器和客户端两个身份。

发送PING命令

从服务器成为主服务器的客户端后,向主服务器发送一个PING命令

PING的作用
- 检查套接字读写状态
- 检查主服务器能否正常处理命令请求

身份验证

根据从服务器设置的masterauth选项,判断是否进行身份验证

发送端口信息

身份验证后,从服务器执行replconf listening-port<port number>

同步

从服务器向主服务器发送PSYNC命令,执行同步操作,并将数据库更新至主服务器数据库当前状态

命令传播

完成同步之后,主服务器进入命令传播阶段

心跳检测

命令传播阶段,从服务器默认会以每秒一次的频率,向主服务器发送命令:

REPLCONF ACK <replication_offset>

replication_offset是从服务器当前的复制偏移量

  • 检测主从服务器网络连接状态
  • 辅助实现 min-slaves选项
  • 检测命令丢失

Sentinel

Sential是Redis的高可用解决方案:一个或多个Sentinel实例组成的Sentinel系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器。并且在被监视主服务器进入下线状态时,自动将下线主服务器属下某个从服务器升级为新的主服务器,代替已下线主服务器继续处理命令请求。

初始化服务器

Sentinel本质上只是一个运行在特殊模式下的Redis服务器,但Sentinel不适用数据库,初始化时也就不会载入RDB或者AOF文件

初始化Sentinel状态的masters属性

Sentinel状态中的masters字段记录了所有被Sentinel监视的主服务器的相关信息:

  • 字典的键时被监视主服务器的名字
  • 字典的值则是被监视主服务器对应的sentinel.c/sentinelRedisInstance结构

每个结构代表一个被Sentinel监视的Redis服务器实例,这个实例可以是主服务器,从服务器,或者另外一个Sentinel

创建连向主服务器的网络连接

初始化Sentinel的最后一步是创建连向被监视主服务器的网络连接,Sentinel将成为主服务器的客户端,可以向主服务器发送命令,并从命令恢复中获取相关的信息。

Sentinel创建两个连向主服务器的异步网络连接:

  • 一个是命令连接,专门用于向主服务器发送命令,并接受命令回复
  • 一个是订阅连接,专门订阅主服务器的sentinel:hell0频道

重点回顾

  • Sentinel通过向主服务器发送INFO命令来获得主服务器属下所有从服务器的地址和信息
  • Sentinel以每十秒一次的频率向被监视的主从服务器发送Info命令,当主服务器下线或者正在对主服务器进行故障转移时,sentinel向从服务器发送INFO的频率改为每秒一次
  • 监视同一个主服务器和从服务器的多个Sentinel来说,会以每两秒一次的频率向sentinel:hello频道发送消息向其他Sentinel宣告存在。
  • Sentinel与主从服务器创建命令连接和订阅连接,sentinel与sentinel创建命令连接。
  • Sentinel以每秒一次的频率向实例发送PING命令,根据PING命令的回复判断实例是否在线。
  • 当Sentinel讲一个服务器判断为主观下线时,它会向同样监视这个主服务器的其他Sentinel进行询问,看它们是否同意这个主服务器已经进入主观下线状态
  • 当Sentinel收集到足够多的主观下线投票时,会将主服务器判断为客观下线,并针对主服务器进行故障转移。

集群

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

节点

一个Redis集群通常由多个节点组成,在刚开始的时候每个节点是独立的。

连接各个节点可以使用CLUSTER MEET命令,可以让node节点与ip和port所指定的节点添加到node节点当前所在的集群中。

启动节点

Redis服务器在启动时根据cluster-enabled配置选项是否为yes来决定是否开启服务器的集群模式

集群数据结构

clusterNode结构保存一个节点的当前状态,创建时间,名字,配置纪元,IP地址,端口号。

CLUSTER MEET命令的实现

通过向节点A发送CLUSTER MEET命令,客户端可以接收命令的节点A将另一个节点B添加到节点A所在的集群里:

CLUSTER MEET <ip> <port>

槽指派

Redis集群通过分片的方式来保存数据库中的键值对:集群的整个数据库被分为16384个槽,数据库中的每个键都属于这16384槽中的一个,每个节点可以处理0个或最多16384个槽。

在集群中执行命令

对数据库中的16384(15位)个槽进行了指派后,集群就会进入上线状态,这时客户端就可以向季群忠的节点发送数据命令了。

计算键属于哪个槽

节点使用以下算法来计算给定键key属于哪个槽

def slot_number(key):
return CRC16(key) & 16383

计算槽是否由当前节点负责处理

当节点计算出键所属的槽i之后,节点就会检查在clusterState.slots数组值得项i,判断键所在的槽是否由自己负责。

节点数据库的实现

节点和单机服务器在数据库方面的一个区别是,节点只能使用0号数据库,单机Redis服务器没有这个限制。

ASK错误

在重新分片期间,源节点向目标节点迁移一个槽的过程中:属于被迁移槽的一部分键值对保存在源节点里面,而另一部分键值对则保存在目标节点里面。

节点收到一个请求key的命令,key所属的槽i指向该节点,节点会尝试在自己的数据库里查找key,如果没找到,会检查clusterState.migrating_slots_to[i],看所属槽i是否正在迁移,正在迁移的话会向客户端发送一个ASK错误,引导客户端到导入的节点去查找key。

ASK错误和MOVED错误的区别

  • MOVED错误代表槽的负责全已经从一个节点转移到了另一个节点:在客户端收到关于槽i的MOVED错误后,客户端每次遇到关于槽i的命令请求时,都可以直接将命令请求发送至MOVED错误所指向的节点,因为该节点就是目前负责槽i的节点

ASK错误只是两个节点在迁移过程中使用的临时措施:在客户端收到关于槽i的ASK错误后,客户端只会在接下来的一次命令请求中将关于槽i的命令请求发送至ASK错误所指示的节点,但这种转向不会对客户端今后发送关于槽i的命令请求产生任何影响,客户端仍然会将关于槽i的命令请求发送至目前负责处理槽i的节点,除非ASK错误再次出现。

复制与故障转移

Redis集群中的节点分为主节点和从节点,主节点用于处理槽,从节点用于复制某个主节点,并在被复制的主节点下线时,答题下线主节点继续处理命令请求。

故障转移

当一个从节点发现自己正在复制的主节点进入了已下线状态时,从节点将开始对下线主节点进行故障转移:

  1. 复制下线主节点的所有从节点里面,会有一个从节点被选中。
  2. 被选中的从节点会执行SLAVEOF no one命令,称为新的主节点。
  3. 新的主节点会撤销所有对已下线主节点的槽指派,并将这些槽全部指派给自己。
  4. 新的主节点向集群广播一条PONG消息,这条PONG消息可以让集群中的其他节点立即知道这个节点已经变成主节点,这个主节点已经接管了原本由已下线节点负责处理的槽。
  5. 新的主节点开始接收和自己负责处理的操的有关的命令请求,故障转移完成。
选举新的主节点

集群选举主节点的方法:
1. 集群的配置纪元是一个自增计数器,它的初始值为0.
2. 当集群里的某个节点开始一次故障转移操作时,集群配置纪元的值会被增一。
3. 对于每个配置纪元,集群里每个负责处理槽的主节点都有一次投票的机会,而第一个向主节点要求投票的从节点将获得主节点的投票。
4. 当从节点发现自己正在复制的主节点进入已下线状态时,从节点会向集群广播一条CLUSTERMSG_TYPE_FALLOVER_AUTH_REQUEST消息,要求所有收到这条消息,并且具有投票权的主节点向这个从节点投票。
5. 如果一个主节点具有投票权,并且这个主节点尚未投票给其他从节点,那么主节点将向要求投票的从节点返回一条CLUSTERMSG_TYPE_FALLOVER_AUTH_ACK消息,表示这个主节点支持从节点称为新的主节点。
6. 每个参与选举的从节点否会接收CLUSTERMSG_TYPE_FALLOVER_AUTH_ACK消息,并根据俄自己受到多少条这种消息来统计自己获得了多少主节点的支持。
7. 如果集群里有N个具有投票权的主节点,那么当一个从节点收集到大于等于N/2+1张投票时,这个从节点就会当选为新的主节点。
8. 因为在每个配置纪元里,每个具有投票权的主节点只能投一次票,具有支持票的从节点只会有一个,确保了主节点只有一个。
9. 如果在一个配置纪元里面没有从节点能收集到足够多的支持票,那么集群进入一个新的配置纪元,并在此进行选举,知道选出新的主节点为止。

消息

MEET消息:发送者向接受者发送MEET消息,请求接受者加入到发送者当前所处的集群里面。
PING消息:集群里每个节点默认每隔一秒就会从已知节点列表中随机选出五个节点,然后对五个节点中最长时间没有发送PING消息的节点发送PING消息,以此来检测被选中的节点是否在线。
PONG消息:当接收者收到发送者发来的MEET消息或者PING消息时,为了向发送者确认这条MEET消息或者PING消息已到达,接收者会向发送者返回一条PONG消息。另外,一个节点也可以通过向集群广播自己的PONG消息来让集群中的其他节点立即刷新关于这个节点的认识。
FAIL消息:当一个主节点A判断另一个主节点B已经进入FAIL状态时,节点A会向集群广播一条关于节点B的FAIL消息,所有收到这条消息的节点都会立即将节点B标记为已下线。
PUBLISH消息:当节点接收到一个PUBLISH命令时,节点会执行这个命令,并向集群广播一条publish消息,所有接受到这条消息的节点都会执行相同的publish命令。

消息头

节点发送的所有消息都由一个消息头包裹,消息头除了包含消息正文之外,还记录了消息发送者自身的一些信息,因为这些信息也会被消息接受者用到,所以严格来讲,我们可以认为消息头本身也是消息的一部分。

0 0
原创粉丝点击