# 初探JGroups

此处使用JGroup 4.x版本,基于JDK 8

# 基本概念

JGroup是一个可靠的组播,或者叫组内通讯的工具。(Group Communication)其基本概念包括组(group)和组员(member)。组员可以具体为一个节点,而组可以具体为一个集群。

一个组内的节点可以是在同一个host下,也可以在同一个局域网下,也可以是在WAN中的。一个组员可以是多个组的成员。

在日常使用中,我们会遇到例如集群中某个节点需要向整个集群节点发送信息的场景(比如广播),这个时候JGroup就派上用场了。

# 基本结构

网络传输层之上,JGroup主要有三部分。分别是Building blocks、JChannel和protocol stack。

# Building Blocks

Building Blocks是一个比较上层的概念,也是一个可选项。我们编程时可以绕过Building Blocks直接和JChannel进行交互,也是没有问题的。Building Blocks的设计初衷是将程序设计者从单调乏味的一些create、send、receive、disconnect等JChannel的代码中解放出来。简单的理解Building Blocks就是针对JChannel的一个封装,上层应用可以直接和Building Blocks进行交互。

# JChannel

对于JGroup中的一个组(group),都有一个唯一标识group name(组名)进行识别。映射为JChannel可以理解为,JChannel中名字相同的JChannel集合,抽象为一个组(group)。

应用程序想成为某个组的组员,就需要在程序中创建一个JChannel,并通过组名将JChannel连接到组中。一旦连接成功,组员就可以向组内其他成员发送信息,并接收组内其他成员的信息了。需要注意的是,一个JChannel同一时间内只能连接一个组(也就是说connect到一个name)。如果一个进程想连接到多个组,就必须创建多个JChannel,依次连接。

JChannel在被关闭(close)前,是可以被重用的。也就是说,一个组员可以在JChannel断开(disconnected)后,再次使用JChannel连接到当前组中。

每个JChannel对应一个地址(Address),JChannel中维护了一个所有组员列表的视图(view),因此组员也可以通过指定某个地址的方式,来给指定的组员发送消息。默认情况下组员通过JChannel发送消息会给所有的组内成员,包括自己发送。当组内成员发生改变时,新生成的视图会发送给所有当前组内的所有成员。

# 协议栈(Protocol Stack)

协议栈在一个双向列表中保存了一系列协议(注意不是网络协议)。所有的发送和接收的信息,都需要经过协议栈中的所有层(layer)。这就意味着,每一个协议栈中的layer都有可能针对这个信息进行修改、记录、pass或者drop,或添加一个头元素。

其实这个有点像Netty中的handler,也有点像传统的网络模型。也就是说一个信息在发送前需要经过一系列的处理,例如拆分信息、添加信息头、聚合信息等。

# 基本接口概念

# 地址Address

每个组的成员通过地址来作为组员的唯一标识符。默认的地址实现是org.jgroups.util.UUID,显然是基于当前节点的IP address和port生成了uuid。

需要注意的是,一般uuid的Address不会被显示地使用,通常是在发送信息的时候被作为发送标识,比如发给指定的组员。

通常显示使用的是逻辑名(logical name),也就是程序中可以指定给节点的名字。这样更可读一点。

也可以通过开启TP.use_ip_addrs来使用IpAddressUUIDs,这个操作在原始uuid上加入了随机因子,来避免重启一个组员的时候,由于在同一个address和port而导致使用了相同的标识符。另一个好处是IpAddressUUIDs保存了物理地址,避免了每次信息交换的时候uuid和ipaddress之间的转换。

public interface Address extends Streamable, Comparable<Address> {
    int size();
}

Address中的size主要是为了在实际发送数据时可以计算出序列化的长度。而Comparable接口保证了当组发生分裂或合并时(一个组变成两个组、两个组合并),可以保证生成的view按照需要的顺序进行排列。

# 消息Message

org.jgroups.Message封装了组员之间需要传输的数据。数据通常逻辑上包括以下四个部分:目的地址(dest)、源地址(src)、头(headers)和实际装入的数据(payload)。

2021-09-02T172225

其中源地址和目的地址可以是null。这里需要解释的是,null并不代表实际发送的时候为null。消息最终会经过协议栈Protocol Stack中的每一层(layer)来进行进一步加工。因此在某一步中的null,实际是具有含义的。源地址null需要发送的layer来实际指定当前地址。而目的地址null表示当前信息是发送给所有组员的。

头信息其实是指所有不放在payload的部分。其中包括了很多其他信息,比如Flags。

消息也有MessageBatch功能,意思是批量发送,来避免频繁的同步过程,减少潜在的加锁次数。

// get dest
Message.getDest()
// get src
Message.getSrc()

# View

org.jgroups.View是视图的抽象。View包含一个当前组的组员列表和ViewId。

每个组在创建的时候,组员列表中的第一个组员(或者说第一个创建组的成员,当然两者不一定是相同的,不相同以view中列表第一个为准)被称为coordinator协调人。coordinator负责view的创建。

也就是说,当组中组员发生变化时,每一个组员可以方便地获取coordinator并且从其获取最新的view,而不需要与其他组员交互。

因此,ViewId中含有两部分信息,一个是创建当前view的节点地址,一个是序列号sequence number。二者可以用来标识出一个唯一的ViewId。

# 参考

  • http://www.jgroups.org/manual5/index.html