初探Apache ActiveMQ
一、前言
先来说一说消息队列,顾名思义,消息队列是按一定顺序存放消息的容器。为什么会有这种东西呢,我们来看这样一个场景:在微信出现之前,也就是短信还非常普及的时代,我们通过短信与他人进行沟通。平时没有什么,发送了的消息几乎马上会被对方。但是如果很多人给你发短信,举个非常极端的例子,大年三十晚上发短信祝福的时候,往往会很久很久才会收到信息。没有收到之前信息在哪里呢,自然是有一个消息队列装载这些消息。
另一方面,使用消息队列还有一个好处就是,可以将两个系统进行解耦,尤其对于高并发并且多个系统处理效率不一致的情况,使用消息队列可以增大系统的吞吐量和效率。
作为一个JAVA开发人员,比较熟悉的消息队列中间件就是Apache的ActiveMQ,下面对其进行介绍。
二、术语名词
名词 | 英文全称 | 缩写 | 解释 |
---|---|---|---|
生产者 | MessageProvider | Provider | 产生或发送消息的系统 |
消费者 | MessageConsumer | Consumer | 处理或接收消息的系统 |
点对点 | Point to Point | P2P | 点对点消息模型 |
发布/订阅 | Publish/Subscribe | Pub/Sub | 发布/订阅消息模型 |
连接工厂 | ConnectionFactory | —— | 创建消息连接的工厂类 |
连接 | Connection | —— | 用于接发消息的连接 |
会话 | Session | —— | 由Connection创建,一个接发消息的会话 |
消息目的地 | Destination | —— | 由Session创建,消息发送的目的地 |
队列 | Queue | —— | 用于点对点消息模型 |
主题 | Topic | —— | 用于发布/订阅模型 |
三、消息模型
所谓消息模型,就是消息发送和接收的模式。在ActiveMQ中,分为两种消息模式,点对点消息模型和发布/订阅消息模型。
点对点模型
点对点消息模型就是一个生产者产生的消息,最终会由一个消费者去消费。在ActiveMQ中,对于同一个Destination,发送的消息放在同一个Queue中,消费者在消息发布之前不需要进行监听,当消费者从消息队列获取消息时,会根据现有的Queue按照获取的优先级获取消息。而且可以配置何时确认Queue消息被消耗。
发布/订阅模型
发布/订阅模型则不是,一个生产者发出的消息可能被多个消费者消费。在ActiveMQ中,对于同一个Destination,会产生一个Topic,当生产者产线消息后,直接进行发布。消费者需要提前进行监听,消息发布后,会自动接收消息。如果消息发布的时候没有任何一个消费者监听,那这个消息将不会被任何消费者消费。
四、消息协议
息协议,是指发送消息的校验格式或者说是传输数据结构,ActiveMQ在官方的例子中,一共包含如下四种消息协议:
1. AMQP协议
Advanced Message Queuing Protocol,被设计为当前流行消息中间件的开放替代品,它的可靠性和互用性是使用它的两个原因。它提供了与消息传递相关的各种功能,包括可靠的排队,基于主题的发布和订阅消息传递,灵活的路由,事务和安全性等。
2. MQTT协议
Message Queue Telemetry Transport,最初是IBM的普遍计算技术团队和其工业部门的合伙人开发的,之后移入开源社区,并被移动应用中流行起来。
3. Openwire协议
这个协议是Apache自己的跨语言线路协议,来让不同的开发语言和平台使用ActiveMQ。
4. stomp协议
Simple/Streaming Text Oriented Messaging Protocol,是基于文本的消息协议,更加类似于HTTP。
五、Hello World
0. 开发准备
- ActiveMQ,官方下载地址:http://activemq.apache.org/download.html
- Java运行环境
- ActiveMQ支持的开发语言开发环境,我这里选用JAVA
1. 启动服务
有两种启动方式:
通过命令行启动
cd到下载ActiveMQ解压后的根路径,执行bin\activemq start
启动成功后,可以访问http://localhost:8161/admin/进入ActiveMQ监控系统。嵌入式启动
引入lib下的jar包,或者直接引入activemq-all.jar到项目中,既可通过如下代码进行嵌入式启动。
BrokerService broker = new BrokerService();
//在这里可以对broker进行相关的配置
broker.addConnector("tcp://localhost:61616");
broker.start();
2. 消息工作类
由于生产者和消费者的初始化,有很多的共通之处,因此编写工作父类
package org.openwire.test;
import java.io.Closeable;
import java.io.IOException;
import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Session;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.command.ActiveMQTopic;
/**
* 消息工作类
* @author 银发Victorique
* @date 20180316
*/
public class MyMessageWorker implements Closeable {
//HOST
private String host = "localhost";
//端口
private int port = 61616;
//用户名
private String username = "admin";
//密码
private String password = "password";
//目的地
private String destination = "sv_test";
//连接工厂
private ActiveMQConnectionFactory factory;
//消息连接
private Connection connection;
//消息会话
private Session session;
//目的地
private Destination dest;
/**
* 全默认配置构造方法
* @param type 消息类型 Topic或Queue
* @param acknowledgeType 消息确认类型 1 2 3
* @throws Exception
*/
public MyMessageWorker(MessageType type, int acknowledgeType) throws JMSException {
if(acknowledgeType != Session.AUTO_ACKNOWLEDGE && acknowledgeType != Session.CLIENT_ACKNOWLEDGE
&& acknowledgeType != Session.DUPS_OK_ACKNOWLEDGE && acknowledgeType != Session.SESSION_TRANSACTED) {
throw new JMSException("消息确认类型不存在!");
}
if(type.equals(MessageType.Queue)) {
this.dest = new ActiveMQQueue(destination);
} else if(type.equals(MessageType.Topic)) {
this.dest = new ActiveMQTopic(destination);
} else {
throw new JMSException("消息类型不存在!");
}
this.factory = new ActiveMQConnectionFactory("tcp://" + host + ":" + port);
this.connection = factory.createConnection(username, password);
connection.start();
this.session = connection.createSession(false, acknowledgeType);
}
/**
* 默认用户名密码的构造方法
* @param type 消息类型 Topic或Queue
* @param acknowledgeType 消息确认类型 1 2 3
* @param host HOST地址
* @param port 端口
* @param destination 消息目的地
* @throws Exception
*/
public MyMessageWorker(MessageType type, int acknowledgeType, String host, int port, String destination) throws JMSException {
this(type, acknowledgeType);
this.host = host;
this.port = port;
this.destination = destination;
}
/**
* 全配置构造方法
* @param type 消息类型 Topic或Queue
* @param acknowledgeType 消息确认类型 1 2 3
* @param host HOST地址
* @param port 端口
* @param destination 消息目的地
* @param username 用户名
* @param password 密码
* @throws Exception
*/
public MyMessageWorker(MessageType type, int acknowledgeType, String host, int port, String destination, String username, String password) throws JMSException {
this(type, acknowledgeType, host, port, destination);
this.username = username;
this.password = password;
}
@Override
public void close() throws IOException {
try {
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
// getter
public Destination getDestination() {
return dest;
}
public ActiveMQConnectionFactory getFactory() {
return factory;
}
public Connection getConnection() {
return connection;
}
public Session getSession() {
return session;
}
/**
* 消息类型
* @author 银发Victorique
* @date 20180316
*/
public enum MessageType {
Queue, Topic;
}
}
3. 生产者
package org.openwire.test;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.TextMessage;
/**
* 消息生产者
* @author 银发Victorique
* @date 20180316
*/
public class MyProvider extends MyMessageWorker {
private MessageProducer producer;
/**
* 全默认配置构造方法
* @param type 消息类型 Topic或Queue
* @param acknowledgeType 消息确认类型 1 2 3
* @throws Exception
*/
public MyProvider(MessageType type, int acknowledgeType) throws JMSException {
super(type, acknowledgeType);
this.producer = getSession().createProducer(getDestination());
}
/**
* 默认用户名密码的构造方法
* @param type 消息类型 Topic或Queue
* @param acknowledgeType 消息确认类型 1 2 3
* @param host HOST地址
* @param port 端口
* @param destination 消息目的地
* @throws Exception
*/
public MyProvider(MessageType type, int acknowledgeType, String host, int port, String destination) throws JMSException {
super(type, acknowledgeType, host, port, destination);
}
/**
* 全配置构造方法
* @param type 消息类型 Topic或Queue
* @param acknowledgeType 消息确认类型 1 2 3
* @param host HOST地址
* @param port 端口
* @param destination 消息目的地
* @param username 用户名
* @param password 密码
* @throws Exception
*/
public MyProvider(MessageType type, int acknowledgeType, String host, int port, String destination, String username, String password) throws JMSException {
super(type, acknowledgeType, host, port, destination, username, password);
}
/**
* 发送消息
* @param message 消息文本
*/
public void send(String message) throws JMSException {
TextMessage msg = getSession().createTextMessage(message);
producer.send(msg);
}
}
4. 消费者
package org.openwire.test;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.TextMessage;
/**
* 消息消费者
* @author SVictorique
*
*/
public class MyConsumer extends MyMessageWorker {
private MessageConsumer consumer;
/**
* 全默认配置构造方法
* @param type 消息类型 Topic或Queue
* @param acknowledgeType 消息确认类型 1 2 3
* @throws Exception
*/
public MyConsumer(MessageType type, int acknowledgeType) throws JMSException {
super(type, acknowledgeType);
this.consumer = getSession().createConsumer(getDestination());
}
/**
* 默认用户名密码的构造方法
* @param type 消息类型 Topic或Queue
* @param acknowledgeType 消息确认类型 1 2 3
* @param host HOST地址
* @param port 端口
* @param destination 消息目的地
* @throws Exception
*/
public MyConsumer(MessageType type, int acknowledgeType, String host, int port, String destination) throws JMSException {
super(type, acknowledgeType, host, port, destination);
}
/**
* 全配置构造方法
* @param type 消息类型 Topic或Queue
* @param acknowledgeType 消息确认类型 1 2 3
* @param host HOST地址
* @param port 端口
* @param destination 消息目的地
* @param username 用户名
* @param password 密码
* @throws Exception
*/
public MyConsumer(MessageType type, int acknowledgeType, String host, int port, String destination, String username, String password) throws JMSException {
super(type, acknowledgeType, host, port, destination, username, password);
}
/**
* 接收消息
* @return
* @throws JMSException
*/
public String receive() throws JMSException {
Message msg = consumer.receive();
if( msg instanceof TextMessage ) {
return ((TextMessage) msg).getText();
} else {
return null;
}
}
}
5. 测试类
package org.openwire.test;
import java.io.IOException;
import javax.jms.JMSException;
import javax.jms.Session;
import org.openwire.test.MyMessageWorker.MessageType;
public class App {
public static void main(String[] args) throws Exception {
int size = 10;
for(int i=0; i<4; i++) {
new Thread(new Runnable() {
@Override
public void run() {
//创建消费者
try(MyConsumer mc = new MyConsumer(MessageType.Queue, Session.AUTO_ACKNOWLEDGE);) {
String id = String.valueOf(Math.random()*100).substring(0, 1);
for(int i=0; i<size; i++) {
String msg = mc.receive();
System.out.println(id+"-ReceiveMessage: "+msg);
}
} catch (IOException | JMSException e) {
e.printStackTrace();
}
}
}).start();
}
//创建生产者
try(MyProvider mp = new MyProvider(MessageType.Queue, Session.AUTO_ACKNOWLEDGE);) {
for(int i=0; i<size; i++) {
String msg = "message"+i;
System.out.println("SendMessage: "+msg);
mp.send(msg);
}
}
}
}
6. 测试结果
六、后记
本篇文章只是初步的介绍了一下Apache的ActiveMQ,并没有细致的介绍各个配置文件,只是让读者有一个初步的概念。当真实项目中需要使用的时候,能有备选方案,而不是书到用时方恨少。另外,ActiveMQ只是一个比较简单的消息队列中间件,还有很多高性能的消息队列,例如阿里巴巴的RocketMQ。最后,祝工作顺利。