数据库
首页 > 数据库> > 《Redis 设计与实现》读书笔记(三)

《Redis 设计与实现》读书笔记(三)

作者:互联网

多机数据库实现

十五 、复制

从服务器通过命令 slaveof 127.0.0.1 6000 成为主服务器的从服务器。然后执行复制操作,保持自己的状态和主服务器一样

1.理论

同步

成为从服务器后的同步操作:

  1. 从服务器会发送SYNC命令给主服务器,
  2. 主机会执行bgsave命令,并记录当前的偏移量。
  3. bgsave命令执行期间执行的写命令,都会记录到缓冲区
  4. bgsave命令执行成功后,主机发送RDB文件给从机
  5. 从机加载RDB文件
  6. 主机发送缓冲区的命令给从机
  7. 从机执行缓冲区命令

命令传播

当主从机的状态一致后
主机每次执行写命令,都会通过命令传播的方式,发送给从机
从机执行写命令,这样主从的状态又会一致了。

2.8版本前的缺陷

如果主从之间网络断开,这样主机的写命令就不能通过命令传播发给从机了,这时候主从就不一致了。
当主从重新连接上后,在2.8版本前的做法是重新执行一次同步操作。
如果主从断开前执行了很多命令,断开期间期间主机只执行了几条写命令,重新执行一次同步操作,效率会比较慢。更好的做法是只同步断开期间执行的命令给从机就好了。所以为了优化这个缺陷,2.8后新出了PSYNC命令

新版命令

新版增加了PSYNC命令,这个命令支持完整重同步部分重同步
简单来讲就是重连后,主机会判断当前能不能执行部分重同步,如果可以就做,如果不可以,就执行完整重同步。

其他知识

info replication

127.0.0.1:6811> info replication
# Replication
role:master #当前节点的角色
connected_slaves:1  #从机数量
slave0:ip=127.0.0.1,port=6801,state=online,offset=2095671,lag=0 #从机1信息
master_repl_offset:2095671  #主机的offset
repl_backlog_active:1  #缓冲区是否可用
repl_backlog_size:1048576 #缓存的大小,默认是1M
repl_backlog_first_byte_offset:1047096 #缓冲区第一个字节的offset
repl_backlog_histlen:1048576 #

在主机执行这个命令,可以查看主从复制的情况,包括有多少个从机,偏移量,缓冲区大小等。

2.过程

PSYNC命令实现

主从同步完整流程

心跳

从机每隔一秒会向主机发送心跳命令 REPLCONF ACK <replication_offset>
心跳可以实现功能:

十六、哨兵

哨兵是Redis高可用的一种方案。Redis的架构是一主多从,然后有一个或者多个哨兵进程去监听主服务器的情况。当哨兵认为主服务器已经下线,提升其中一个从服务器为主服务器,然后修改其他从服务器的复制配置。
哨兵的作用类似Mysql的MHA,只是哨兵支持多个,MHA只有一个manager。

1.初始化哨兵Sentinel

哨兵也是一个Redis进程,启动方式是redis-sentinel /config.conf
哨兵进程只能执行哨兵相关的命令,不能执行其他的Redis命令。

数据结构

哨兵配置

port 6711


#监听的存储redis,TestMaster1是redis名称,127.0.0.1是ip,6702 是端口,1是升级为Master的权重


sentinel monitor mymaster 127.0.0.1 6721 1
sentinel down-after-milliseconds mymaster 3000
sentinel failover-timeout mymaster 10000
daemonize yes
#指定工作目录
dir "/data/redis_demo"


logfile "/data/redis_demo/log/sentinel.log"
#redis主节点密码
sentinel auth-pass mymaster 123456

哨兵启动后,初始化后,就会和主服务器建立连接,有两个:

2.获取主从服务器信息

建立连接后,哨兵会每10秒向主服务器发送INFO命令。INFO命令会返回主服务器的所有从服务器信息。这样哨兵就能知道主服务器有多少从服务器了。
然后会新建或者更新主服务器的slaves结构
slaves结构的key是从服务器的ip和端口,例如127.0.0.1:7000,value是sentinelRedisInstance数据结构。

当有新的从服务器,哨兵会像和主服务器建立的连接一样,和从服务器也建立两个连接。

然后会每10向从服务器发送INFO命令。

3.获取其他哨兵信息

哨兵会每个2秒向主和从服务器发送订阅消息,频道是__sentinel__:hello,消息是:PUBLISH __sentinel__:hello "s_ip,s_port,s_runid,s_epoch,m_name,m_ip,m_port,m_epoch"

假如哨兵1发送了这个消息,因为其他哨兵,例如2和3,都会订阅这个频道,所以它们也能收到这个消息,哨兵1自己也会收到。
所以当它们收到这个信息后:

