Skip to content

tzrea1/GroupMembership

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

87 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GroupMembership

Distributed group membership service 分布式组成员服务
Dr-IceCream XingYu WoodyNeVerMind

项目环境

项目搭建在三台腾讯云服务器中,通过Idea编译器的远程连接进行Java开发及测试:

操作系统:Centos 7.6 及 Ubuntu 20.04

JavaSdk:1.8

服务器之间通过公网IP进行通信。

每个服务器上通过运行不同的线程,使用不同的端口来创建出多个虚拟机,在本项目中我们让每个服务器都运行2-3个虚拟机。

组成员算法设计

组成员算法通过heart-heatinggossip机制,实现了成员故障检测以及共享变量组成员列表(加入、离线、宕机)的更新。目前共有6个组成员节点,其中0号节点设置为introducer,我们首先对组成员节点设计了一个圆形的拓扑结构,每个组成员有一个用来存储左右邻居节点编号的List和一个组成员列表。

heart-heating主要用于离线检测:组成员只向在线的左右邻居发送heart-heating,其左右邻居则监听该成员的心跳更新用以检测其是否离线,若离线,它的左右邻居就要找到新的邻居并将离线的邻居节点从自己的组成员列表中删除;当新的成员想要加入组服务时,首先需要将自身信息传给introducer使introducer的组成员列表添加新节点,再从introducer处获得包括自己的组成员列表,并根据此列表找到自身的邻居,开始发送心跳。它的邻居一旦接收到其作为新节点传来的心跳信息,就意味着它的邻居也需要将新节点更新进自己的邻居列表中,这样所有节点就通过neighbor维护了一个正确完整的拓扑结构。

gossip主要用于组成员列表同步:heat-heating机制维护了所有节点的邻居List,并使得部分节点的组成员列表更改,而gossip则是用于同步组成员列表。如上面所说,新节点加入时,只有introducer和新节点的组成员列表是正确的,当节点离线时,只有离线节点的邻居节点的组成员列表是正确的。所以需要gossip将正确的组成员列表广播给所有成员,使大家的组成员列表正确同步。每个组成员通过gossip定时向邻居节点广播自己组成员列表,各个节点在收到信息后,根据收到的组成员列表的时间戳判断是否为最新的正确成员列表,并使自己的组成员列表更新成正确状态。这样当组服务中出现加入或离线情况时,剩余的在线节点能快速同步出正确的组成员列表,并且同步过程中占用的带宽很小。

成员可拓展性

我们通过heart-beating、gossip和introducer共同实现了组成员的动态加入或离线。对于成员数量的上限,以及组成员具体是谁,并没有做任何限制。也就是说只要introducer在线,任意节点都可以随时加入到组成员服务中,成员可拓展性良好。并且由于gossip和heart-heating机制是在拓扑结构上实现的而不是n-n的发送,所以即使组服务中的节点很多,每次通信对组成员的带宽要求仍然很小,提高了组成员服务的承载能力。

同步低延迟性

同步通过gossip进行,不同节点的gossip线程之间是并行的,所以对于我们目前的六个节点,gossip周期为1.8秒来说,最多两个gossip周期就可以使所有组成员节点的组成员列表更新为最新的正确组成员列表。对于更多的n节点,由于为了节省带宽gossip只向各个邻居节点发送组成员列表信息,所以时间开销上略有牺牲,时间复杂度大概为n/4,不过对于节点数不是特别多的情况,低延迟性是完全满足的。

消息格式

为了增强分布式系统的可拓展性,我们需要确保平台相关的字段(如 int)需封装为平台无关的消息格式,例如 Google Protocol Buffers(Protobuf),在此项目中,我们使用的便是Protobuf,借助于此,我们可以实现语言无关、平台无关的消息传输,并且相较于XML、Json格式,使用Protobuf消耗的资源(尤其是带宽资源)也会更少,至于实现,基本的流程为:先编写.proto文件定义数据格式,再使用protoc编译为java的类,最后配置java程序运行时的依赖。

具体而言,我们首先在某个服务器上下载指定版本的protoc编译工具,然后对其进行一系列初始化操作;随后编写.proto文件定义消息格式,并用protoc进行编译,将生成的的类放入我们的项目工程中;最后,在项目工程的pom.xml中添加所需的运行依赖,重新加载项目即可使用封装完成的消息类。

到具体的通信过程中,我们在进行socket通信时,发送时将数据按照消息类提供的方法序列化为字节序列进行发送,接受时进行反序列化为可用的数据,即可完成通信。

日志格式

为方便阅读,日志以xml形式的内容写入到.log文件中,.log文件包括heartbeat.log、gossip.log、join.log、crash.log、offline.log以及introducer节点特有的introducer.log,分别记录不同动作的日志。

日志格式如下(以heartbeat为例):

<heartbeat>                         //动作类别标签(这里是接收心跳)
<changed>false</changed>            //此动作是否改变了组成员列表
<timestamp>1671421880155</timestamp>//触发动作的时间
<object>                            //动作发生的主体
  <name>1</name>
  <ip>公网IP</ip>
  <port>9021</port>
</object>
<memberList>                        //主体目前的组成员列表
  <member>
    <memberName>0</memberName>
    <memberIp>公网IP</memberIp>
    <memberPort>9020</memberPort>
    <memberTimestamp>2022-12-19 11:51:13</memberTimestamp>
  </member>
</memberList>
</heartbeat>

查询集成

本项目与项目一的查询进行了集成,在项目一的基础上给每一个存储虚拟机开启了另外端口,并挂载了组服务的线程,二者在实现上不冲突不干扰。在生成日志方面,客户机在给出查询条件后,各存储虚拟机会将查询结果通过组服务日志线程写到本地的log文件中,实现了查询日志的生成。

网络带宽开销

通信主要发生在heart-heating,gossip和新节点join时,由于节点join不是周期动作,故带宽开销不考虑join的部分。heart-heating和gossip都是按各自周期向邻居List中的节点发送心跳,故对于每个成员节点来说,最多同时发送两条heart-heating信息和两条gossip信息,也最多同时接收heart-heating信息和gossip信息,带宽开销有限,对网络带宽的要求小。

错误比例

在项目中,采用错误比例衡量组成员服务的性能。错误比例越低,则性能越好。

其具体含义如下:

设某一虚拟机在整个生命周期中,相对于完全正确的MemberList,每一台虚拟机存储于本地的MemberList的错误时长为T1,其整个生命周期时长为T2.

错误比例为:*T1 / T2 100%

在这里,我们通过查询全部虚拟机生成的日志文件,获取每一时间段完全正确的MemberList,并与每一台虚拟机所维护的MemberList进行对比。通过时间戳的差值计算出错误比例。

About

Distributed group membership service

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages