简介

在前面一篇文章里讨论过几种应用系统集成的方式,发现实际上面向消息队列的集成方案算是一个总体比较合理的选择。这里,我们先针对具体的一个消息队列Activemq的基本通信方式进行探讨。activemq是JMS消息通信规范的一个实现。总的来说,消息规范里面定义最常见的几种消息通信模式主要有发布-订阅、点对点这两种。另外,通过结合这些模式的具体应用,我们在处理某些应用场景的时候也衍生出来了一种请求应答的模式。下面,我们针对这几种方式一一讨论一下。

基础流程

在讨论具体方式的时候,我们先看看使用activemq需要启动服务的主要过程。

按照JMS的规范,我们首先需要获得一个JMS connection factory.,通过这个connection factory来创建connection.在这个基础之上我们再创建session, destination, producer和consumer。因此主要的几个步骤如下:

1. 获得JMS connection factory. 通过我们提供特定环境的连接信息来构造factory。

2. 利用factory构造JMS connection

3. 启动connection

4. 通过connection创建JMS session.

5. 指定JMS destination.

6. 创建JMS producer或者创建JMS message并提供destination.

7. 创建JMS consumer或注册JMS message listener.

8. 发送和接收JMS message.

9. 关闭所有JMS资源,包括connection, session, producer, consumer等。

publish-subscribe

发布订阅模式有点类似于我们日常生活中订阅报纸。每年到年尾的时候,邮局就会发一本报纸集合让我们来选择订阅哪一个。在这个表里头列了所有出版发行的报纸,那么对于我们每一个订阅者来说,我们可以选择一份或者多份报纸。比如北京日报、潇湘晨报等。那么这些个我们订阅的报纸,就相当于发布订阅模式里的topic。有很多个人订阅报纸,也有人可能和我订阅了相同的报纸。那么,在这里,相当于我们在同一个topic里注册了。对于一份报纸发行方来说,它和所有的订阅者就构成了一个1对多的关系。这种关系如下图所示:

p2p

p2p的过程则理解起来更加简单。它好比是两个人打电话,这两个人是独享这一条通信链路的。一方发送消息,另外一方接收,就这么简单。在实际应用中因为有多个用户对使用p2p的链路,它的通信场景如下图所示:

在p2p的场景里,相互通信的双方是通过一个类似于队列的方式来进行交流。和前面pub-sub的区别在于一个topic有一个发送者和多个接收者,而在p2p里一个queue只有一个发送者和一个接收者。

这两种通信模式的代码实现有很多相同之处,下面我们用一个例子来简单实现这两种通信方式:

发送者

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.util.StringTokenizer; import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage; import org.apache.activemq.ActiveMQConnectionFactory; public class Publisher {
public static final String url = "tcp://localhost:61616";// 缺省端口,如果要改,可在apache-activemq-5.13.3\conf中的activemq.xml中更改端口号
ConnectionFactory factory;
Connection connection;
Session session;
MessageProducer producer;
Destination[] destinations;
ComunicateMode comunicateMode = ComunicateMode.pubsub; enum ComunicateMode {
p2p, pubsub
} public Publisher(ComunicateMode mode) throws JMSException {
this.comunicateMode = mode;
factory = new ActiveMQConnectionFactory(url);// 这里的url也可以不指定,java代码将默认将端口赋值为61616
connection = factory.createConnection();
try {
connection.start();
} catch (JMSException e) {
connection.close();
throw e;
}
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
producer = session.createProducer(null);
} protected void setDestinations(String[] stocks) throws JMSException {
destinations = new Destination[stocks.length];
for (int i = 0; i < stocks.length; i++) {
destinations[i] = comunicateMode == ComunicateMode.pubsub ? session
.createTopic("Topic." + stocks[i]) : session
.createQueue("Queue." + stocks[i]);
}
} protected void sendMessage(String msg) throws JMSException {
for (Destination item : destinations) {
TextMessage msgMessage = session.createTextMessage(msg);
producer.send(item, msgMessage);
System.out.println(String.format("成功向Topic為【%s】发送消息【%s】",
item.toString(), msgMessage.getText()));
}
} protected void close() throws JMSException {
if (connection != null)
connection.close();
} public static void main(String[] args) throws JMSException,
InterruptedException, IOException {
Publisher publisher = new Publisher(ComunicateMode.p2p);// 这里可以修改消息传输方式为pubsub
publisher.setDestinations(new String[] { "1", "2", "3" });
BufferedReader reader = null;
String contentString = "";
do {
System.out.println("请输入要发送的内容(exit退出):");
reader = new BufferedReader(new InputStreamReader(System.in));
contentString = reader.readLine();
if (contentString.equals("exit"))
break;
publisher.sendMessage(contentString);
} while (!contentString.equals("exit"));
reader.close();
publisher.close();
}
}
 