4.判断主观下线

哨兵会每1秒向其他哨兵和主从服务器发送PING命令。其他服务器会返回:

当在down-after-milliseconds时间内,例如是5s,对方连续返回无效回复,例如是5次PING都返回无效回复,哨兵就会把这个服务标记为主观下线,就是把flags值修改为SRI_S_DOWN。

5.判断客观下线

当哨兵判断一个主服务器主观下线后(从服务器不会触发),会向其他哨兵发送命令:

SENTINEL is-master-down-by-addr <ip> <port> <current_epoch> <runid>

分别为主服务器的ip 端口,自己的配置纪元,runid=*号。

其他哨兵接收这个命令后,会返回

上面的 current_epoch run_id leader_runid leader_epoch都是用于选举领头羊哨兵的,在判断客观下线中没有用。

所以总的来说,哨兵判断一个主服务器下线后,会询问其他哨兵,是否也把这个服务器标记为下线,如果有大于等于quorum参数的哨兵投票说主服务器已下线,哨兵会把主服务器标记为客观下线,也就是把flags标记为SRI_O_DOWN

6.选举领头羊leader

当一个哨兵把主服务器标记为客观下线后,就会进入选领头羊leader环节,在多个哨兵中选择一个领头羊哨兵,来执行故障转移操作。

配置纪元问题:

解答

异常情况

Raft算法视频
配置纪元
Raft算法

7.故障转移

成为领头羊leader的哨兵将执行主服务器的故障转移工作

十七、集群

集群通过分片(sharding)来进行数据共享,并提供复制和故障转移功能。

1.节点

一个集群由多个节点组成,一开始这些节点是互相不能感知的。
我们需要通过命令cluster meet <ip> <port>,让节点加入集群。例如在A节点执行meet命令,ip和port是B节点的,这样A和B节点就相互感知了。
通过 cluster nodes命令可以查看当前集群的情况。

127.0.0.1:6812> cluster nodes
1fdfb5833caf8e9cf3b7f1233ce3969e0a324db7 127.0.0.1:6804 master - 0 1572954527331 12 connected 0-1104 5461-5779 11423-12004
72234454d061c86c630e8eb7995e2480fe340b95 127.0.0.1:6803 master - 0 1572954527331 8 connected 12005-16383

启动

节点需要配置 cluster-enabled yes 才会开启集群模式。
集群模式的节点启动后,其他都和单机节点一样的,只会在serverCron函数中增加一个clusterCron函数的调用

集群数据结构

集群增加了3种数据结构

cluster meet命令

然后节点A和B通过Gossip协议,然自己一直的节点认识彼此。

2.槽指派

Redis集群有16384个槽。数据库中每个键都对应这些槽中的一个。每个节点处理0-16384个槽。
只有当全部槽都有节点处理,集群才会进入上线状态。

槽指派命令

cluster addslots 0 1 2

这里把槽 0 1 2 3个槽指派给当前连接的节点。

槽的数据结构

槽的信息存储在clusterNode结构的unsigned char slots[16384/8]。这是一个二进制字符串列表,只有0 1。 如果是1表示这个下标的槽由当前节点处理。还要个numslots记录处理的槽的总数。
在clusterState结构有个 clusterNode *slots[16384]变量用来存储每个槽对应的节点对象。
这样就能实现通过O1复杂度可以

执行cluster addslots命令后,当前节点会把自己负责的槽都同步给其他节点。

当机器所有槽都有节点处理,机器就会进入上线状态

集群中执行命令

通过命令cluster keyslot test 可以查看test这个key属于哪个槽.
如果使用-c集群模式启动客户端,MOVED命令会被隐藏。否则会抛出。

数据库实现

集群模式,的数据库实现和单机模式差不多,不同点:

4.重新分片

重新分片就是把N个槽从节点A迁移到节点B。重新分片过程中,集群是一直在线状态的。

重新分片工作一般是使用管理软件redis-trib负责的
步骤是

  1. 对目标节点发送cluster setslot <slot> IMPORTING <source_id>命令,让目标节点做好导入槽的准备
  2. 对源节点发送cluster setslot <slot> MIGRATING <target_id>命令,让源节点做好导出槽的准备。
  3. 对源节点发送cluster getkeysinslot <slot> <count>命令,获取count个属于槽slot的key
  4. 对源节点发送 migrate <target_ip> <target_port> <key_name> 0 <timeout>命令,将对应的key迁移到目标节点。一条命令只能迁移一个key。

数据结构

客户端请求

因为迁移的过程,机器是一直上线的,所以就会存在问题:迁移过程中,如果客户端操作迁移中的key,怎么办。解决方法就是引入ASK错误。

