集群开源软件赏:JGroups

目前我在腾讯主要负责一个服务器端软件的相关开源项目,所以接下来几天的开源内容是最近工作上积累的一些经验和想法,下图中的内容就是我目前主要的工作内容和一些小小的成果。

以下正文:

JGroups介绍

我们在开发集群系统的过程中,往往需要在多个进程间同步很多状态,比如每个服务器进程的负载状况、数据队列长度等等……。还有一些情况,我们需要把不同的进程分类,然后分发不同的通知消息,最常见的是发出一些运维命令,如回写数据、清理缓存……。在游戏服务集群中,需要群发消息的功能更是常见,比如全副广播、玩家在线列表维护……。因此我们往往需要实现“分组”“广播”的功能。在开源软件库中,有一款专门完成这类工作的产品,叫做JGroups。

这是一款Apache基金会下的Java语言开源库,官网地址是:http://jgroups.org/

集群服务中,各节点之间的通信能力至关重要。如果个个节点(进程)无法通信,集群将无法协同工作。——这正是JGroups的目标。在一般的情况下,我们可能会选择使用“消息队列”,比如JAVA就有JMS的消息队列规范,而Apache的开源软件ActiveMQ正是实现JMS规范的著名软件。但是,消息队列在使用的时候会显得太“重”,他需要额外启动的队列进程:存储队列消息,特定的进程间通信接口。对于希望能构建轻量级集群的方案里,部署一堆消息队列进程,然后配置管理他们,都是非常麻烦的。而jgroups则正好填补了这个空缺:它是一个库,使用API接口就好了,无须对协议编程;它让你的业务进程直接有通信的能力。

jgroups本身的通信能力,是基于UDP的,因此也支持使用UDP组播能力。这在轻量的服务集群中非常实用:往往一批服务器都连在同一个交换机上。这种情况下的UDP组播丢包率低,时延也低,而且能有效利用内网带宽。

jgroups自身的架构,和很多开源的通信库一样,是分层的。应用程序使用JChannel构建的使用界面API,以“频道”的模型来使用通信能力。底层则分为多个不同能力的实现层。

组播功能

我们先来看看如何用jgroups实现通信功能。jgroups是一个库,因此完全通过API编程就能让你的进程实现通信能力,无须安装部署任何其他软件。

首先,你需要建立一个频道,并连接进去。这个频道无须额外配置,只要调用代码就可以了。其次,构造一条需要发送的消息,这个消息包括内容、发送方地址、接收方地址。接收方地址标识在对应的频道中的具体接收目标,如果不填就是广播,频道中所有节点都会收到,消息的内容就是byte[],也就是任何的数据类型都可以。最后,注册一个接受消息的接口回调对象,让这个对象也连接到同样名字的频道就可以了。

//加入集群 
JChannelchannel = new JChannel();
channel.connect(“pushserver2”);
//发送消息,参数一是接收方地址,参数二是发送方地址;如果接收方地址是null,表示广播
Message msg= new Message(null, channel.getAddress(), “hello”.getBytes());
channel.send(msg);
//接收消息,Receiver类是实现了ReceiverAdapter接口的 
ReceiverAdapter receiver = new Receiver();
channel.setReceiver(receiver);
channel.connect(“pushserver2”);
//接收回调函数 
@Override
public void receive(Message msg)
{
         byte[]buf = msg.getBuffer();
}

在以上操作的过程中,每个节点(进程)对于“频道”的状态,是在不断变化的。下图表述了这种变化:

除了直接用JChannel来收发消息外,jgroups还提供了一种叫BuildingBlocks的使用方法。这个其实是对JChannel更高级的一种封装。主要分为Message Dispatcher和Rpc Dispatcher。

MessageDispatcher可以直接不显式建频道进行广播和单播,每次调用就完成一次简单的调用。这样就无须编码维护JChannel的状态。RpcDispatcher则更好用,他通过JAVA的反射功能,直接调用目标节点上的方法。他们的使用接口如下:

MessageDispatcher
public <T>RspList<T> castMessage(final Colllection<Address>dests, Message msg, RequestOptions options) //广播 
public <T>T sendMessage(Message msg, RequestOptions opts) //单播 
RpcDispatcher
public <T> RspList<T> callRemoteMethods(
                   Collection<Address>dests,
                   Stringmethod_name,
                   Object[]args,
                   Class[]types,
                   RequestOptionsoptions) throws Exception;

发现功能

在集群系统中,我们除了要给节点发消息,还需要随时了解集群中节点的状况。因此jgourps也提供了对应的接口,让我们了解这些情况。用法就是实现MembershipListener这个接口,其中的回调方法就会由jroups在发生事件时调用。

//状态回调接口 
public interfaceMembershipListener 
{
 voidviewAccepted(View new_view); //集群成员有变化 
 voidsuspect(Object suspected_mbr); //可能有节点挂了 
 voidblock();  //表示发送消息将被阻塞 
 voidunblock();  //表示发送消息将不再被阻塞 
}

状态传递

在集群系统里面,有时候我们不仅仅是通过消息来传递消息,还需要“同步”一些状态。也就是说不是说发一个内容给对方,而是让多个节点获得一致的数据,这个状态是在同一个“组”里面共享的。如果用广播API去做这个事情,自己还需要编写一堆代码。所以jgroups直接提供了一套易用的API来完成这个事。

