基于流言协议的服务发现存储仓库设计

2017-06-29  本文已影响0人  正常的兔子

前言

随着近年来微服务理论越来越流行,其基础之一的服务发现也越来越受到人们的重视。传统的单点服务仓库受限于不易扩展、容灾麻烦等缺点的考虑已不再适用于复杂的集群系统。目前来说,大多数的成熟服务发现系统采用的是zookeeper、etcd或是consul作为服务存储仓库,如kubernetes用的就是etcd。虽然以上三种组件内部协议和功能侧重点各不相同,但最终都实现了一个具有强一致性、高可用性、分布式的存储系统。对于这样的存储系统,分布式支持服务发现可用水平扩展、高可用性可以保证了服务发现的稳定性,KV系统存储了服务数据,而唯一有质疑的是强一致性。下面我们要探讨一个问题,究竟服务发现是否真的需要强一致性?网上有一篇文章《为什么不要把ZooKeeper用于服务发现》探讨了这个问题。有兴趣的同学可以看一下。
 其实强一致性有两个方面限制了服务发现:

本文介绍了一种基于流言协议的KV存储系统,该系统具有最终一致性、分布式、高可用的特点,可以有效的运用与服务发现系统。

服务仓库工作示意图

流言协议

流言协议是一个去中心化的通信协议,当某节点收到一个新事件时会有以下的操作:

流言协议主要实现了两个功能:节点存活检查和消息传递。常用的检测节点存活技术是心跳协议,即定时发出心跳包来证明自己存活。但这样的心跳协议有很多缺点,首先如何是确保心跳包准确送达目标节点,如果网络问题导致心跳包没有正确送达,会不会误伤健康的节点?这个虽然可以通过协议来解决但同时导致了协议的复杂度。其次心跳协议是一个端到端的协议,如何应用与大规模的集群中?采用星型拓扑结构会带来单点问题;采用多对多拓扑随着节点数目过多带来通讯成本急剧上升;采用环状拓扑会因为同时两处节点故障导致不可用。消息传递同样面临这存活检测遇到上述问题,如何保证数据准确传达和选择通讯拓扑结构?对此流言协议可以很好的解决这些问题。


不靠谱的拓扑结构

"SWIM: Scalable Weakly-consistent Infection-style Process Group Membership Protocol"。这篇论文详细描述了流言协议实现方式:

此文章同时也论证了流言协议两个特点:

流言协议的传播速度

服务发现的KV存储系统

现在我们需要设计一个用于服务发现的KV存储系统,考虑到实践运用场合,该系统应该具备:

我们不难发现分布式、高可用和状态检测这些特性流言协议已经实现了。无论集群的节点数目有多少,每个节点只要付出固定的成本就可以实现快速的存活检测和数据传输。这样我们可以构建这样的模型:

但是这样模型还是有些问题。假设两个不同节点同时对一个Key值的数据进行修改,那对于集群的其他节点来说,究竟应该采用哪个节点的数据?这种情况会导致丧失最终一致性。我们发现仅仅依靠流言协议是无法实现服务发现的,还要做一些其他的工作。
 针对服务发现这个特殊场合,我们不难发现一个特点:每个节点只会主动增减和自己相关的数据。也就是说A节点上服务发生变化只会让A节点增减数据,其他节点只是被动的接收A节点的数据变化。A节点上的数据,A节点具有最终的发言权!为了解决不同节点同时写带来的数据不一致问题,我们加上两条规定:

但这并不能完全解决最终一致性的问题。流言协议不能保证传输的顺序性,也就是说假设A节点先发送了消息a=1,再发送a=2。B节点可能会先收到a=2再收到a=1。消息传输完毕后,我们发现A节点中a=2,B节点a=1,失去了最终一致性。其实解决这种只有唯一写者的顺序不一致问题很简单,只需要对每个消息添加一个版本号就可以。只有收到的消息比当前数据版本号新才修改数据。在这新增两条规则:

到这里看起来已经解决了最终一致性的问题,但是在实践运用场合我们发现这样的问题。如图在T3时刻,A节点发生崩溃重启再加入集群,由于重启时间较短,集群其他节点不认为此节点失活因此保留A节点相关数据。但新启动的A节点并不知道崩溃前的最新版本号,还是从版本号1开始同步数据。这个同步消息自然遭到目前版本是3的B节点的拒绝,至此最终一致性丧失。


节点崩溃导致最终一致性丧失

 解决这个问题有两个思路,一个是实时将节点最新的版本号记录在本地文件或是外部存储器中,这样即使崩溃重启也能恢复最新版本号。另一种方法比较简单,按照之前的原则”节点具有本节点数据的最终发言权“,当A节点发现属于自己节点的数据版本号竟然比自己当前版本号还大时,就立即明白本节点的版本号已经过时了。这样将本节点版本号置为数据版本号+1,再同步数据,就解决了这个问题。如图虽然T4时刻同步失败了,当T5时刻收到B节点转发的数据后,A节点更新了自己的版本号,只后同步数据就OK了。在此新增一条规则:

通过以上的几条规则,我们成功的在流言协议的基础上实现了最终数据一致性,从而完成了基于服务发现KV存储的设计。在实际使用过程中,服务端嵌入KV存储来实时更新集群可用服务,客户端既可以通过嵌入KV存储也可以通过查询嵌入KV存储的DNS服务器来查询服务。


基于流言协议的服务发现框架

代码实现

实现流言协议并不是一件轻松的事情,幸好hashicorp的memberlist组件已经帮我们实现了流言协议主要功能。memberlist的流言传播用的是udp协议。这带来一个问题,众所周知udp是一个一个包传输的,数据信息作为ping或ack的附加载荷受限与udp包的大小,这限制了包的信息的传播速度。幸好服务发现数据量不是很大,udp包也完全满足。但只有一种情况除外,那就是新节点加入集群时需要同步自己全部的数据,此时数据量就可能比较大了。如果只用udp传输协议,可能导致数据同步比较慢。memberlist为此新增了tcp同步的功能,我们可以利用tcp同步的接口一次传输大量数据,加速数据收敛速度。

参考

https://github.com/hashicorp/memberlist
https://github.com/docker/libnetwork
Gossip Protocol
SWIM: Scalable Weakly-consistent Infection-style Process Group Membership Protocol

上一篇下一篇

猜你喜欢

热点阅读