scylladb-源码分析-gossip

一、gossip协议介绍

基本原理

Gossip protocol 也叫 Epidemic Protocol (流行病协议),实际上它还有很多别名,比如:“流言算法”、“疫情传播算法”等。

这个协议的基本思想就是:一个节点同步信息到整个集群中的所有节点时,每次只向集群中的几个随机节点发送消息,收到消息的节点也会发送消息给其他随机几个节点,直至整个集群中的所有节点都收到了这个消息。

 

优缺点

优点:

1.扩展性

网络可以允许节点的任意增加和减少,新增加的节点的状态最终会和其他节点一致

2.容错

网络中任何节点的宕机和重启都不会影响Gossip消息的传播,Gossip协议

3.去中心化

无需中心节点,所有节点都是对等的,任意节点无需知道整个网络状况,只要网络连通,任意节点可把消息散播到全网。

4.一致性收敛

消息会以“一传十的指数级速度”在网络中传播,因此系统状态的不一致可以在很快的时间内收敛到一致。消息传播速度达到了 logN。

 

缺点:

1.消息延迟

节点随机向少数几个节点发送消息,消息最终是通过多个轮次的散播而到达全网;不可避免的造成消息延迟。

2.消息冗余

节点定期随机选择周围节点发送消息,而收到消息的节点也会重复该步骤;不可避免的引起同一节点消息多次接收,增加消息处理压力。

 

当然,优缺点都是相对的,我们还是需要了解其原理,以便和其他分布式协议进行比较。如果从CAP的角度出发,gossip协议的特点就是低一致性、高分区容错性、高可用性。

 

gossip类型

传播策略的分类:

1.Anti-Entropy(逆熵,大致意思就是降低混乱程度,也就是使所有节点的状态一致)

每个节点周期性的随机选择其他节点,然后通过互相交换自己的所有数据来消除两者之间的差异。这种方法比较可靠,但开销较大。

2.Rumor-Mongering

每个节点只在有了新的信息后,再周期性的通知其他节点,且只发送新信息,直到所有的节点都知道该信息。这种方法开销小,但可能有小概率不能将信息同步到所有节点。

 

通信方式的分类:

1.Push:每个节点只对外发送信息,收到信息的节点更新状态

2.Pull:每个节点只从其他节点拉取信息,拉取后更新本节点的状态

3.Push&Pull:每个节点对外发送信息,并从收到信息的节点拉取该节点的信息(也就是收到信息的节点会返回信息给发送方)

 

scylladb中的gossip方案

scylladb中使用Anti-Entropy + Push&Pull方案。

每个节点定期(1s)执行以下过程:

1.挑选一些节点

a)随机一个活节点

b)随机一个死节点,主要用于拉起死节点

c)随机一个seed节点

挑选方式可能有变化,大致是这样子。记gossip的发起者为A节点,接收者为B节点

2.A节点向B节点发送gossip_digest_syn消息。消息主要内容是A节点中保存的节点摘要列表(digests),digest包括IP地址以及版本

3.B节点收到gossip_digest_syn后,回复gossip_digest_ack消息给A节点。B节点会通过A节点的digests和本地的比较结果,发送差异信息以及节点摘要列表给A节点。

4.A节点收到gossip_digest_ack消息后,回复gossip_digest_ack2消息给B节点。A节点收到gossip_digest_ack消息后,同步信息到本地,并返回差异信息给B节点。

 

二、源码分析

gossip的相关代码在gms/gossiper.cc中,初始化在main.cc中调用相关接口进行。

初始化过程

main函数中的流程,scylladb初始化过程比较复杂,这里只整理了gossip的相关部分:

 

第一个只是获取了一下gossiper,没有进行什么操作,下一步gossiper.start内容比较多,创建了gossiper实例:

上面的gossiper::run是gossiper的主体方法,start执行完后只是设置了run方法到定时器中,还没有运行起来。

第三步init_gossiper主要是给gossiper传入了cluster_name、seed、配置等信息。

第四步开启了定时器,run开始运行。这是在storage_service中进行了,主要应该是需要先读取一些系统相关的表中的信息后,再运行gossip,所以需要先对storage_service进行一些初始化。

另外,第四步中还有对gossip的消息注册了handler,在gossiper::init_messaging_service_handler方法中,内容如下,前三个对应3各gossip消息的处理,其他的还没有研究在哪里使用:

注册handler
_messaging.register_gossip_digest_syn([] (const rpc::client_info& cinfo, gossip_digest_syn syn_msg) {
        auto from = netw::messaging_service::get_source(cinfo);
        // In a new fiber.
        (void)smp::submit_to(0, [from, syn_msg = std::move(syn_msg)] () mutable {
            auto& gossiper = gms::get_local_gossiper();
            return gossiper.handle_syn_msg(from, std::move(syn_msg));
        }).handle_exception([] (auto ep) {
            logger.warn("Fail to handle GOSSIP_DIGEST_SYN: {}", ep);
        });
        return messaging_service::no_wait();
    })