在迁移的过程中,迁移的slot依然由源节点负责,所以对这个slot的key的操作依然是对源节点发送命令的。

ASK命令

5.复制和故障转移

集群里面有

消息

集群的节点通过消息来进行交流。
发送消息的节点成为发送者
接收消息的节点成为接收者
消息有5种:

消息头
消息头是一个结构,里面包含正文和其他属性

消息正文

  1. MEET PING PONG消息的实现
    1. 正文是两个clusterMsgDataGossip结构的实例
    2. 因为MEET PING PONG3种消息的正文结构一样,所以通过消息头的type来判断是哪种消息
    3. 发送者会从自己已知节点里面随机找两个节点(可以是主或者从)。然后把两个节点的信息保存到两个clusterMsgDataGossip结构里面,有数据
      1. char nodeName[REDIS_CLUSTER_NAMELEN] 节点名称
      2. uint32_t ping_sent 最后一次向该节点发送PING的时间戳
      3. uint32_t pong_received 最后一次从该节点接受PONG消息的时间戳
      4. char ip[16] IP
      5. uint16_t port 该节点端口
      6. uint16_t flags 节点的标识值
    4. 接受者收到这三种消息后,会查看里面的两个Gossip结构,也就是两个其他节点的信息
      1. 如果接受者第一次接触节点,就会向这个节点握手
      2. 如果接受者已接触这个节点,就会更新节点信息
  2. FAIL消息的实现
    1. 消息使用clusterMsgDataFail结构,只有一个变量char nodename[REDIS_CLUSTER_NAMELEN]
    2. 当接受者收到这个消息,就会标识这个节点为下线状态
  3. PUBLISH消息的实现
    1. PUBLISH命令有两个参数,channel和msg,例如publish "channel1" "msg1"
    2. 消息使用clusterMsgDataPublish
      1. uint32_t channel_len channel的长度
      2. uint32_t message_len 消息的长度
      3. unsigned char bulk_data[8] 消息内容,不一定是8字节
      4. 例如上面的例子:bulk_data存储的是channel1msg1。channel_len =8 message_len =4

设置从节点

cluster replicate <node_id>

通过这个命令,可以让接收命令的节点成为node_id的从节点。
接收命令的节点会:

例如节点A标记节点B为疑似下线。然后通过PING PONG命令,节点A会把这个信息同步给集群其他节点。
当节点C收到节点A认为节点B疑似下线。节点C会在节点B的clusterNode结构的fail_reports链表里面添加一个clusterNodeFailReport结构,有变量:

当集群里面半数以上负责槽的主节点都将某个节点标记为疑似下线,那么这个节点会被标记为下线,标记的节点会向集群广播FAIL消息,通知其他节点。

例如这里的节点C,它收到了A节点的报告,同时如果他自己PING节点B也是失败,而且集群里面只有ABC3个负责槽的主节点,那么节点C就会标记节点B位下线,并广播FAIL消息。

故障转移

当集群中其中一个主节点,例如节点B被标记为下线

  1. 那节点B的从节点,会有一个成为主节点,例如节点D
  2. 节点D会执行slaveof no one命令,成为新的主节点
  3. 节点D撤销所有对节点B的槽指派,并将这些槽都指派给自己
  4. 节点D向集群广播一条PONG消息。让其他节点知道自己成为了主节点并接管了节点B的所有槽指派
  5. 节点D开始接受和处理客户端的命令请求,转移完成

选举新节点

  1. 配置纪元是一个自增变量,初始值是0
  2. 当集群某个节点开始一次故障转移时,配置纪元的值会加一
  3. 在一个配置纪元中,主节点只有一次投票机会。它会把票投给第一个要求它投票的节点
  4. 当从节点知道自己的主节点已下线后,会广播一条CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST消息,要求其他主节点为它投票
  5. 如果主节点有投票权(它正在负责处理槽),并且没有投过票给其他节点,那它会给第一个要求投票的节点返回CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息
  6. 每个参选的从节点都会受到ACK消息,如果自己收到的ACK消息大于可投票的节点的半数,这个节点就会成为新的主节点。
  7. 如果在一个配置纪元里面,没有从节点收到足够多的票数(例如3个主节点,挂了一个,剩下2个,2个从节点各自收到一个投票)。那集群就会进入一个新的配置纪元。再次进行选举。
    1. 有点不太明白。怎么进入新的纪元?谁来决定是否进入新的纪元?
    2. 选举算法和哨兵的类似,也是Raft算法

标签:读书笔记,Redis,哨兵,命令,从机,服务器,设计,节点,纪元
来源: https://www.cnblogs.com/Xjng/p/12085114.html