目前业界流行的分布式消息队列系统(或者可以叫做消息中间件)种类繁多,比如,基于Erlang的RabbitMQ、基于Java的ActiveMQ/Apache Kafka、基于C/C++的ZeroMQ等等,都能进行大批量的消息路由转发。它们的共同特点是,都有一个消息中转路由节点,按照消息队列里面的专业术语,这个角色应该是broker。整个消息系统通过这个broker节点,进行从消息生产者Producer到消费者Consumer的消息路由。当然了,生产者和消费者可以是多对多的关系。消息路由的时候,可以根据关键字(专业的术语叫topic),进行关键字精确匹配、模糊匹配、广播方式的消息路由。

  简单来说,一个极简的分布式消息队列系统主要的构成模块有:

  Broker:简单来说就是消息队列服务器实体。

  Producer:消息的生产者,主要用来发送消息给消费者。

  Consumer:消息的消费者,主要用来接收生产者的消息。

  Routing Key:路由关键字(Topic),主要用来控制生产者和消费者之间的发送与接收消息的对应关系。

  Channel:消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务。

  到此为止,我们明白了一个分布式消息队列系统的主要构成模块,现在本人就通过Netty,这个优秀的Java NIO网络通讯框架,构建一个支持上述应用场景的分布式消息队列系统,本人把其命名为AvatarMQ。后续我会基于这个开源项目,连载出基于Netty构建分布式消息队列系统系列相关的文章,阐明主要的设计思路、组织结构、模块划分依据、类图结构等等。为了说明方便,后续本文中,如果没有特殊说明,有涉及基于Netty构建的分布式消息队列系统,就是指代AvatarMQ。由于整个开源项目涉及的代码量比较多,所以希望大家在本人编写系列博客文章的基础上,耐心地理解、分析其中的代码模块,相信一定不会让您失望!

  AvatarMQ基于Netty,所以首先,你要能清楚的理解Netty是什么?它能做什么?有兴趣的朋友可以关注一下Netty项目的官网(http://netty.io/),上面有很详细的入门文章介绍。虽然都是英文的,但是这些一手的资料更具权威性,值得花时间深入研究探索,毕竟现在流行的云计算、大数据领域成功的开源项目比如Hadoop、Storm等等,网络通信层这块全部依赖Netty,可见Netty的功能强大。

  基于Netty可以开发定制高性能、高可靠性的Java企业级服务端应用,而本文是我,在继利用Netty构建高性能RPC服务器系列文章之后,又一个基于Netty开发的分布式消息队列系统(AvatarMQ)。此外AvatarMQ还大量使用了Java多线程的相关类库。所以希望在此之前,大家能回忆复习一下,这样理解起来会更加得心应手、事半功倍。

  AvatarMQ是基于Netty构建的分布式消息队列系统,支持多个生产者和多个消费者之间的消息路由、传递。主要特性如下:

  • AvatarMQ基于Java语言进行编写,网络通讯依赖Netty。
  • 生产者和消费者的关系可以是一对多、多对一、多对多的关系。
  • 若干个消费者可以组成消费者集群,生产者可以向这个消费者集群投递消息。
  • 消费者集群对于有共同关注点的消费者支持消息的负载均衡策略。
  • 支持动态新增、删除生产者、消费者。
  • 目前仅仅支持关键字的精确匹配路由,后续会逐渐完善。
  • 消息队列服务器Broker基于Netty的主从事件线程池模型开发设计。
  • 网络消息序列化采用Kryo进行消息的网络序列化传输。
  • Broker的消息派发、负载均衡、应答处理(ACK)基于异步多线程模型进行开发设计。
  • Broker消息的投递,目前支持严格的消息顺序。其中Broker还支持消息的缓冲派发,即Broker会缓存一定数量的消息之后,再批量分配给对此消息感兴趣的消费者。

  AvatarMQ项目开源网址:https://github.com/tang-jie/AvatarMQ

  整个开源项目依赖的jar包请参考:https://github.com/tang-jie/AvatarMQ/blob/master/nbproject/project.properties

  另外,值得注意的是:

  AvatarMQ使用的Netty是基于4.0版本(下载地址:http://dl.bintray.com/netty/downloads/netty-4.0.37.Final.tar.bz2)。

  消息序列化使用的Kryo是基于kryo-3.0.3版本(下载地址:https://github.com/EsotericSoftware/kryo/releases/tag/kryo-parent-3.0.3)。

  请大家自行去官网下载使用。

  现在,现在言归正传,我们先来看下整合AvatarMQ项目的软件架构图:

  从上述图例中,我们可以很清楚的看到:生产者和消费者之间是通过Broker进行消息的路由和转发,同时Broker还负责应答生产者和接收消费者的处理应答。

  在了解了,整个AvatarMQ的组织架构之后,我们再来实际运行一下AvatarMQ!

  首先,先启动一下Broker服务器(对应代码:https://github.com/tang-jie/AvatarMQ/blob/master/src/com/newlandframework/avatarmq/spring/AvatarMQServerStartup.java

  如果一切正常,终端控制台会打印如下输出:

  

  接着,我们就来实际验证一下AvatarMQ的消息推送功能。

  1、生产者发送1条消息给关注这条消息的消费者。我们先启动消费者,再启动生产者。

  其中消费者1的测试代码(AvatarMQConsumer1.java)如下所示:

package com.newlandframework.avatarmq.test;

import com.newlandframework.avatarmq.consumer.AvatarMQConsumer;
import com.newlandframework.avatarmq.consumer.ProducerMessageHook;
import com.newlandframework.avatarmq.msg.ConsumerAckMessage;
import com.newlandframework.avatarmq.msg.Message; /**
* @filename:AvatarMQConsumer1.java
* @description:AvatarMQConsumer1功能模块
* @author tangjie<https://github.com/tang-jie>
* @blog http://www.cnblogs.com/jietang/
* @since 2016-8-11
*/
public class AvatarMQConsumer1 { private static ProducerMessageHook hook = new ProducerMessageHook() {
public ConsumerAckMessage hookMessage(Message message) {
System.out.printf("AvatarMQConsumer1 收到消息编号:%s,消息内容:%s\n", message.getMsgId(), new String(message.getBody()));
ConsumerAckMessage result = new ConsumerAckMessage();
result.setStatus(ConsumerAckMessage.SUCCESS);
return result;
}
}; public static void main(String[] args) {
AvatarMQConsumer consumer = new AvatarMQConsumer("127.0.0.1:18888", "AvatarMQ-Topic-1", hook);
consumer.init();
consumer.setClusterId("AvatarMQCluster");
consumer.receiveMode();
consumer.start();
}
}

  生产者1的测试代码(AvatarMQProducer1.java)如下所示,其含义是发送1条消息,给关注“AvatarMQ-Topic-1”主题的消费者:

package com.newlandframework.avatarmq.test;

import com.newlandframework.avatarmq.msg.Message;
import com.newlandframework.avatarmq.msg.ProducerAckMessage;
import com.newlandframework.avatarmq.producer.AvatarMQProducer;
import org.apache.commons.lang3.StringUtils; /**
* @filename:AvatarMQProducer1.java
* @description:AvatarMQProducer1功能模块
* @author tangjie<https://github.com/tang-jie>
* @blog http://www.cnblogs.com/jietang/
* @since 2016-8-11
*/
public class AvatarMQProducer1 { public static void main(String[] args) throws InterruptedException {
AvatarMQProducer producer = new AvatarMQProducer("127.0.0.1:18888", "AvatarMQ-Topic-1");
producer.setClusterId("AvatarMQCluster");
producer.init();
producer.start(); System.out.println(StringUtils.center("AvatarMQProducer1 消息发送开始", 50, "*")); for (int i = 0; i < 1; i++) {
Message message = new Message();
String str = "Hello AvatarMQ From Producer1[" + i + "]";
message.setBody(str.getBytes());
ProducerAckMessage result = producer.delivery(message);
if (result.getStatus() == (ProducerAckMessage.SUCCESS)) {
System.out.printf("AvatarMQProducer1 发送消息编号:%s\n", result.getMsgId());
} Thread.sleep(100);
} producer.shutdown();
System.out.println(StringUtils.center("AvatarMQProducer1 消息发送完毕", 50, "*"));
}
}

  首先我们先来启动消费者,如果一切正常,控制台输出结果为:

  这个时候我们再运行生产者,发送一条消息给消费者。启动生产者之后,控制台输出结果如下:

  那现在,我们切回去看下消费者是否收到生产者的消息了呢?

  非常正确,我们的消费者果然收到了生产者发送过来的消息。

  2、生产者发送1条消息给不关注这条消息的消费者。

  首先说明的是,代码样例还是基于上述的AvatarMQConsumer1.java、AvatarMQProducer1.java。只不过这次是生产者发送的主题改成:“AvatarMQ-Topic-Test”,消费者关注的主题改成“AvatarMQ-Topic-1”。然后依次启动消费者、生产者。下面是实际的运行情况:

  生产者成功发送消息:

  那按照要求,消费者应该无法收到生产者的这条消息,实际情况是不是这样呢?事实胜于雄辩,看如下截图所示:

  消费者依然处理启动监听状态,说明完全符合我们的预期。

  3、生产者发送N条消息(这里是发送100条消息)给一个消费者集群(有2个消费者组成,并且这2个消费者关注的消息主题topic是相同的)。

  我们先启动2个消费者,再启动生产者。消费者代码参考:

package com.newlandframework.avatarmq.test;

import com.newlandframework.avatarmq.consumer.AvatarMQConsumer;
import com.newlandframework.avatarmq.consumer.ProducerMessageHook;
import com.newlandframework.avatarmq.msg.ConsumerAckMessage;
import com.newlandframework.avatarmq.msg.Message; /**
* @filename:AvatarMQConsumer2.java
* @description:AvatarMQConsumer2功能模块
* @author tangjie<https://github.com/tang-jie>
* @blog http://www.cnblogs.com/jietang/
* @since 2016-8-11
*/
public class AvatarMQConsumer2 { private static ProducerMessageHook hook = new ProducerMessageHook() {
public ConsumerAckMessage hookMessage(Message message) {
System.out.printf("AvatarMQConsumer2 收到消息编号:%s,消息内容:%s\n", message.getMsgId(), new String(message.getBody()));
ConsumerAckMessage result = new ConsumerAckMessage();
result.setStatus(ConsumerAckMessage.SUCCESS);
return result;
}
}; public static void main(String[] args) {
AvatarMQConsumer consumer = new AvatarMQConsumer("127.0.0.1:18888", "AvatarMQ-Topic-2", hook);
consumer.init();
consumer.setClusterId("AvatarMQCluster2");
consumer.receiveMode();
consumer.start();
}
}

  生产者代码参考(目的是发送100条消息)给消费者集群。

package com.newlandframework.avatarmq.test;

import com.newlandframework.avatarmq.msg.Message;
import com.newlandframework.avatarmq.msg.ProducerAckMessage;
import com.newlandframework.avatarmq.producer.AvatarMQProducer;
import org.apache.commons.lang3.StringUtils; /**
* @filename:AvatarMQProducer2.java
* @description:AvatarMQProducer2功能模块
* @author tangjie<https://github.com/tang-jie>
* @blog http://www.cnblogs.com/jietang/
* @since 2016-8-11
*/
public class AvatarMQProducer2 { public static void main(String[] args) throws InterruptedException {
AvatarMQProducer producer = new AvatarMQProducer("127.0.0.1:18888", "AvatarMQ-Topic-2");
producer.setClusterId("AvatarMQCluster2");
producer.init();
producer.start(); System.out.println(StringUtils.center("AvatarMQProducer2 消息发送开始", 50, "*")); for (int i = 0; i < 100; i++) {
Message message = new Message();
String str = "Hello AvatarMQ From Producer2[" + i + "]";
message.setBody(str.getBytes());
ProducerAckMessage result = producer.delivery(message);
if (result.getStatus() == (ProducerAckMessage.SUCCESS)) {
System.out.printf("AvatarMQProducer2 发送消息编号:%s\n", result.getMsgId());
} Thread.sleep(100);
} producer.shutdown();
System.out.println(StringUtils.center("AvatarMQProducer2 消息发送完毕", 50, "*"));
}
}

  我们依次启动消费者AvatarMQConsumer2两次,这个时候终端控制台依次输出:

  这个时候我们再启动生产者,运行截图如下:

  说明生产者发送了100条消息出去,看下我们消费者1接收的情况:

  继续看下我们的消费者2,消息接收的情况,截图如下:

  最终统计一下,消费者1,接收的消息编号都是奇数,一共50个。消费者2,接收到的消息编号都是偶数,一共50个。两个消费者接收的消息总数加起来,刚好等于生产者发送的消息总数100个,完全符合我们的预期!另外消费者1、消费者2都收到了来自生产者的消息,说明Broker进行了消息的路由传递。

  4、多个生产者和多个消费者的消息传递,以及动态新增、删除生产者、消费者。

  这个就交给大家自行测试了,由于篇幅有限,在此本人就不一一阐述。

  到目前为止,相信大家对于AvatarMQ所具备的基本功能,有了一个大致的印象。当然,AvatarMQ还有一些美中不足,比如:

  • 不支持消息的刷盘存储,可能由于系统Crash,造成消息的丢失。后续需要接入一个存储系统(基于Java NIO),保证消息的持久序列化。
  • AvatarMQ的生产者、消费者模块,要进一步支持,断网重连Broker的功能,确保在Broker重启的情况下,把在途的消息继续发送、接收完毕。
  • Broker单点的问题,根据高可用性集群HA(High Available)的标准,Broker也要有主节点和从节点机制。在主节点宕机的情况,从节点要能灰度过渡,不至于Broker主节点宕机,整个AvatarMQ消息系统陷入瘫痪状态。
  • 消息应答失败,还未支持重试功能。
  • 当然还有一些未知的bug,有待发现和修复。
  • AvatarMQ的处理性能,未经历过生产系统实际检验,暂时无法保证其安全和可靠性。

  由于代码编写、测试等等工作,都是本人利用工作之余的时间完成,时间点上比较仓促。加上本人的技术水平有限,难免有说的不对及写得不好的地方,或者其中应该有更好的解决方案。欢迎广大同行、爱好者在线下进行学习交流,有什么宝贵的建议和观点,恳请批评指正,不吝赐教。虽然AvatarMQ和业界主流、久经考验的消息队列系统,在处理性能、可靠性上,肯定还有不小的差距。但是可以基于此,加深对分布式消息队列的理解,做到知其然知其所以然,何乐而不为?

  最后,本人后续会逐渐推出“基于Netty构建的分布式消息队列系统(AvatarMQ)”,架构设计、原理分析的详解连载文章,敬请期待!

  PS:目前AvatarMQ已经开源,整个项目托管到github,对应的网址为:https://github.com/tang-jie/AvatarMQ,欢迎有兴趣的同行朋友、爱好者关注支持。如果觉得还不错,可以点击Star收藏、关注。当然,你还可以点击推荐本文,也算是对我辛苦付出的一点支持和回报,谢谢大家!

Netty构建分布式消息队列(AvatarMQ)设计指南之架构篇的更多相关文章

  1. Netty构建分布式消息队列实现原理浅析

    在本人的上一篇博客文章:Netty构建分布式消息队列(AvatarMQ)设计指南之架构篇 中,重点向大家介绍了AvatarMQ主要构成模块以及目前存在的优缺点.最后以一个生产者.消费者传递消息的例子, ...

  2. [.NET领域驱动设计实战系列]专题八:DDD案例:网上书店分布式消息队列和分布式缓存的实现

    一.引言 在上一专题中,商家发货和用户确认收货功能引入了消息队列来实现的,引入消息队列的好处可以保证消息的顺序处理,并且具有良好的可扩展性.但是上一专题消息队列是基于内存中队列对象来实现,这样实现有一 ...

  3. 分布式服务(RPC)+分布式消息队列(MQ)面试题精选

    ​ 分布式系统(distributed system)是建立在网络之上的软件系统.正是因为软件的特性,所以分布式系统具有高度的内聚性和透明性.因此,网络和分布式系统之间的区别更多的在于高层软件(特别是 ...

  4. 【转】快速理解Kafka分布式消息队列框架

     from:http://blog.csdn.net/colorant/article/details/12081909 快速理解Kafka分布式消息队列框架 标签: kafkamessage que ...

  5. 分布式消息队列RocketMQ(一)安装与启动

    分布式消息队列RocketMQ 一.RocketMQ简介 RocketMQ(火箭MQ) 出自于阿里,后开源给apache成为apache的顶级开源项目之一,顶住了淘宝10年的 双11压力 是电商产品的 ...

  6. C#分布式消息队列 EQueue 2.0 发布啦

    前言 最近花了我几个月的业余时间,对EQueue做了一个重大的改造,消息持久化采用本地写文件的方式.到现在为止,总算完成了,所以第一时间写文章分享给大家这段时间我所积累的一些成果. EQueue开源地 ...

  7. EQueue - 一个纯C#写的分布式消息队列介绍2

    一年前,当我第一次开发完EQueue后,写过一篇文章介绍了其整体架构,做这个框架的背景,以及架构中的所有基本概念.通过那篇文章,大家可以对EQueue有一个基本的了解.经过了1年多的完善,EQueue ...

  8. EQueue - 一个C#写的开源分布式消息队列的总体介绍

    前言 本文想介绍一下前段时间在写enode时,顺便实现的一个分布式消息队列equeue.这个消息队列的思想不是我想出来的,而是通过学习阿里的rocketmq后,自己用c#实现了一个轻量级的简单版本.一 ...

  9. ENode 1.0 - 消息队列的设计思路

    开源地址:https://github.com/tangxuehua/enode 上一篇文章,简单介绍了enode框架内部的整体实现思路,用到了staged event-driven architec ...

随机推荐

  1. 阿里云直播 C# SDK 如何使用

    阿里云直播SDK的坑 1.直播云没有单独的SDK,直播部分被封装在CDN的相关SDK当中. 2.针对SDK,没有相关Demo. 3.针对SDK,没有相关的文档说明. 4.针对SDK的说明,官网上的说明 ...

  2. 谈谈如何使用Netty开发实现高性能的RPC服务器

    RPC(Remote Procedure Call Protocol)远程过程调用协议,它是一种通过网络,从远程计算机程序上请求服务,而不必了解底层网络技术的协议.说的再直白一点,就是客户端在不必知道 ...

  3. javascript的api设计原则

    前言 本篇博文来自一次公司内部的前端分享,从多个方面讨论了在设计接口时遵循的原则,总共包含了七个大块.系卤煮自己总结的一些经验和教训.本篇博文同时也参考了其他一些文章,相关地址会在后面贴出来.很难做到 ...

  4. .NET 基础 一步步 一幕幕[面向对象之方法、方法的重载、方法的重写、方法的递归]

    方法.方法的重载.方法的重写.方法的递归 方法: 将一堆代码进行重用的一种机制. 语法: [访问修饰符] 返回类型 <方法名>(参数列表){ 方法主体: } 返回值类型:如果不需要写返回值 ...

  5. Vue + Webpack + Vue-loader 系列教程(2)相关配置篇

    原文地址:https://lvyongbo.gitbooks.io/vue-loader/content/ 使用预处理器 在 Webpack 中,所有的预处理器需要和一个相应的加载器一同使用.vue- ...

  6. windows+nginx+iis+redis+Task.MainForm构建分布式架构 之 (nginx+iis构建服务集群)

    本次要分享的是利用windows+nginx+iis+redis+Task.MainForm组建分布式架构,由标题就能看出此内容不是一篇分享文章能说完的,所以我打算分几篇分享文章来讲解,一步一步实现分 ...

  7. XML技术之DOM4J解析器

    由于DOM技术的解析,存在很多缺陷,比如内存溢出,解析速度慢等问题,所以就出现了DOM4J解析技术,DOM4J技术的出现大大改进了DOM解析技术的缺陷. 使用DOM4J技术解析XML文件的步骤? pu ...

  8. Expression Blend创建自定义按钮

    在 Expression Blend 中,我们可以在美工板上绘制形状.路径和控件,然后修改其外观和行为,从而直观地设计应用程序.Button按钮也是Expression Blend最常用的控件之一,在 ...

  9. .NET Core 2016 回顾

    都在回顾自己的2016,今天我们来看看.NET Core的2016. 每一年的脚步的确是快,转眼间马上就2017.新的一年,带着理想和抱负继续出发. 1 月 ASP.NET 5 改名 ASP.NET ...

  10. android手机登录时遇到“QQ安全登录发现病毒”解决

    android手机作为开源系统非常容易感染病毒,有时候我们会经常遇到手机QQ登录时检测到app被感染,一般情况是由手机感染病毒所引起的,安装腾讯管家后只能检测病毒和卸载感染病毒的软件,不能清除病毒.解 ...