_messaging.register_gossip_digest_ack([] (const rpc::client_info& cinfo, gossip_digest_ack msg) {
        auto from = netw::messaging_service::get_source(cinfo);
        // In a new fiber.
        (void)smp::submit_to(0, [from, msg = std::move(msg)] () mutable {
            auto& gossiper = gms::get_local_gossiper();
            return gossiper.handle_ack_msg(from, std::move(msg));
        }).handle_exception([] (auto ep) {
            logger.warn("Fail to handle GOSSIP_DIGEST_ACK: {}", ep);
        });
        return messaging_service::no_wait();
    })
_messaging.register_gossip_digest_ack2([] (gossip_digest_ack2 msg) {
        // In a new fiber.
        (void)smp::submit_to(0, [msg = std::move(msg)] () mutable {
            return gms::get_local_gossiper().handle_ack2_msg(std::move(msg));
        }).handle_exception([] (auto ep) {
            logger.warn("Fail to handle GOSSIP_DIGEST_ACK2: {}", ep);
        });
        return messaging_service::no_wait();
    })
_messaging.register_gossip_echo([] {
        return gms::get_local_gossiper().handle_echo_msg();
    })
_messaging.register_gossip_shutdown([] (inet_address from) {
        // In a new fiber.
        (void)smp::submit_to(0, [from] {
            return gms::get_local_gossiper().handle_shutdown_msg(from);
        }).handle_exception([] (auto ep) {
            logger.warn("Fail to handle GOSSIP_SHUTDOWN: {}", ep);
        });
        return messaging_service::no_wait();
    })
_messaging.register_gossip_get_endpoint_states([] (const rpc::client_info& cinfo, gossip_get_endpoint_states_request request) {
        return smp::submit_to(0, [request = std::move(request)] () mutable {
            return gms::get_local_gossiper().handle_get_endpoint_states_msg(std::move(request));
        });
    })

gossiper.handle_syn_msg

上面对初始化的过程做了介绍,流程已经到gossiper::run方法中发送gossip_digest_syn消息了。

另一个节点收到gossip_digest_syn消息后,就要执行该消息的handler了,也就是gossiper.handle_syn_msg方法。

可以看到,这个方法的主要内容就是要判断哪些信息需要在后面的ack和ack2消息中进行pull/push。

 

gossiper.handle_ack_msg

ack2的handle就不细讲了,流程是类似的。

消息内容说明

  • endpoint

标识一个节点,实际就是IP地址

  • heart_beat_state

generation+version,generation指节点启动的时间,version指节点的当前版本,每执行一轮gossip本地节点的version会加1

  • digests

节点摘要,就是endpoint+heart_beat_state

  • versioned_value

value+version,value记录了一些值,值是有对应的version的,用于判断哪个节点上的信息是最新的,以便判断是否需要更新

  • endpoint_state

heart_beat_state+map<application_state, versioned_value>,这个保存了各种状态的信息内容

application_state表示类型,versioned_value保存数据

application_state的取值如下,STATUS应该用于表示节点的在线/下线等状态,LOAD表示节点的负载信息。。。这些信息的更新在其他模块中进行,还没有研究,暂不详细介绍了。更新通过add_local_application_state方法进行,可以搜索该方法来查找代码中使用到的相关模块。

enum class application_state {
    STATUS = 0,
    LOAD,
    SCHEMA,
    DC,
    RACK,
    RELEASE_VERSION,
    REMOVAL_COORDINATOR,
    INTERNAL_IP,
    RPC_ADDRESS,
    X_11_PADDING, // padding specifically for 1.1
    SEVERITY,
    NET_VERSION,
    HOST_ID,
    TOKENS,
    SUPPORTED_FEATURES,
    CACHE_HITRATES,
    SCHEMA_TABLES_VERSION,
    RPC_READY,
    VIEW_BACKLOG,
    SHARD_COUNT,
    IGNORE_MSB_BITS,
    CDC_STREAMS_TIMESTAMP,
    // pad to allow adding new states to existing cluster
    X9,
    X10,
};

syn消息主要就是digests

ack和ack2主要就是digests+endpoint_state,state内容视场景,可能只更新一部分内容,也可能发送全部信息。

故障检测

故障检测模块在类failure_detector中,相关的流程主要是:

1.gossiper的构造函数

调用构造函数进行初始化,设置了cfg中的参数:_fd(cfg.phi_convict_threshold(), std::chrono::milliseconds(cfg.fd_initial_value_ms()), std::chrono::milliseconds(cfg.fd_max_interval_ms()))

注册了event_listerner:fd().register_failure_detection_event_listener(this);

2.failure_detector::report  更新心跳信息

处理ack和ack2消息时,会调用notify_failure_detector,最终调用到failure_detector::report。

操作内容是更新failure_detector::_arrival_samples,保存的内容是所有节点的历史心跳数据。

3.failure_detector::interpret  获取故障判断结果

根据failure_detector::_arrival_samples中的内容计算phi,并与phi_convict_threshold比较,如果超过了就认为节点下线,会调用listener的convict方法。

phi_convict_threshold是配置的一个阈值,默认是8。

 

phi的计算方法:

phi = t / m

t是最近一次心跳和上一次心跳的间隔,心跳就是在report中更新的。m是过去n次的间隔平均值,n取的是SAMPLE_SIZE,定义的是1000。

 

故障检测的是Accrual Failure Detector算法(增量故障检测),但做了简化,计算方法简化了很多。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值