1. Overview
SWIM协议是一个成员协议,帮助系统中的每个结点:
- 维护一个列表,列表存放存活的结点
- 当结点加入或退出时,告知列表中的结点以更新状态
2. SWIM
SWIM即Scalable Weakly-consistent Infection-style Process Group Membership Protocol:
- Scalable: 可伸缩的。之前的成员协议使用Heartbeat来检测成员状态,那么当集群中有N个结点时,每次心跳要发送O(N^2)次,明显结点变多时,系统伸缩性不好。而SWIM只需要发送O(N)次,伸缩性好
- Weakly-consistent: 对于某个时间点,每个结点对于整个集群的认知可能不同,但是最终这些结点的状态会收敛到一致,所以叫弱一致性(使用间接Ping-Pong,传播式Gossip等,强一致性不能保证)
- Infection-style: 感染式/传播式。一个结点会把最新的状态传给自己同伴的子集,然后这些同伴的子集结点会把消息传给各自的同伴子集,如此一级一级传播下去,让整个系统状态一致(这在Gossip协议中有使用)
- Membership: 说明SWIM是成员协议
3. SWIM组件
心跳解决了2个问题:检测结点宕机/错误、维护集群中的同伴列表
SWIM为了解决2个问题,使用了2个模块。
3.1. 错误检测模块
使用了直接探测和间接探测。
假定N1
要检测N2
的状态:
- 首先进行直接探测,
N1
发送ping
给N2
- 假如
N2
返回ack
给N1
,则N1
知道N2
存活,流程结束 - 假如
N2
在规定时间内没返回ack
给N1
,则执行第2步,进行间接探测
- 假如
- 进行间接探测,选择另外一个结点
N_other
,发送ping-req(N2)
请求 N_other
接收到ping-req(N2)
请求后,向N2
发送ping
- 假如
N2
返回ack
给N_other
,那么N_other
返回一个ack
给N1
,此时N1
知道N2
存活,流程结束 - 假如
N2
没返回ack
给N_other
,那么N_other
不会返回N1
消息,N1
会检测到超时,则重新执行第2步
- 假如
- 若重复第2步连续K次超时,则认定
N2
挂掉了
3.2. 传播模块
假如结点状态变化,被探测感知到(例如上述的N2
挂掉了),那么感知到的结点(如上述的N1
)就会多播新消息到自己的所有同伴结点的子集,然后接收到消息的同伴结点会将接收到的消息多播到各自的同伴结点子集,最终导致整个集群状态一致。
这是最初的协议设计,实际上可通过“捎带”进行优化,而不用多播
4. SWIM优化 - 让SWIM更健壮
4.1. 传播模块使用捎带而非多播
多播可通过:IP多播,UDP包多播
然而前者并不是所有平台都支持,后者会有丢包问题(可靠性不好)
传播模块不使用多播,而是通过在检测时的ping
,ack
和ping-req
捎带(piggyback)变化的消息,以实现状态的传递。
4.2. 应用“怀疑机制“到错误检测模块
当结点检测到某个结点可能挂了之后,不是将其从同伴列表移除,而是告知自己和其它同伴结点,怀疑这个结点可能挂了。系统的其它结点依旧可以接受这个结点的消息,假如接收到,就取消怀疑(认为它存活);若经过一段时间后,没收到该结点任何消息,才认为它挂了。
- 如
N1
通过ping
和一系列ping-req(N2)
,都没有收到对应的ack
,则N1
向自己和自己的同伴结点发送suspect(N2)
, 认为N2
可能挂了,但依旧可以接收N2
的消息- 假如在一段时间内,集群中某个结点收到了
N2
的消息,则重新标记N2
是存活的,并传播这个状态 - 假如在一段时间内,集群中所有节点没收到了
N2
的消息(即超时),则标记N2
是死的,并传播这个状态
- 假如在一段时间内,集群中某个结点收到了
4.3. 使用Round-Robin来探测结点
每个结点维护一个结点列表,保存要探测状态的结点。
探测时,不通过随机选节点,而是在这个列表中进行Round-Robin探测,保证了一轮探测下来的时间下限:probe interval * number of nodes in the list