Cassandra源码阅读(7)-Gossip协议

本文介绍Gossip协议在Cassandra中的实现细节

什么是Gossip协议

Gossip是分布式系统中被广泛使用的协议,其主要用于实现分布式节点或者进程之间的信息交换。Gossip协议利用一种随机的方式将信息散播到整个网络中。正如Gossip本身的含义一样,Gossip协议的工作流程即类似于绯闻的传播,或者流行病的传播。
更详细的信息可以查看维基百科页面Gossip_protocol

Cassandra中的实现

在cassandra中gossip相关的处理集中于Gossiper这个类中。发起gossip的方法在Gossiper.gossipTask.run方法中,他是一个runnable,在上层被一个定时任务每秒钟执行一次

节点状态的数据结构

Gossiper.endpointStateMap<InetAddress,EndPointState>保存了当前节点所知道的整个集群的状态,key就是对应节点的IP地址,value是EndPointState。

EndPointState由两部分组成:HeartBeatState和ApplicationState。

HeartBeatState里面有两个成员,generation和version。generation可以理解成当前节点状态大的版本号,默认值是启动时间秒数时间戳,version是小版本号,从0开始。

ApplicationState实际上是一个Map<ApplicationState,VersionedValue>,里面存放着多种应用信息和该信息对应的版本。ApplicationState是一个枚举.

Cassandra中的Gossip协议分3个阶段。SYN,ACK,ACK2。以A向B发送gossip消息来为例:A发送SYN消息给B,B收到SYN消息之后回复ACK消息,A收到来自B的ACK消息之后再回复ACK2消息给B,完成一次完整的消息交换。

下面描述每一个阶段的具体实现细节

SYN

  1. 更新本地节点的HeartBeatState.version,把它加1.
  2. 构造SYN消息。SYN消息包含一个List< GossipDigest > 和集群名字还有集群的分片方式。GossipDigest是一个节点信息的摘要,包含3个字段:节点的地址(InetAddress),Generation(就是heartbeatstate里面的generation),maxVersion(heartBeat的version和applicationState中的versionedValue取最大值)
    构造SYN消息的具体步骤就是把endpointStateMap中的所有endpointState取出来构造一个List
  3. 从活着的节点中(Gossiper.liveEndPoints)中随机选一个,并发送SYN消息。如果liveEndPoints为空,则什么也不做。
  4. 尝试给无法到达的节点发送SYN消息,具体做法是生成一个随机数rnd,如果rnd < 不可达节点数/(存活节点数+1),就在不可达节点中随机选一个发送SYN消息。
  5. 如果第3步没有发出任何消息,或者活着的节点数 < 种子节点数 , 那么给种子节点发消息,算法类似第4步, rnd < 种子节点数/(存活节点+不可达节点)

ACK

B收到A的SYN之后,回复ACK消息给A,具体做法如下:

  1. 排序从A收到的List< GossipDigest > ,和本地的endStateMap做比较,按照Version相差的多少倒序排序,version差的越多排在越前面(也就是这个EndpointState在A和B上相差的越多)。这里只排序A发来的,B有A没有的不做处理
  2. 比较A发来的状态和B已有的状态,产出两个数据:List< GossipDigest > deltaGossipDigestList 和 Map<InetAddress,EndPointState> deltaEpStateMap.

    deltaGossipDigestList存放着A有B没有,或者说A更加新一些的GossipDigest,在下面的情况会放入:
    I. A有B没有
    II. A的Generation比B的大
    III. A的version比B大

    deltaEpStateMap则是存放B有A没有,或者B更新一些的EndPointState,具体判断条件与上面的类似。

  3. 构造ACK消息,ACK消息只包含上面的两个数据,并回复给A。

ACK2

A收到B的ACK消息之后:

  1. 根据deltaEpStateMap更新本地的状态,只有在genration或者version大于本地时才更新。
  2. 根据deltaGossipDigestList从本地取出对应最新的节点状态,构造一个epStateMap<InetAddress,EndPointState>(因为第一次SYN只发送了摘要,没有发送具体的EndPointState)
  3. 构造ACK2消息,ACK2只包含2中的epStateMap,发送给B。
  4. B收到ACK2后更新本地状态。

至此,A与B之间的一轮Gossip信息交换结束.整个过程可以用下图描述:

cassandra_gossip