接收者

import java.io.IOException;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Session;
import javax.jms.TextMessage; import org.apache.activemq.ActiveMQConnectionFactory; public class Consumer {
public static final String url = "tcp://localhost:61616";// 缺省端口,如果要改,可在apache-activemq-5.13.3\conf中的activemq.xml中更改端口号
ConnectionFactory factory;
Connection connection;
Session session;
MessageConsumer[] consumers;
ComunicateMode comunicateMode = ComunicateMode.pubsub; enum ComunicateMode {
p2p, pubsub
} public Consumer(ComunicateMode mode, String[] destinationNames)
throws JMSException {
this.comunicateMode = mode;
factory = new ActiveMQConnectionFactory(url);// 这里的url也可以不指定,java代码将默认将端口赋值为61616
connection = factory.createConnection();
connection.start();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
consumers = new MessageConsumer[destinationNames.length];
for (int i = 0; i < destinationNames.length; i++) {
Destination destination = comunicateMode == ComunicateMode.pubsub ? session
.createTopic("Topic." + destinationNames[i]) : session
.createQueue("Queue." + destinationNames[i]);
consumers[i] = session.createConsumer(destination);
consumers[i].setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
try {
System.out.println(String.format("收到消息【%s】",
((TextMessage) message).getText()));
} catch (JMSException e) {
e.printStackTrace();
}
}
});
}
} public void close() throws JMSException {
if (connection != null)
connection.close();
} public static void main(String[] args) throws JMSException, IOException {
Consumer consumer = new Consumer(ComunicateMode.p2p,
new String[] { "2" });// 这里可以修改消息传输方式为pubsub
System.in.read();
consumer.close();
} }
 

request-response

和前面两种方式比较起来,request-response的通信方式很常见,但是不是默认提供的一种模式。在前面的两种模式中都是一方负责发送消息而另外一方负责处理。而我们实际中的很多应用相当于一种一应一答的过程,需要双方都能给对方发送消息。于是请求-应答的这种通信方式也很重要。它也应用的很普遍。

请求-应答方式并不是JMS规范系统默认提供的一种通信方式,而是通过在现有通信方式的基础上稍微运用一点技巧实现的。下图是典型的请求-应答方式的交互过程:

在JMS里面,如果要实现请求/应答的方式,可以利用JMSReplyTo和JMSCorrelationID消息头来将通信的双方关联起来。另外,QueueRequestor和TopicRequestor能够支持简单的请求/应答过程。

現在,如果我们要实现这么一个过程,在发送请求消息并且等待返回结果的client端的流程如下:

client端创建一个临时队列并在发送的消息里指定了发送返回消息的destination以及correlationID。那么在处理消息的server端得到这个消息后就知道该发送给谁了。Server端的大致流程如下:

这里我们是用server端注册MessageListener,通过设置返回信息的CorrelationID和JMSReplyTo将信息返回。

以上就是发送和接收消息的双方的大致程序结构。具体的实现代码如下:

Client:

