* 本文讲哨兵模式按照配置运行起来之后 哨兵 master slave 之间连接建立过程. 我觉得了解了建立过程以及正常运行时的一个连接拓扑 对了解整个监视过程非常有帮助。因为之后的故障转移就是继续维持一个这样的拓扑。
1 假设有三个master以及各自的两个从节点: m1(r1, r2), m2(r3, r4), m3(r5, r6) 这里为了方便展示 将主节点的从节点画在一起
2 现在启动三个sentinel s1 s2 s3, 每个sentinel实例都监视m1(r1, r2), m2(r3, r4), m3(r5, r6). 我们先看s1与 m1(r1, r2), m2(r3, r4), m3(r5, r6) 建立连接实现监视的过程。
1) 当 redis 以 sentinel 模式启动以后,会创建一个 struct sentinelState 结构的全局实例 sentinel,也就是s1.这个结构存放为监视的m1(r1, r2), m2(r3, r4), m3(r5, r6) 而创建的 struct sentinelRedisInstance 实例.
2) 假设sentinel.conf 中配置的监视 m1,m2,m3 三个master (s2, s3也如此)
sentinel monitor m1 127.0.0.1 6379 2
sentinel monitor m2 127.0.0.1 7379 2
sentinel monitor m3 127.0.0.1 8379 2
sentinel 启动时会首先调用 createSentinelRedisInstance 创建三个sentinelRedisInstance实例用于处理与m1,m2,m3的连接,起名为 s1-m1,s2-m2,s3-m3.这三个实例已经分别保存了m1,m2,m3的 ip及port 以及一些必要的数据结构 这里不展开
3) 当s1-m1,s2-m2,s3-m3 实例创建成功以后并没有与m1,m2,m3建立连接而是只是准备好了处理m1,m2,m3连接的结构,紧接着 sentinel 会在每个tick中调用 sentinelTimer, 这那里遍历他们,然后分别开始建立与m1,m2,m3的连接。过程是一样的,这里以s1-m1 与 m1(r1,r2)建立连接的过程为例
首先 s1-m1 与 m1 建立两条连接,分别是 link->cc, link->pc. 通过link->cc 向m1 发送 info 命令 和 ping 命令, 通过 pc 向m1 注册 SENTINEL_HELLO_CHANNEL "__sentinel__:hello" 频道并定时发布消息,建立完连接之后的结构如下:
注:为了方便s1-m1 与 m1建立过程中没有画出m1,m2,m3 的副本 r1-r6
以上s1-m1 与 m1 之间的连接是双向的,由于连接是异步的,在连接建立时已经注册好了回调函数。创建link->cc连接时,注册了 sentinelLinkEstablishedCallback 以及 sentinelDisconnectCallback 并且马上向m1 发送ping
创建 link->pc 时 首先注册 __sentinel__:hello 频道,并且设置回调函数 sentinelReceiveHelloMessages。此时 m1 如果没有hello频道会创建该频道。注意sentinel 创建的sentinelRedisInstance 只与master及slave才会创建link->pc 连接, 不会与其它sentinel创建
也就是说只有master和slave上有 __sentinel__:hello 频道, sentinel实例身上是没有 __sentinel__:hello 频道的。
4) s1-m1 与 m1建立两条连接之后 同样是在同一个 tick 里 sentinelTimer里 会向 m1 通过link->cc 发送 info 和 ping 命令,通过 link->pc 向 m1 publish hello消息. sentinel 正是通过处理 m1 收到这三条消息之后传回来的信息建立与m1的副本及其他sentinel之间的连接关系的。当然目前阶段还没有其他sentinel(s2, s3)参与,所以s1在当前阶段通过info消息处理m1的副本r1,r2的连接.
info: (此处注明,处理m1回传信息中的副本slaves 只是info处理函数得一部分功能,之后的故障迁移也会通过该处理函数处理) 当m1 首次收到 s1-m1的info消息后,返回给s1的消息里带有m1当前slaves(r1, r2)的ip 端口等信息。sentnel的info回调函数正是通过这些信息处理的。处理步骤如下:
11) info的回调函数 sentinelInfoReplyCallback 被调用 根据info信息中的slave字段,找到slave 的ip 和 port
22)检查s1-m1 中是否创建了处理r1和r2对应的sentinelRedisInstance实例,如果没有创建则创建,这样在本次回调处理后s1-m1中会创建两个处理r1,r2的实例 叫做s1-r1,s1-r2.
原谅这个糟糕的图,表达的东西应该是没错...
s1 通过s1-m1发送给m1的info信息的回调创建s1-r1,s1-r2实例的过程 与 刚开始s1创建s1-m1,s1-m2,s1-m3的过程类似,都是只创建实例没有创建连接,连接由每个tick 调用sentinelTimer 去创建。
由上图可以发现,s1-r1,s1-r2 属于s1-m1实例,实际上s1-m1实例中有一个map用来存放s1-r1,s1-r2实例,与m1(r1,r2)对应.同时还有一个sentinel map 同来存放其他监视m1的sentinel(s2,s3).
s1-r1,s1-r2实例在sentinel info回调创建完之后,在sentinelTimer 中去创建于 副本r1,r2的连接,连接同样是两个类似于s1-m1和m1之间的两条连接,这里不再赘述,连接创建完之后如下:
注: s1-r1和s1-r2所在的红色框只是逻辑上的属于s1-m1中的一个map 别无他意,同理之后的s1-m1中的sentinel map亦如此
5) 至此,以s1-m1与m1为例建立连接的过程结束. 之后再每个tick 里 s1-m1:m1, s1-r1:r1, s1-r2:r2 之间会按照条件一直发送 info , ping, 发布hello, 来保持连接,从而实现s1 监视m1(r1, r2).
同理 s2-m2(r3, r4) 与 m2, s3-m3 与 m3(r5, r6) 的建立过程与 s1-m1 与 m1 建立过程是一样的.
注: 因为我们是以 s1-m1 与 m1(r1, r2) 建立连接为例展开的,实际上由于是异步,s2-m2 与 m2(r3,r4),s3-m3 与 m3(r5,r6) 连接建立不一定等到 1-m1与m1(r1, r2) 全部建立连接结束之后才开始,可能会穿插这进行,但最后都会达到收敛状态
即连接全部建立完。
下面附上s1 与m1(r1, r2), m2(r3, r4), m3(r5, r6)全部建立完连接之后的结构:
3 以上是s1 监视m1(r1, r2), m2(r3, r4), m3(r5, r6) 后的结果并没有其他sentinel(s2, s3) 加入监视。
现在s2 也开始监视m1(r1, r2), m2(r3, r4), m3(r5, r6) ,相对于s2 本身它自己与m1(r1, r2), m2(r3, r4), m3(r5, r6)连接的过程以及方式以及建立之后的结果与s1 是一样的
但是 s2 加入之后对 s1 的结构会有影响,同时,当 s2 建立好与 s1 一样的结构之后,同样会感知到s1,此时s1 对 s2 结构的改变与s2对s1结构的改变是一样的。
从 s2 的加入对s1 结构的改变开始讲起.
3.1 我们前面讲到当s1完成与 m1(r1, r2), m2(r3, r4), m3(r5, r6) 建立连接之后 在 m1(r1, r2), m2(r3, r4), m3(r5, r6)的各个redis实例上边都建立了 __sentinel__:hello 频道 并且 s1 的各个 sentinelRedisInstance 实例都会定期发送 publish 消息,
同样当 s2 中的实例与 创建完s2-m1, s2-m2, s2-m3 实例 并且与 m1,m2,m3建立连接之后 也会向 m1,m2,m3 publish 消息. 那么此时 s1 会受到s2 中的实例向m1,m2,m3的hello频道发的消息,其实s1 和 s2 通过对应的hello频道的消息感知对方的存在。我们先来看实 例publish 了什么 再看收到这些publish消息后是如何处理的.
publish: s1 通过各个sentinelRedisInstance实例 向master及其副本publish消息,这个过程是在tick时完成的。publish的消息含有8个字段
分别是,s1所在的redis 实例本身的ip,port,myid,及s1此时的curent_epoch 这是前四个字段,另外四个字段:如果当前实例是master实例如s1-m1则直接发送其存储的m1的ip和port以及s1-m1的名字和其当前的config_epoch.如果当前实例是s1-m1的副本实例s1-r1或者s1-r2则找到->master 即s1-m1发送同样的字段。s1-m1 publish 到 m1, s1-m2 publish 到 m2, s1-m3 publish 到 m3。当只有s1完成监控时,各个被监控的redis实例的hello频道只有s1创建的对应的实例完成注册。
当 s2 监听过程执行到类似s1 的下面过程后(将下图中s1 改成 s2)
下一个tick, 分别开始创建与m1,m2,m3的连接,并在连接建立完成之后向m1,m2,m3 publish消息,此时,由于s1中的各个实例已经注册了hello的回调,并且参数就是自己实例本身,因此当s2 的 s2-m1,s2-m2,s2-m3实例 分别向m1,m2,m3 publish 消息后,对应s1会收到通知,处理s2 publish的消息,由s1-m1, s1-m2, s1-m3 分别处理。
注: 此时的s1结构是上边创建完成连接之后的结构,这里为了显示简便,没有全部画出
当s2 完成了创建s2-m1, s2-m2,s2-m3,并在下一个tick创建与m1,m2,m3的连接后,分别如上图所示 publish:msg1, msg2, msg3到m1,m2,m3
msg1, msg2, msg3 三个消息的前四个字段是相同的,都是s2所在服务器实例的ip,port,myid,及s2此时的curent_epoch,之后的四个字段是各自s2-m*实例的名字以及各自的config_epoch以及所连接m1,m2,m3的ip, port。
publish 到m1,m2,m3后 s1中的回调会分别用 s1-m1,s1-m2,s1-m3 处理各自收到的msg1,msg2,msg3。
现在再回到s1,s1 所在服务器通过调用回调函数 sentinelProcessHelloMessage 处理连接上的数据,即s1-m1,s1-m2,s1-m3各自的 link->pc 连接处理msg1, msg2, msg3.
现在只看回调通过s1-m1处理msg1.其他的跟此一样.
首先通过msg1消息中的mastername也就是s2-m1的名字来找到s1中的对应的s1-m1实例,因为当不同sentinel监视同一个master实例时,创建的对应的sentinelInstance实例的名字必须是一样的
例如 sentinel monitor m1 127.0.0.1 6379 2. s1-m1.s2-m3.实例的名字都是 "m1".
找到了s1-m1之后,便根据msg1中过的s2的ip和port和s2所在服务器市里的runid(myid) 查找s1-m1是否创建了处理s2的实例s1-s2,如果没创建,就创建一个. 第一次当时是没创建所以还在s1-m1的sentinel map中创建一个s1-m1-s2实例
然后再下一个tick 与 s2 所在的服务器实例连接,之前的s1,s2的各个实例创建的连接逻辑上是归各自实例的,但是s1-s2创建的连接逻辑上就是与s2 所在的服务器直接相连的
注,上图s1-s2所在红圈中表示在之前基础上添加的新实例
注意s1-m1-s2实例与s2服务器所在实例只有一条link->cc连接 没有link->pc连接。以上表示s1-m1处理msg1之后的与s2连接建立过程,同理s1-m2,s1-m3与此过程相同,因此当s1中的master实例分别处理完s2的master实例发布的消息之后结构变成
注: 下边图就是上边的s1 部分 结合着看
当s1 处理完s2发来的msg1,2,3 之后,s1的每个master实例中的两个map 一个slave map 一个sentinel map 里分别有了 {s1-r1, s1-r2}, {s1-m1-s2} 实例,如果之后的s3 以同样的方式开始监视
m1(r1, r2), m2(r3, r4), m3(r5,r6).则s1 的master实例中的sentinel map里会继续增加一个 {s1-m1-s2, s1-m1-s3}.
这里有一个细节,上图中,s1 的 三个master实例中的sentinel map 中都会有s*-m*-s2, 也就是会有三个连接去连 s2 所在的服务器实例,当前版本的redis烧饼模式实现认为这是多余的,因此,如果当s1 中的任何master实例在处理其msg*的消息中创建了s*-m*-s2 之后,其他的master实例处理自己的msg*消息时,首先会查找有没有其他master实例已经创建了对应的sentinel实例,有的话就共享已经创建的sentinel实例中的link->cc连接. 因为例如s1 当其创建完一个sentinelInstance实例后,在下一个tick中会为这个实例创建连接。当其处理msg*消息时,会先检查其他master实例是否已经创建的sentinel实例,有的话,就直接把当前sentienl实例的link->cc连接直接用已经创建的sentienl实例的Link->cc赋值,这样的话,哨兵s1中的所有处理s2的哨兵实例公用一个连接,这样就节省了大量的连接创建.
这样一来上图左边的s1-m*-s2的三条连接实际上只有一条,他们共享最快与s2建立连接的sentinel实例的link->cc 与s2通信。用官方的话讲,加入5个哨兵 每个都监视同样的100个master 那样的话 所创建的sentinel 实例一共只会创建5条和5个哨兵的连接 而不是500条。
到现在为止,s1 处理完了当 s2 也加入监视行列之后 对自己结构的影响,同理s2 处理s1 对自己的结构影响也是一样的,这时候如果s3 也加入监视,建立完连接之后,整个系统就会达到收敛状态,s2,s3的结构以及与m1(r1, r2), m2(r3, r4), m3(r5,r6) 的连接与s1的是一样的 这里就不在赘述 就是照着s1 在整张图上补充上 s2, s3 的结构和连接 就是整个的收敛状态.
*******另外这里补充一下。本文举得例子s1,s2,s3 三个哨兵指的是 sentinelState 实例,当redis以哨兵模式启动时,会直接创建一个全局 sentinelState。而 这些实例所在的 server 实例就是redis服务器。如果上边没有说清,这里补充一下 希望不要搞混。
同时也要再强调一下,因为哨兵中创建完实例之后 在每次tick 中创建连接,发送消息是异步的,所以整个连接顺序可能不会是上边讲的那样,可能会交叉着连接,但是正常的话都会达到一个收敛状态
有错误欢迎及时指正!!!