写在前头,ActiveMQ开头的几篇博客都是基于windows环境去写,大家更熟悉windows环境。等把该讲的都讲完了,在换到linux系统上去。
序号 | 说明 |
---|---|
C_2021-03-04 | 第一次编写 |
U_2021-03-05 | 更新了持久化、协议的内容,并关联链接 |
U_2021-03-06 | 增加了关于ActiveMQ的基础操作 |
U_2021-03-12 | 增加了基础API详解 |
U_2021-03-15 | 增加了ActiveMQ消息发送的知识 |
文章目录
文档说明
这边博客会基于Windows环境下,对ActiveMQ进行讲解,并且在后续编写ActiveMQ的博客时,将对应的例子引入到这边博客中。因此,这篇博客可以认为是基于windows环境下,对ActiveMQ(不涉及集群)的一篇导读。
举个例子,假设在下面的ActiveMQ配置子章节里,讲到了死信的配置信息。但是作为第一篇讲ActiveMQ的博客,我精力不够一次性写到死信,到时候会把简单的配置写好,然后扔一个跳转到高阶应用的地址。基于这种概述和链接的组合模式,构成了这篇windows下的导读。
后续写到集群时,会迁移至linux系统进行演示。
初识ActiveMQ
在windows环境下安装和启动ActiveMQ
ActiveMQ官网中提供了ActiveMQ的下载选项,下载windows系统对应的压缩包,之后进行解压,解压后文件夹内如下:
- bin
bin文件夹里面是对应的bat脚本,分为32位和64位。 - conf
conf文件夹里是配置文件。 - data
不论是windows还是linux系统,ActiveMQ在使用默认的持久化机制(kahadb)时,对应的文件会存在data文件夹里。 - docs
文档 - examples
examples文件夹里包罗了各式各样的例子。 - lib
jar包 - webapps&webapps-demo
ActiveMQ web控制台
ActiveMQ是一个解压就可以直接使用的程序。解压完成后,双击32位或者64位中对应的 activemq.bat 进行启动。
基础配置
解压后的conf文件夹内容如上所示,其中 jetty.xml是web控制台的配置, activemq.xml则是ActiveMQ的配置文件,其余各式各样的资源文件被配置文件引用了。其他的文档我并没关注什么。
Web控制台配置
-
控制台端口
最常用的第一个配置,web控制台的主机地址和端口号,在配置文件的117行。
-
账户密码
在配置文件一开始,引入了 jetty-realm.properties文件,这里面是web控制台的账户密码。web控制台打开需要身份验证。账号密码就在这个资源文件里面改。
ActiveMQ配置
ActiveMQ的配置文件是activemq.xml,下面讲解的内容都是基于这个文件进行描述。
持久化
ACtimeMQ提供了多种持久化策略,参见持久化策略概述。
在配置ActiveMQ的持久化策略时,是在broker标签下的persistenceAdaptor中。
协议
ActiveMQ支持多种传输协议,查看完整协议,请点击链接。下面会单独挑选出几个进行简单讲解和配置,ActiveMQ常见协议概述。
在配置ActiveMQ的传输协议时,是在broker标签下的transportConnectors
空间大小
在broker标签下,有一个systemUsage标签,里面标注了当前broker对应的物理空间,配置如下。
<systemUsage>
<systemUsage>
<memoryUsage>
<!--Java虚拟机可用内存百分比-->
<memoryUsage percentOfJvmHeap="70" />
</memoryUsage>
<storeUsage>
<!--当前broker最大存储空间-->
<!--即使服务器对物理空间实现了热插拔,但是broker只会之别在启动时,设备提供的可用大小-->
<storeUsage limit="100 gb"/>
</storeUsage>
<tempUsage>
<!--当最大存储空间用完了之后,会启用这部分临时空间-->
<tempUsage limit="50 gb"/>
</tempUsage>
</systemUsage>
</systemUsage>
其他
这部分内容,随着使用在逐步往里加。
hello activemq
这个子章节里会讲解ActiveMQ最基本的代码,并且根据这个demo解释一些ActiveMQ中的角色。
角色
- broker:消息服务器,作为server提供消息核心服务
- provider:消息的生产者,由会话创建的一个对象,用于把消息发送到一个目的地(destination)
- consumer:消息的消费者,由会话创建的一个对象,用于从消息从目的地(destination)提取出来。
提取消息的方法由两种,一种同步阻塞,一种异步不阻塞。
同步阻塞:consumer的receive方法。在调用这个方法后,线程会一直阻塞直到收到消息。
异步方法:通过consumer的messageListener接口,重写了onMessage方法。当有消息被提取的时候,会自动回调这个onMessage方法。 - p2p:点对点的传播方式,Queue。
消息生产者生产消息后发送到Queue(destination的子类)中,之后消息消费者再从Queue中去提取消息。Queue中的消息一旦被一个消费者消费后,Queue就不在存储这个消息。Queue支持多个消费者,但是对一个消息而言,只会有一个消费者可以消费,其他的消费者不能再消费这个消息。当消费者不存在的时候,消息会持续存在,直到有消费者(前提是消息没有过期)。 - pub/sub:发布订阅的形式,Topic。
消息生产者生产消息后发送到Topic(destination的子类)中,broker会一次性把消息推送给所有的consumer。除了这点不同之外,在Topic下,不论是否有消费者去提取消息,broker都不会类似Queue这样保留消息等候消费者去提取,也就是说,在Topic中,如果消息生产后没有消费者去消费,当有新的消费者来了之后,也无法从Topic取到这个消息(持续订阅除外)。 - connectionFactory:连接工厂,JMS中用它创建连接。因为ActiveMQ满足JMS规范,所以ActiveMQ也是通过ConnectionFactory创建连接。
- Connection:连接,JMS Connection封装了用户与JMS提供者(供应商)之间的一个虚拟连接。
- Destination:目的地,更直观的就是队列的名称,再具体些,就是在实例化Queue或者Topic的时候给定的名称。在点对点传输中,destination被称为queue,在发布订阅中,被称为topic。
- session:JMS Session,会话,是生产与消费消息的一个单线程上下文。
Session(会话) 用于创建provider(消息生产者)、consumer(消息消费者)、message(消息)、Queue(消息队列)和Topic(发布订阅)。
Session(会话)提供了一个事务性的上下文,在这个上下文中,一组发送和接收操作组合到一个原子操作中。
步骤
在使用ActiveMQ的时候与使用JDBC非常类似,都是有固定步骤的。
- 创建连接工厂 ConnectionFactory
- 创建连接 Connection
- 开启连接 Connection.start()
注意,如果是provider(生产者)端,不开启连接并不影响使用,但是在consumer(消费者)端,不开启连接会影响后面的使用。 - 创建会话
- 用Session创建Destination,在创建destination的时候,实际就是创建Queue或者Topic。
创建Session和Destination完成之后,需要根据自己的场景有两处细分。
如果是provider端:
- 创建完成Destination之后,在用Session创建provider,此时需要把刚才创建的destination作为参数。
- 用Session创建Message。Message有很多具体的类型来简化我们的操作,比如,TextMessage就是用来传递String类型的信息。
- 用provider发送Message到ActiveMQ。
如果是consumer端:
- 用Session创建consumer,此时需要把刚才创建的Destination作为参数。
- 用创建好的Consumer从ActiveMQ中提取消息(Message)。这里就会出现在上面介绍Consumer时提到的,提取消息有两种方法,分别是同步阻塞和异步不阻塞两种方式。
最后,不论是consumer或者是provider,当不再需要的时候,还需要关闭连接。
代码
代码示例采用了Queue这种形式编写。
代码结构如下所示,是一个简单的springboot项目。这里使用Springboot,只是为了方便启动。在创建Springboot时,选择了Web和ActiveMQ5这两个Starter。之所以选择Web是为了方便演示。
注意,这个项目并没有演示Springboot整合ActiveMQ,所以在application.properties中没有写任何有关于ActiveMQ的配置信息,它是个空的。
Controller代码如下,在Controller中提供了一个方法send。在send方法中,调用了发送和收取两个方法。其实,接收的方法没必要写在这里被调用,因为我这边实现Consumer提取消息的时候,使用的是异步回调的形式,而非单次同步阻塞的方式。写在这里,纯粹是为了降低读代码的难度。
package com.phl.spring_active_mq.simpledemo.controller;
import com.phl.spring_active_mq.simpledemo.service.ReceiveService;
import com.phl.spring_active_mq.simpledemo.service.SendService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.jms.JMSException;
/**
* 在simple demo 包里面,做了一个基于web演示的ActiveMQ demo。
* 在这个demo中,并不是继承springboot+ActiveMQ,只是单纯的使用ActiveMQ,springboot只是作为一个启动容器存在。
*
*/
@RestController
public class MainTest {
@Autowired
SendService sendService;
@Autowired
ReceiveService receiveService;
/**
* 这个方法中,给destination中发送信息msg
* @param destination 目的地
* @param msg 消息
* @return
*/
@RequestMapping("/send/{destination}/{msg}")
public String send(@PathVariable("destination") String destination, @PathVariable("msg") String msg) throws JMSException {
sendService.sendQueue(destination,msg);
receiveService.receiveQueue(destination);
return "OK";
}
}
工具类Util代码如下,在Util中,完成了上述讲解步骤中的内容,封装了对应的方法中,便于理解。
在这个例子中,除了实例化连接工厂的时候需要提供一个具体的实现类之外,其他地方如Connection,Provider,Consumer,Session等都是使用 javax.jms包里的接口。强调只是为了再次提醒,因为ActiveMQ满足JMS规范(其实就是实现了JMS提供的接口)所以可以粗放地使用这些接口。其实,对于ActiveMQ而言,全都使用ActiveMQ提供的对象的话更好,它提供了JMS中没提供的一些方法。
package com.phl.spring_active_mq.simpledemo.util;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.springframework.stereotype.Component;
import javax.jms.*;
@Component
public class ConnectionUtil {
static String brokerUrl = "tcp://localhost:61616";
private Connection queueConnection;
private Session session;
{
/*
* ActiveMQ在使用的过程中,非常类似使用JDBC,同样存在固定的顺序。
*/
// 创建连接工厂 ConnectionFactory
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(brokerUrl);
// 创建连接 Connection
try {
this.queueConnection = connectionFactory.createConnection();
} catch (JMSException e) {
e.printStackTrace();
}
// 开启连接
// 注意,这个connection在使用的时候,如果是provider端,不去start也可以正常使用。
// 但是,在consumer端,不进行start,不能正常使用。
try {
this.queueConnection.start();
} catch (JMSException e) {
e.printStackTrace();
}
// 创建会话
try {
this.session = this.queueConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
} catch (JMSException e) {
e.printStackTrace();
}
}
/**
* 返回 session
* @return
*/
public Session getSession() {
return this.session;
}
/**
* 创建Queue
* @param destination
* @return
* @throws JMSException
*/
public Queue getQueue(String destination) throws JMSException {
Queue queue = this.getSession().createQueue(destination);
return queue;
}
/**
* 创建生产者
* @param destination
* @return
* @throws JMSException
*/
public MessageProducer getProducer(String destination) throws JMSException {
return this.getSession().createProducer(this.getQueue(destination));
}
/**
* 创建消费者
* @param destination
* @return
* @throws JMSException
*/
public MessageConsumer getConsumer(String destination) throws JMSException {
return this.getSession().createConsumer(this.getQueue(destination));
}
public void stop() throws JMSException {
this.session.close();
this.queueConnection.stop();
}
}
下面就分别是provider和consumer的代码。
Provider端:
package com.phl.spring_active_mq.simpledemo.service;
import com.phl.spring_active_mq.simpledemo.util.ConnectionUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
@Service
public class SendService {
@Autowired
ConnectionUtil util;
/**
* 发送信息到ActiveMQ
* @param destination
* @param msg
* @throws JMSException
*/
public void sendQueue(String destination, String msg) throws JMSException {
Session session = util.getSession();
MessageProducer producer = this.util.getProducer(destination);
TextMessage textMessage = session.createTextMessage(msg);
producer.send(textMessage);
}
}
Consumer端:
package com.phl.spring_active_mq.simpledemo.service;
import com.phl.spring_active_mq.simpledemo.util.ConnectionUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.jms.*;
@Service
public class ReceiveService {
@Autowired
ConnectionUtil util;
public void receiveQueue(String destination) throws JMSException {
MessageConsumer consumer = this.util.getConsumer(destination);
/**
* 得到consumer之后,有两种方法去消费 ActiveMQ 中的信息。
* 方法一:
* consumer.receive();
* 这个方法在从 ActiveMQ 收到消息之前,会在这里阻塞住,在实际生产中使用并不多。
* 方法二:
* 创建一个messageListener,当有消息过来之后,会回调listener中的onMessage方法。
* 在 Java8 及更高版本中,下面的代码可以简化成lambda表达式,但是这个表达式太简洁,不利于不熟悉的人读代码。
*/
consumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
try {
String msg = ((TextMessage) message).getText();
System.out.println("ActiveMQ 中取出来的数据是 :: " + msg);
} catch (JMSException e) {
e.printStackTrace();
}
}
});
}
}
运行结果
上面是所有的代码,下面结合着ActiveMQ的web控制台进行结果演示。
上图就是在ActiveMQ启动后,它提供的web控制台。控制台的账户密码在上面讲配置的时候讲到了。进入web控制台之后,点击“manage ActiveMQ broker”,进入管理页面。
进来之后根据自己的需要选择Queue或者Topic进入相应页面。
之后,启动springboot项目,在浏览器中访问send方法。因为Controller中已经设置了,url倒数第二位是Destination的名字,最后以为是msg的内容,因此当我们访问之后,在之前的外部控制台就能看到对应信息,同时,在IDEA的consolo中我也做了对应的输出。
多刷新几次页面后,发现这个情况:
consumer的个数增加了。那是因为例子里没有关闭连接,并且consumer在send方法里进行了实例化所致。
基础API详解
在上面的例子中,已经做了一个基于Queue的简单应用。现在,会基于上面的例子,将常用API进行讲解和测试。基础API讲解&一些对象解释。
消息发送
关于消息发送的内容已经完成编写,ActiveMQ_消息发送。
一些高级应用和高级场景
内容正在编写, 请稍后