首先,任何一个节点都可以对任何一个组,发起状态同步的请求。然后,在组中的“最老”的节点,就会有一个回调函数被调用,这个回调函数负责发送状态数据给请求者。状态数据可以是任何类型,是通过一个OutputStream来发送的。当然我们常常会使用JDK的ObjectOutputStream来直接发送一个状态对象。最后,这个状态请求者就会收到一个回调,数据由参数的InputStream传入。这套API的特点是,发送数据和接收数据者并不需要互相知道,而是通过一个组关联起来,这样就避免了复杂的地址管理工作。因为组中的成员可能随时退出和加入,要维持一个公共的数据变得很麻烦。

1. 发起状态请求:

public void getState(Addresstarget, long timeout) throws Exception;

2. 触发某一节点(最老)回调:

public void getState(OutputStreamoutput) throws Exception;

3. 触发请求者的回调:

public void setState(InputStreaminput) throws Exception;

协议

由于jgroups是一个组播的通信库,因此其协议栈包含了其内部支持的功能。这里列举一下:

  1. 消息传输:UDP/TCP/TUNNEL
  2. PING :发现协议,IP多播
  3. MERGE3:合并子集群回归一整个集群
  4. FD_SOC:故障检测
  5. VERIFY_SUSPECT:Double-checks故障节点
  6. BARRIER:状态传输
  7. UFC/MFC:单播、多播流量控制协议

可以看到,为了实现组播,其实底层是需要有很多网络维护工作的,特别是集群中节点故障状态的控制,由于jgroups帮我们做了,所以我们才可以这么简单的完成可靠性如此之高的组播功能

思考

jgrops的功能固然强大,但是让我更感兴趣的是其设计思想,这个产品体现出很多设计上的闪光点:

1. 如何简化集群管理接口。在很多方案中,集群管理库都是使用起来非常复杂的,但是jgroups设计了几个模型,大大简化了使用者需要理解的概念。

a) 使用IP组播或dir地址标识集群

b) 多个功能的集群可以合并到一个集群做管理,区分“组”单位即可

c) 自定义字符串节点组的名字

d) 通知集群变化的回调

e) 基于byte[]和对象的操作API

2. 对于集群环境的启示。我们在实际工作中,集群往往是按需搭建的,因此缺乏统一、有效的集群模型,其中有技术上实现难度的原因,也有思维上缺乏构建模型的原因,但是jgroups让我们发现,集群的管理功能,如状态同步、消息传递、底层通信都可以模块化设计的。

a) 可重用的集群状态管理和通信组件库

b) 隔离底层具体技术实现,而以Channel为单位管理

c) 丰富的非功能需求集成

感谢大家的阅读,如觉得此文对你有那么一丁点的作用,麻烦动动手指转发或分享至朋友圈。如有不同意见,欢迎后台留言探讨。

原文发布于微信公众号 - 韩大(handa1740168)

原文发表时间:2016-01-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏JetpropelledSnake

Python Web学习笔记之TCP/IP、Http、Socket的区别

经常在笔试、面试或者工作的时候听到这些协议,虽然以前没怎么涉及过,但至少知道这些是和网络编程密不可分的知识,作为一个客户端开发程序员,如果可以懂得网络编程的话,...

42815
来自专栏从流域到海域

在微服务之间进行通信

原文地址:https://dzone.com/articles/communicating-between-microservices

1735
来自专栏杨建荣的学习笔记

MySQL的double write和Oracle对比学习(r4笔记第47天)

之前有网友希望我对mysql的double write和oracle能够做一个对比,其实这种对比方式挺好,能够触类旁通,举一反三。不过限于本人水平有限,欢迎拍砖...

2845
来自专栏一英里广度一英寸深度的学习

流式计算

spark是一个大数据分布式的计算框架,有一些并行计算的基础会更容易理解分布式计算框架的概念。对比并行计算,谈三个概念:

672
来自专栏架构师之路

webim如何保证消息的可靠投递

《webim如何保证消息的可靠投递》 上一章和大家分享了webim消息的实时性问题 消息的可靠性,即消息的不丢失和不重复,也是im系统中的一个难点。当初qq在技...

4358
来自专栏Java架构沉思录

单线程的Redis为什么这么快?

https://blog.csdn.net/xlgen157387/article/details/79470556

483
来自专栏CSDN技术头条

Kafka和消息队列之间的超快速比较

本文的目的是让读者快速了解Kafka与消息队列之间的关系,告诉读者为什么会考虑使用它的原因。以下为译文。 Kafka最初是由Linkedin社区开发的一项技术。...

1816
来自专栏LanceToBigData

Spring(一)Spring的第一滴血

前言   开始工作了,但是一进来公司本来是做爬虫和数据分析的,但是走了一个后端的,导致我必须要去顶替他的工作。因为这个项目使用的是Spring、   Sprin...

2146
来自专栏美团技术团队

分布式队列编程优化篇

前言 “分布式队列编程”是一个系列文,上篇《分布式队列编程模型、实战》,主要剖析了分布式队列编程模型的需求来源、定义、结构以及其变化多样性;根据作者在新美大实际...

3534
来自专栏鸿的学习笔记

简单聊聊分布式系统的一致性问题

记得有人说过,分布式系统的所有问题归根结底都是一致性问题。前面文章提到的数据复制,分区以及事务面临的问题都是如何保证数据一致。而分布式系统不同于单机,它不...

601

扫码关注云+社区