您的位置:首页 > 编程语言 > Go语言

Gossip简介

2010-08-02 20:14 423 查看
Cassandra集群没有中心节点,各个节点的地位完全相同,它们通过一种叫做gossip的协议维护集群的状态。通过gossip,每个节点都
能知道集群中包含哪些节点,以及这些节点的状态,这使得Cassandra集群中的任何一个节点都可以完成任意key的路由,任意一个节点不可用都不会造
成灾难性的后果。

gossip介绍

gossip的学名叫做Anti-entropy(逆熵?),比较适合在没有很高一致性要求的场景中用作同步信息。信息达到同步的时间大概是
log(N),这里N表示节点的数量。

gossip有两种形式:anti-entropy和rumor-mongering。

gossip中的每个节点维护一组状态,状态可以用一个key/value对表示,还附带一个版本号,版本号大的为更新的状态。

消息的处理有3种方式,Cassandra采用第三种方式——Push-pull-gossip

Push-gossipA节点将状态集合发送到B,B通过和本地的状态集合比较,返回
S(A)和S(B)的笛卡尔积
Pull-gossipA发送一个摘要(digest,只包含key和version)给
B,B通过比较,仅仅返回A上需要更新的状态
Push-pull-gossip这种方式和pull-gossip一样,在B发送给A其需要更新的
状态的同时,会向A请求本地过期的状态
关于gossip协议本身更详细的信息可以参考这篇paper


Cassandra的实现

当一个节点启动时,获取配置文件(storage-conf.xml)中的seeds配置,从而知道集群中所有的seed节点。

Cassandra内部有一个Gossiper,每隔一秒运行一次(在Gossiper.java的start方法中),按照以下规则向其他节点发
送同步消息:

随机取一个当前活着的节点,并向它发送同步请求

向随机一台不可达的机器发送同步请求

如果第一步中所选择的节点不是seed,或者当前活着的节点数少于seed数,则向随意一台seed发送同步请求

第一和第二步好理解,通过第一步可以和当前活着的节点同步状态,以更新本地的状态,通过第二步可以尽早发现不可用的节点重新可用了。

第三步中的第一个条件,如果第一步中的节点不是seed,则向随意一台seed发送同步请求也比较好理解,因为seed理论上总是有较多的节点状态
信息。

第三步中第二个条件则有点难理解,当活着的节点数少于seed时,也需要向随机的seed发送同步消息。其实这里是为了避免出现seed孤岛。

如果没有这个判断,考虑这样一种场景,有4台机器,{A, B, C,
D},并且配置了它们都是seed,如果它们同时启动,可能会出现这样的情形:

A节点起来,发现没有活着的节点,走到第三步,和任意一个种子同步,假设选择了B

B节点和A完成同步,则认为A活着,它将和A同步,由于A是种子,B将不再和其他种子同步

C节点起来,发现没有活着的节点,同样走到第三步,和任意一个种子同步,假设这次选择了D

C节点和D完成同步,认为D活着,则它将和D同步,由于D也是种子,所以C也不再和其他种子同步

这时就形成了两个孤岛,A和B互相同步,C和D之间互相同步,但是{A,B}和{C,D}之间将不再互相同步,它们也就不知道对方的存在了。

加入第二个判断后,A和B同步完,发现只有一个节点活着,但是seed有4个,这时会再和任意一个seed通信,从而打破这个孤岛。

关于这个问题更详细的信息可以参考这里


Cassandra的每个节点都有一个实现了IEndPointStateChangeSubscriber接口的订阅者,它负责处理接收到的消
息,该接口包含以下方法:

方法名含义
onjoin有机器加入到集群中
onChange有状态发生变更了
onAlive机器可用
onDead机器不可用
同时Gossiper本身实现了IEndPointStateChangePublisher,该接口包含register和unregister
两个方法,用于添加和删除订阅者。

gossip通信的状态信息主要有3种:

HeartBeatState

ApplicationState

EndPointState

HeartBeatState由generation和version组成,generation每次启动都会变化,用于区分机器重启前后的状态

ApplicationState用于表示系统的状态,每个对象表示一种状态,比如表示当前load的状态大概是这样:(1.2,
20),含义为版本号为20时该节点的load是1.2

EndPointState封装了一个节点的ApplicationState和HeartBeatState

一个节点自身的状态只能由自己修改,其他节点的状态只能通过同步更新。

一次同步的过程
两个节点之间的一次同步过程可以用下图表示:



这里假设192.168.1.1(源节点)决定和192.168.1.2(目标节点)同步,首先源节点向目标节点发送
GossipDigestSynMessage包,这个包包括本机维护的所有节点的状态信息的最新版本摘要,摘要只包含key和version,不包含具
体的value,这样可以减小同步的带宽消耗。

当目标节点收到GossipDigestSynMessage包时,它需要做两件事:

找出收到的消息中比本地版本新的状态,按照版本号差异大小排序,将这些状态的摘要
放入
GossipDigestAckMessage中

找出本地比源节点版本更新的状态,将这些状态
放入GossipDigestAckMessage中

当GossipDigestAckMessage构建完成后,会被发送给源节点

这里按照版本号差异大小排序的原因是每个Message允许发送的状态数量是有限的(参见Gossip.java中的
MAX_GOSSIP_PACKET_SIZE定义),这样可以保证比较老的状态(版本号差异大的)可以优先得到更新。

源机器接收到GossipDigestAckMessage后,使用发送过来的目标节点更新的状态更新本地的状态,这样源节点就获取到了目标节点上
比自己更新的状态。

同时源节点把包含在GossipDigestAckMessage中摘要对应的状态通过GossipDigestAck2Message发送到目标
服务器,目标服务器更新本地的状态,这样目标服务器也获取到了源节点上比自己更新的状态。

完成这样一次同步后,源节点和目标节点上的状态都得到了同步。这中工作方式还是比较优的
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息