Redis源码思考 - Cluster节点之间的handshake

Redis支持Cluster,那么必然要有一个协议来支撑这些Cluster node之间进行状态数据同步。Redis采用了gossip,关于gossip,网上有很多文章,可以去搜来看。gossip的特点是去中心化,并且尽量减小信息扩散带来的网络消息爆炸。

我理解的gossip,是一种分布式系统状态同步(扩散)的实现思想,我只搜到了的各种文章都是些gossip实现的算法。我没有看到gossip的标准协议,所以Redis的gossip应该是基于gossip思想的私有协议。

好,回到正题。gossip的基础,肯定是先要让不同的cluster node之间建立某种信任关系,这个过程在任何协议交互中几乎都叫做“handshake”。

一个简单的handshake其实非常简单,一个request-ack来回两个消息就可以达到握手的效果。

Redis的实现,其实也是这么简单的。它定义了两个消息PING和PONG,A给B发个PING,B给A回个PONG,就握手成功了。当然,在某些情况下,用MEET消息替代了PING。

不过,真要这么简单,我就不写这篇文章了。Redis有些地方的实现细节,确实需要多看几遍源码才能理解的。我们慢慢来分析。

 

Redis的通信模型

Redis的通信和大多数通信设备软件一样,是分层的,大同小异。

最底层是POSIX SOCKET,当然下面还有操作系统,被封装了,我们不管。Redis实现了一个Connect层,该层封装了socket的概念,并且屏蔽了不同操作系统之间的一些差异,这是个公共模块,凡是要用到socket通信的都可以基于Connect。

Cluster再封装了一层叫ClusterLink,它和ClusterNode关联(也可以不关联,后面会讲),同时提供了消息收发的buf操作。

所以,Redis Cluster之间通信的模型就很清晰了,ClusterNode-Link-Conn-socket,层层上送层层下发。

 

handshake过程详解

了解了上面的通信模型,那么如何打通两个NODE之间的通信通路呢?我们很自然地能想到,首先得要TCP/IP层打通,也就是socket要建立好。这样就行了吗?当然不行,我们的通信模型是要实现业务层面的通信,那么业务层面肯定是要有一个协议交换过程的,这个过程其实就是handshake。

所以,本质上来说,我们的MEET\PING\PONG,这些是业务层的协议过程。和socket得分开看。

我们先想想,如果你来实现这个代码,你的socket会怎么设计呢?至少我最开始看代码的时候想当然地认为它的socket应该是完美对称的,像下面这样:

这样多好,每两个NODE之间创建一个socket,两两连接。完美,简单,合理。

我一开始其实也是抱着这个方案去看的代码(我最初看代码的时候是没有动手启动过Redis Cluster的,就是硬读代码),但是越读代码越感觉不对劲,很多逻辑都不通顺。后来总算抛弃了先入为主的思想,从头理了一遍,慢慢读懂了作者的用意。Redis两个ClutesrNode之间的socket并不是完美对称的,存在两个socket。我们以两个Node节点为例。

每两个NODE之间,存在两个socket来进行通信(注意,这里不包括那些个用于listen端口的socket)。Why??目前我没法解释清楚,看完下面的才行!

好,我们记住每两个NODE之间存在两个socket。下面我们先看看参与handshake过程的三个消息:

CLUSTERMSG_TYPE_MEET - 当你配置了cluster meet命令后,触发的handshake过程发的第一个业务消息就是meet。它发给对端node,告诉本node的存在,并让对端建立对应的node。

CLUSTERMSG_TYPE_PING - ping的主要作用的检查某个node是否还存活着,它和meet很像,但是对端不会保存本地为node。

CLUSTERMSG_TYPE_PONG - pong是meet和ping的回复应答消息。pong可以通告本端的状态,比如本端的node name、ip等。ping收到pong的话,认为节点还活着。

同时,上面三个消息参与gossip过程。它们除去消息头以外,会协议一个gossip section,里面包含本地节点存的若干个node信息。gossip就是这样扩散node状态的,它没有中心节点,所有节点每次随机选几个node发给其它节点,只要算法设计得好,整个cluster中的所有节点都能获取到全量的节点状态信息,实现整网同步。这是主备倒换、vote机制等的基础,后面文章我们再详细分析其实现细节。

 

好,我们继续handshake。

最简单的一个场景,用户在NodeA上面去meet NodeB,通过执行命令行 cluster meet ip port。

NodeA的执行过程如下:

1、解析命令行

2、触发handshake(clusterStartHandshake)。这里面主要做了两件事:创建ClusterNode、置node中的flag为CLUSTER_NODE_HANDSHAKE|CLUSTER_NODE_MEET。需要注意的是,这里并没有创建ClusterLink。同时这里创建的ClusterNode的name是一个随机值(假的name)。

3、ClusterCron会不停滴检查ClusterNode中是否有link为null的,第2步创建的这个肯定就命中了。然后给它创建link(假设这个socket为FD-A1),并且触发connect。

4、connect成功(clusterLinkConnectHandler),向对端发送一个CLUSTERMSG_TYPE_MEET。同时,关联ClusterNode和Link之间的关系。

5、收到NodeB的PONG消息。这时候能获取到NodeB的真实name,所以需要刷新一下。

6、NodeA这一端的handshake完成。

 

NodeB的执行过程如下:

1、在监听端口accept到NodeA的connect连接,生成一个新的socket(假设叫 FD-B1),注意这个socket和上面那个FD-A1是匹配的,是一对儿。NodeA发过来的meet消息,以及给回复的PONG消息,都是基于该socket。另外,这个FD-B1只会对应创建link,不会产生ClusterNODE。node在哪产生呢?答案是收到NodeA的meet消息时。但是一定要记住,这里创建的node并没有绑定FD-B1。这就是上面说的Redis通信模型没有完美对称的根源。

2、NodeB在收到MEET消息后,创建了一个link为null的ClusterNode。下面的流程你肯定也猜到了,和NodeA一毛一样,ClusterCron会遍历到这个node,创建link(注意,这是一个新的socket,假设叫FD-B2),并且触发connect。connect成功后,发送PING消息(注意这里是Ping不是MEET)。

3、收到A回复的PONG,更新本地的nodeb name。

4、NodeB这一端的handshake完成。

 

大家看明白了吗? 两个NODE之间其实是各自主动发起了一次handshake,并且建立了两对儿socket。

如何理解呢?

可以认为,socket有主动和被动之分,或者用在redis代码中提到的概念(incoming conn和outgoing conn)。我倾向于前者,好理解些。

主动socket - 主动发起Handshake过程(发起ping、meet)的一端拥有的socket。这个socket和node绑定。

被动socket - 被动参与handshake过程(回复pong)的一端拥有的socket。这个socket不和node绑定。

 

当NODE需要向其他node主动发送消息时,通过主动socket。被动socket则是被动接收消息,以及回复消息。你可能有疑问了,被动socket不是没有绑定node吗?如果回复消息中需要填充node信息怎么办? 答案是,被动socket只是说没有绑定node,并不代表它找不到node啊。它会通过消息头里面的name,去查找node(clusterLookupNode)。

 

如果让被动socket绑定node,是不是就只需要一对儿socket通信了呢?

这其实也是我很困惑的地方。理论上是完全没有问题的。比如在PONG后面再增加一个消息,这样双方都能握手,并且能获取到对方node的name。

这种不对称的设计还会带来一些看着不那么合理的现象,比如,如果其中一条link故障,而另外一条是好的。结果表现为,A认为B是pfail状态,而B认为A是ok的。 

也许是我考虑多了...

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值