节点间连接管理设计
words: 2.1k views: time: 7min问题
在一个拓扑结构中,每个节点都是独立的。如何让每个节点都感知到拓扑中其他节点的信息变化,以及节点之间的连接情况。
思路
每个节点只维护自身的信息,然后通过在节点间设置心跳消息来进行连接检测和信息交换,相互补充和更新,最终在每个节点中都维护一份完整的节点拓扑数据。对于同样的数据,在更新时会以心跳时间最新的为准,所以这也要求了所有的节点在时间上必须是同步的。
数据结构定义
如下为拓扑数据的结构,每条数据包括源节点的全部信息,目标节点的id和地址,以及连接状态和心跳时间。通过源节点id、目标节点id、源节点地址、目标节点地址这四个信息,简单称为四元组,可以确定一个的拓扑连接。通过节点id和节点地址,则可以确定节点信息。
规定每个节点都有一个固定的id标识,由节点自己提供。但是并不要求节点的id唯一,因为节点的地址已经能保证节点的唯一性,所以通过共用id可以对节点进行分组,并以组为单位对外进行暴露,这样同一组内的节点就可以互相作为主备。
另外,我们可以定义一个id为”-1”的虚拟节点,其地址记为”#”,用来作为拓扑中孤立节点和边缘节点(本身不发起任何连接,但可以被其它节点连接)的目标节点,而对于其它节点则不应该指向”-1”节点。
心跳消息定义
上面拓扑数据中的目标节点id其实是来自于心跳响应,对于拓扑中的任何节点,它可以向附近已知的一个或多个节点发送心跳消息。
- 对于心跳请求的定义:
- 对于心跳响应的定义:
连接状态检测
对于心跳的发起节点,它一定是最先知道连接的状态变化,只要心跳失败了,它就会将连接状态改为断开。而对于心跳接收方,它只能根据刷新时间来判断。比如,如果超过五个心跳周期没刷新时间,就将状态改为断开;如果超过十个心跳周期没刷新,就删除对应的连接数据;
数据更新规则
对于连接数据中的心跳时间,只有在收到心跳请求或者响应时才应该进行更新,如果只是更新节点信息则不可以修改心跳时间。
- 对于心跳接收方:
如果节点B收到节点A的心跳请求,则说明节点A到节点B的链路是连通的,所以:
1.刷新当前心跳连接A-B的状态和时间,同时更新A-B连接中源节点A的信息;
2.根据节点id和节点地址更新其它连接(由节点A发起的连接)中的A节点信息,但是不更新连接状态和时间;
3.对于心跳请求中的其它连接数据,根据四元组信息进行比较更新,如果数据的刷新时间比节点B维护的对应数据的时间还旧则忽略。另外,对于上面已经更新了的A-B连接,以及由B作为源节点的连接,也忽略更新,因为节点B自己更了解自己所发起的连接情况。
- 对于心跳发起方:
类似的节点A收到节点B的心跳响应,也说明节点A到节点B的链路是连通的,所以:
1.刷新当前心跳连接A-B的状态和时间;
2.根据节点id和节点地址更新连接(由节点B发起的连接)中的B节点信息,但是不更新连接状态和时间;
3.对于心跳响应中的其它连接数据,根据四元组信息进行比较更新,同样如果连接数据的刷新时间比发起节点A维护的对应数据的时间还旧则忽略。对于已经更新了的A-B连接,以及其他由A作为源节点的连接,也忽略更新。
- 对于孤立节点或边缘节点:
这些节点不会主动发起心跳,最多只会作为心跳接收方,它们会记一条指向节点”-1”的数据,但是也要按照心跳周期来刷新与节点”-1”之间连接的时间,否则那些指向它的其他节点可能得不到更新(因为修改节点信息并不会修改数据时间),而且如果时间不刷新,将会很快被检测为无效链接而被删除。
另外,如果给孤立节点或边缘节点添加了心跳目标,那么应该在心跳成功后删除指向”-1”的连接数据,因为它已经不再符合孤立节点或边缘节点的定义。
小结
对于节点的信息,肯定节点自己最清楚,他可以主动修改自己的信息。其次是直接相邻的心跳节点,他们通过心跳更新对方节点的信息。但是在更新节点信息时并不刷新心跳时间。
对于节点的连接,只有心跳的双方清楚情况,所以只有它们有资格直接更新连接情况,并刷新心跳时间。对于其它节点,只是能在接收数据时根据四元组信息进行覆盖更新,并以时间最新的为准。
示例推演
- 假设有2个独立的节点,节点A[id=10]和节点B[id=20],那么他们会各自记录一条指向”-1”的连接数据
现在让节点B指向节点A发起心跳(假设心跳间隔为30秒),那么在一次心跳之后他们的数据应该如下
- 如果将节点数添加到6个,并将它们直接的连接关系设置成如下
那么经过几个周期之后,它们各自的节点数据应该如下所示
- 假设现在断开节点F与节点E之间的连接
那么节点F将因为心跳失败而发现连接被断开,于是将F-E连接的状态置为断开,而其它的连接数据由于得不到更新,将会在一段时间之后被清除。最终只会剩一条F-E连接数据,这是因为心跳会不停的失败重试并更新时间。对于左侧的节点,也会在一段时间发现F-E连接没有更新而将其删除,所以最终节点中的数据应该如下两个版本
- 如果再断开节点E与节点B之间的连接
类似的,节点E也会很快发现E-B连接被断开。但是在节点B中,E-B连接数据并不会被删除,因为节点E很快会将这个断开的E-B连接数据通过其他节点传给节点B,并且由于失败重试,数据时间会一直刷新,除非节点E主动去除这个心跳目标从而停止尝试。所以这里数据最终会如下所示
- 同理,我们还可以继续断开节点[A、B、C、G]直接的任意一个连接,只要保证节点间还有链路能够连接到,最终就能保证每个节点都有完整的拓扑数据以及最新的节点信息。这里以断开节点B与节点G的连接为例
这样的话,最终数据会变成下面这样
- 进一步假设现在节点G发生宕机
那么其他节点将会很快发现B-G这条链路也断开了,并且由于节点G指向”-1”的连接数据不再更新,将很快被剔除,最终数据变成下面这样
可以看到,虽然节点G在拓扑数据中被剔除了,但是如果以节点id标识作为目标,我们依然有节点A可以访问,这样也就达到了灾备的效果。
参考: