目前业界流行的分布式消息队列系统(或者可以叫做消息中间件)种类繁多,比如,基于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. TypeScript: Angular 2 的秘密武器(译)

    本文整理自Dan Wahlin在ng-conf上的talk.原视频地址: https://www.youtube.com/watch?v=e3djIqAGqZo 开场白 开场白主要分为三部分: 感谢了 ...

  2. MySQL设置字段的默认值为当前系统时间

    问题产生: 当我们在对某个字段进行设置时间默认值,该默认值必须是的当前记录的插入时间,那么就将当前系统时间作为该记录创建的时间. 应用场景: 1.在数据表中,要记录每条数据是什么时候创建的,应该由数据 ...

  3. jQuery.Ajax IE8 无效(CORS)

    今天在开发的时候,遇到一个问题,$.get()在 IE8 浏览器不起作用,但 Chrome,Firefox 却是可以的,网上资料很多,最后发现是 IE8 默认不支持 CORS 请求,需要手动开启下: ...

  4. springMVC初探--环境搭建和第一个HelloWorld简单项目

    注:此篇为学习springMVC时,做的笔记整理. MVC框架要做哪些事情? a,将url映射到java类,或者java类的方法上 b,封装用户提交的数据 c,处理请求->调用相关的业务处理—& ...

  5. [原]一个针对LVS的压力测试报告

    LVS 测试报告 测试计划 基本功能测试 流量压力测试 响应时间测试 配置正确性测试 灾难恢复测试 测试点 基本功能测试 客户端IP地址正确性 RealServer 访问Internet测试(包括Ip ...

  6. css3线条围绕跑马+jquery打字机效果

    原文地址:css3线条围绕跑马+jquery打字机效果 有图有真相,今天偶然看到了一种效果,仔细看了下,发现它是用css的clip+css3的动画实现的,简直叼.于是自己拿来了前一阵子写的打字机效果, ...

  7. win7下利用ftp实现华为路由器的上传和下载

    win7下利用ftp实现华为路由器的上传和下载 1.  Win7下ftp的安装和配置 (1)开始->控制面板->程序->程序和功能->打开或关闭Windows功能 (2)在Wi ...

  8. Hadoop的安装与设置(1)

    在Ubuntu下安装与设置Hadoop的主要过程. 1. 创建Hadoop用户 创建一个用户,用户名为hadoop,在home下创建该用户的主目录,就不详细介绍了. 2. 安装Java环境 下载Lin ...

  9. [Unity3D]利用Raycast实现物体的选择与操作

    本文系作者原创 转载请注明出处 如果是一个2D的平面项目或者说需要在三维空间选择一个物体时(经常表现为抓取物件),我们需要用到Raycast事件 那么首先先说说什么是Raycast 按照字面上来理解的 ...

  10. fmt标签把时间戳格式化日期

    jsp页面标签格式化日期 <%@taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="f" %> ...