这里的代码除了初始化构造函数里的参数还同时设置了两个destination,一个是自己要发送消息出去的destination,在session.createProducer(adminQueue);这一句设置。另外一个是自己要接收的消息destination, 通过Destination tempDest = session.createTemporaryQueue(); responseConsumer = session.createConsumer(tempDest); 这两句指定了要接收消息的目的地。这里是用的一个临时队列。在前面指定了返回消息的通信队列之后,我们需要通知server端知道发送返回消息给哪个队列。于是txtMessage.setJMSReplyTo(tempDest);指定了这一部分,同时txtMessage.setJMSCorrelationID(correlationId);方法主要是为了保证每次发送回来请求的server端能够知道对应的是哪个请求。这里一个请求和一个应答是相当于对应一个相同的序列号一样。

同时,因为client端在发送消息之后还要接收server端返回的消息,所以它也要实现一个消息receiver的功能。这里采用实现MessageListener接口的方式:

public void onMessage(Message message) {
String messageText = null;
try {
if (message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
messageText = textMessage.getText();
System.out.println("messageText = " + messageText);
}
} catch (JMSException e) {
//Handle the exception appropriately
}
}

Server:

这里server端要执行的过程和client端相反,它是先接收消息,在接收到消息后根据提供的JMSCorelationID来发送返回的消息:

前面,在replyProducer.send()方法里,message.getJMSReplyTo()就得到了要发送消息回去的destination。

另外,设置这些发送返回信息的replyProducer的信息主要在构造函数相关的方法里实现了:

总体来说,整个的交互过程并不复杂,只是比较繁琐。对于请求/应答的方式来说,这种典型交互的过程就是Client端在设定正常发送请求的Queue同时也设定一个临时的Queue。同时在要发送的message里头指定要返回消息的destination以及CorelationID,这些就好比是一封信里面所带的回执。根据这个信息人家才知道怎么给你回信。对于Server端来说则要额外创建一个producer,在处理接收到消息的方法里再利用producer将消息发回去。这一系列的过程看起来很像http协议里面请求-应答的方式,都是一问一答。

一些应用和改进

回顾前面三种基本的通信方式,我们会发现,他们都存在着一定的共同点,比如说都要初始化ConnectionFactory, Connection, Session等。在使用完之后都要将这些资源关闭。如果每一个实现它的通信端都这么写一通的话,其实是一种简单的重复。从工程的角度来看是完全没有必要的。那么,我们有什么办法可以减少这种重复呢?

一种简单的方式就是通过工厂方法封装这些对象的创建和销毁,然后简单的通过调用工厂方法的方式得到他们。另外,既然基本的流程都是在开头创建资源在结尾销毁,我们也可以采用Template Method模式的思路。通过继承一个抽象类,在抽象类里提供了资源的封装。所有继承的类只要实现怎么去使用这些资源的方法就可以了。Spring中间的JMSTemplate就提供了这种类似思想的封装。具体的实现可以参考这篇文章

总结

activemq默认提供了pub-sub, p2p这两种通信的方式。同时也提供了一些对request-response方式的支持。实际上,不仅仅是activemq,对于所有其他实现JMS规范的产品都能够提供类似的功能。这里每种方式都不太复杂,主要是创建和管理资源的步骤显得比较繁琐。

activemq的两种基本通信方式的使用及总结的更多相关文章

  1. activemq的几种基本通信方式总结

    简介 在前面一篇文章里讨论过几种应用系统集成的方式,发现实际上面向消息队列的集成方案算是一个总体比较合理的选择.这里,我们先针对具体的一个消息队列Activemq的基本通信方式进行探讨.activem ...

  2. SpringBoot使用JMS(activeMQ)的两种方式 队列消息、订阅/发布

    刚好最近同事问我activemq的问题刚接触所以分不清,前段时间刚好项目中有用到,所以稍微整理了一下,仅用于使用 1.下载ActiveMQ 地址:http://activemq.apache.org/ ...

  3. ActiveMQ的两种消息模式,主题、队列

    1.开发的模式流程如下: 2.队列模式Queue 如果生产者产生了100条消息,那么两个消费同时在的话,会分工合作来接收这100条消息.就是每个消费者接收到50条来处理. 3.主题模式topic 如果 ...

  4. activemq的三种通信方式

    一.安装与启动 1.下载安装activemq,下载地址:http://activemq.apache.org/download.html. 2.安装完成后,进入其所在目录的bin目录下面,根据系统位数 ...

  5. ActiveMQ消息传递的两种方式

    1.什么是ActiveMQ? ActiveMQ是apache提供的开源的,实现消息传递的一个中间插件,可以和spring整合,是目前最流行的开源消息总线,ActiveMQ是一个完全支持JMS1.1和J ...

  6. 消息队列:快速上手ActiveMQ消息队列的JMS方式使用(两种模式:Topic和Queue的消息推送和订阅)

    1.实现功能 希望使用一套API,实现两种模式下的消息发送和接收功能,方便业务程序调用 1.发送Topic 2.发送Queue 3.接收Topic 4.接收Queue 2.接口设计 根据功能设计公共调 ...

  7. 转:nginx和php-fpm的两种通信方式

    原文地址:https://segmentfault.com/q/1010000004854045 Nginx和PHP-FPM的进程间通信有两种方式,一种是TCP,一种是UNIX Domain Sock ...

  8. ActiveMQ两种模式PTP和PUB/SUB<转>

    1.PTP模型 PTP(Point-to-Point)模型是基于队列(Queue)的,对于PTP消息模型而言,它的消息目的是一个消息队列(Queue),消息生产者每次发送消息总是把消息送入消息队列中, ...

  9. php-fpm 和 nginx 的两种通信方式

    在 linux 中,nginx 服务器和 php-fpm 可以通过 tcp socket 和 unix socket 两种方式实现. 一下内容转自:https://blog.csdn.net/qq62 ...

随机推荐

  1. MFC网页

    写网页, 选择MFC,MFC应用程序,其他默认,单击确定 项目类型,选Offce,其他默认,单击下一步 默认,单击下一步 文件拓展名,输入html,其他默认,单击下一步 数据库支持,默认,单击下一步 ...

  2. 在asp.net中如何实现伪静态页 [转]

    我在这里就不过多讨论静态页.伪静态页.动态页的长短利弊了.只是单纯的讲解如何在asp.net中如何实现伪静态页,以帮助有这方面有需求的朋友,快速解决boss派下来的任务.(拿奖金的时候,记得有我一份功 ...

  3. POJ 1753 Flip Game (DFS + 枚举)

    题目:http://poj.org/problem?id=1753 这个题在開始接触的训练计划的时候做过,当时用的是DFS遍历,其机制就是把每一个棋子翻一遍.然后顺利的过了.所以也就没有深究. 省赛前 ...

  4. 基于Linux 3.0.8 Samsung FIMC(S5PV210) 的摄像头驱动框架解读(一)

    作者:咕唧咕唧liukun321 来自:http://blog.csdn.net/liukun321 FIMC这个名字应该是从S5PC1x0開始出现的.在s5pv210里面的定义是摄像头接口.可是它相 ...

  5. 提交时提示错误This Bundle is invalid.New apps and app updates submitted to the App Store must be built wit

    this bundle is invalid . new apps and app updates submitted to the app store must be built with publ ...

  6. Oracle初级入门 根据某字段重复只取一条记录,并计计算重复条数

    在平常开发中,去重复数据经常使用到,本人新手,接触Oracle也不久,开发中用到的小知识点,记录一下,老鸟可绕道,如果有写错的,请指正. 去重复记录可以使用distinct,当只查询一列数据时,可以轻 ...

  7. Mvc里删除Cooki

    /// <summary> /// 删除Cookie /// </summary> /// <param name="skuID">从购物车选择 ...

  8. Web 应用性能提升 10 倍的 10 个建议

    转载自http://blog.jobbole.com/94962/ 提升 Web 应用的性能变得越来越重要.线上经济活动的份额持续增长,当前发达世界中 5 % 的经济发生在互联网上(查看下面资源的统计 ...

  9. [C++基础]在构造函数内部调用构造函数

    看下面的面试题: #include <iostream> using namespace std; struct CLS { int m_i; CLS( int i ) : m_i(i){ ...

  10. can't find which disk is full

    df -lh lsof | grep delete $program_id df -lh # 搞定问题