1、下载安装
linux在activemq.apache.org下载,选择保存文件。自己使用命令解压到自己想要的文件目录中。
在此之前需要确保自己的虚拟机中的确有java环境,因为activemq的底层为java,所以需要java环境支持。
activemq的默认进程端口为61616
命令行 | 效果 |
---|---|
ps -ef|grep activemq|grep -v grep | 查出来的就是结果 |
ps -ef|grep active | 会多带一行grep activemq的结果 |
netstat -anp|grep 61616 | 查询端口是否被使用 |
lsof -i:61616 | 查询端口号占用情况 |
./activemq stop/start/ | 启动或者关闭 |
./activemq start>/soft/activemq.log | 带运行日志地启动方式 |
在windows系统使用虚拟机地址:8161/admin/来访问图形化界面,账号密码为admin
2、java代码连接ActiveMQ
1、队列的写法以及总结
依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xjj</groupId>
<artifactId>activemq_001</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--activemq的依赖-->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.15.9</version>
</dependency>
<!--和spring整合的基础包-->
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-spring</artifactId>
<version>3.16</version>
</dependency>
<!--下面是junit/log4j等基础通用配置-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
procedure端
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* @author 徐敬杰
*/
public class Produce {
public static final String ACTIVEMQ_URL="tcp://192.168.216.128:61616";
public static final String QUEUE_NAME="queue02";
public static void main(String[] args) throws JMSException {
/*1、创建连接工厂,按照给定的url,采用默认的用户名和密码*/
ActiveMQConnectionFactory activeMQConnectionFactory=new ActiveMQConnectionFactory(ACTIVEMQ_URL);
/*2、通过连接工厂,获得连接connection,并且启动*/
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
/*3、创建session会话,第一个参数为事务,第二个为签收*/
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
/*4、创建队列还是主题的目的地*/
Queue queue = session.createQueue(QUEUE_NAME);
/*5、创建消息的生产者*/
MessageProducer producer = session.createProducer(queue);
/*6、通过使用消息生产者生产三条消息发送到mq队列里面*/
for(int i=1;i<=3;i++){
/*7、创建消息*/
TextMessage textMessage = session.createTextMessage("msg02--" + i);
/*8、生产者发送消息*/
producer.send(textMessage);
}
/*9、关闭连接*/
producer.close();
session.close();
connection.close();
System.out.println("消息发布到mq上了,已经完成");
}
}
consume端
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* @author 徐敬杰
*/
public class Consumer {
public static final String ACTIVEMQ_URL="tcp://192.168.216.128:61616";
public static final String QUEUE_NAME="queue02";
public static void main(String[] args) throws JMSException {
//1、创建工厂
ActiveMQConnectionFactory activeMQConnectionFactory=new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2、获取连接
Connection connection=activeMQConnectionFactory.createConnection();
connection.start();
//3、创建session
Session session=connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
//4、创建queue
Queue queue=session.createQueue(QUEUE_NAME);
//5、创建接收者
MessageConsumer consumer = session.createConsumer(queue);
//6、接收消息
while(true){
//这种方法会一直等候,一旦有新消息就会直接取出来
//TextMessage textMessage = (TextMessage)consumer.receive();
//这种方法当取完了队列中的信息以后会等待4秒,如果时间到了还没有信息就退出程序
TextMessage textMessage=(TextMessage)consumer.receive(4000L);
if(textMessage!=null) {
System.out.println(textMessage.getText());
} else{
break;
}
}
consumer.close();
session.close();
connection.close();
}
}
consumer端使用MessageListener
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
import java.io.IOException;
/**
* @author 徐敬杰
*/
public class Consumer {
public static final String ACTIVEMQ_URL="tcp://192.168.216.128:61616";
public static final String QUEUE_NAME="queue02";
public static void main(String[] args) throws JMSException, IOException, InterruptedException {
//1、创建工厂
ActiveMQConnectionFactory activeMQConnectionFactory=new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2、获取连接
Connection connection=activeMQConnectionFactory.createConnection();
connection.start();
//3、创建session
Session session=connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
//4、创建queue
Queue queue=session.createQueue(QUEUE_NAME);
//5、创建接收者
MessageConsumer consumer = session.createConsumer(queue);
consumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
//如果信息不为空,同时也可以强制转型为TextMessage
if(message!=null&&message instanceof TextMessage){
TextMessage textMessage=(TextMessage)message;
try {
System.out.println("接收到了消息:"+textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
//如果使用messageListener的话,就必须暂缓程序,不能直接走完,否则接收不到信息
//不会自动停止,会无限期等待新的信息到来,类似于不带参数的写法
//System.in.read();
//会自动停止,但只停留4s,类似于带参数的写法
Thread.sleep(4000L);
consumer.close();
session.close();
connection.close();
}
}
消费者三大消费情况
情况 | 结果 |
---|---|
先生产,只启动一号消费者 | 1号可以消费到,2号不行 |
先生产,先启动一号消费者,再启动二号消费者 | 1号可以消费到,2号不行 |
先启动两个消费者,再生产6条消息 | 根据谁先启动进入的消费者队列判定,然后大家一条一条轮流交替。 |
2、topic的使用(写法一致,只是将queue改为了topic)
生产者端
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* @author 徐敬杰
*/
public class ProducerTopic {
public static final String ACTIVEMQ_URL="tcp://192.168.216.128:61616";
public static final String TOPIC_NAME="topic01";
public static void main(String[] args) throws JMSException {
ActiveMQConnectionFactory activeMQConnectionFactory=new ActiveMQConnectionFactory(ACTIVEMQ_URL);
Connection connection=activeMQConnectionFactory.createConnection();
connection.start();
Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
Topic topic=session.createTopic(TOPIC_NAME);
MessageProducer producer = session.createProducer(topic);
for(int i=1;i<=3;i++){
TextMessage message =(TextMessage) session.createTextMessage("topic:" + i);
producer.send(message);
}
producer.close();
session.close();
connection.close();
System.out.println("消息发布到topic成功");
}
}
消费者端
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* @author 徐敬杰
*/
public class ConsumerTopic {
public static final String ACTIVEMQ_URL="tcp://192.168.216.128:61616";
public static final String TOPIC_NAME="topic01";
public static void main(String[] args) throws JMSException {
System.out.println("启动了一号消费者");
ActiveMQConnectionFactory activeMQConnectionFactory=new ActiveMQConnectionFactory(ACTIVEMQ_URL);
Connection connection=activeMQConnectionFactory.createConnection();
connection.start();
Session session=connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
Topic topic=session.createTopic(TOPIC_NAME);
MessageConsumer consumer = session.createConsumer(topic);
while(true){
//这种方法会一直等候,一旦有新消息就会直接取出来
TextMessage textMessage = (TextMessage)consumer.receive();
//这种方法当取完了队列中的信息以后会等待4秒,如果时间到了还没有信息就退出程序
//TextMessage textMessage=(TextMessage)consumer.receive(4000L);
if(textMessage!=null){
System.out.println("消息接收成功:"+textMessage.getText());
} else {
break;
}
}
consumer.close();
session.close();
connection.close();
}
}
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
import java.io.IOException;
/**
* @author 徐敬杰
*/
public class ConsumerTopic {
public static final String ACTIVEMQ_URL="tcp://192.168.216.128:61616";
public static final String TOPIC_NAME="topic01";
public static void main(String[] args) throws JMSException, IOException {
System.out.println("启动了一号消费者");
ActiveMQConnectionFactory activeMQConnectionFactory=new ActiveMQConnectionFactory(ACTIVEMQ_URL);
Connection connection=activeMQConnectionFactory.createConnection();
connection.start();
Session session=connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
Topic topic=session.createTopic(TOPIC_NAME);
MessageConsumer consumer = session.createConsumer(topic);
//使用高级特性来写
consumer.setMessageListener((message) -> {
if(message!=null&message instanceof TextMessage){
try {
System.out.println("接收到了消息:"+((TextMessage) message).getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
});
//如果使用messageListener的话,就必须暂缓程序,不能直接走完,否则接收不到信息
//不会自动停止,会无限期等待新的信息到来,类似于不带参数的写法
//System.in.read();
//会自动停止,但只停留4s,类似于带参数的写法
Thread.sleep(4000L);
consumer.close();
session.close();
connection.close();
}
}
3、两大模式比较
比较项目 | topic | queue |
---|---|---|
工作模式 | 订阅发布模式,如果当前没有订阅者,消息将会被丢弃,如果有多个订阅者,那么这些订阅者都会收到消息 | 负载均衡模式,如果当前没有消费者,消息也不会丢弃;如果有多个消费者,那么一条消息也只会发送给其中一个消费者,并且要求消费者ack信息 |
有无状态 | 无状态 | Queue数据默认会在mq服务器上以文件的形式保存,比如Active MQ一般保存在$AMQ_HOME\data\kr-store\data下面。也可以配置成DB存储 |
传递完整性 | 如果没有订阅者,消息会被丢弃 | 消息不会丢弃 |
处理效率 | 由于消息要按照订阅者的数量进行复制,所以处理性能会随着订阅者的增加而明显降低,还要结合不同消息协议自身的性能差异 | 由于一条消息只发送给一个消费者,所以就算消费者再多,性能也不会有明显降低,当然不同消息协议的具体性能也是有差异的。 |
3、JMS
1、结构
- JMS provider:实现JMS接口和规范的消息中间件,也就是我们的MQ服务
- JMS producer:消息生产者,创建呵发送JMS消息的客户端应用
- JMS consumer:消息消费者,接受和处理JMS消息的客户端应用
- JMS message
- 消息头(标题)
- JMSDestination,手动设置地址
- JMSDeliveryMode,设置是否持久化
- JMSExpiration,设置过期时间
- JMSPriority,消息优先级
- JMSMessageID,唯一识别的id
- 消息体(内容)
- 封装具体的消息数据
- 5种消息格式
-
TextMessage:普通字符串消息,包含一个String
-
MapMessage:一个Map类型的消息,key为String类型,而值为Java的基本类型
//发送部分 MapMessage mapMessage=session.createMapMessage(); //要放入什么类型的value就调用什么类型的方法 mapMessage.setString("k1","v1"); producer.send(mapMessage); //接收部分 if(message!=null&&message instanceof MapMessage){ MapMessage mapMessage=(MapMessage)message; try { //接收参数为同理 System.out.println("接收到了map:"+mapMessage.getString("k1")); } catch (JMSException e) { e.printStackTrace(); } }
-
BytesMessage:二进制数组消息,包含一个byte[]
-
StreamMessage:Java数据流消息,用标准流操作来顺序的填充呵读取。
-
ObjectMessage:对象消息,包含一个可序列化的Java对象。
-
- 发送和接受的消息体类型必须一致对应
- 消息属性
-
如果需要去除消息头字段以外的值,那么可以使用消息属性
-
识别/去重/重点标注等操作非常有用的方法
-
相当于消息头的拓展,所有的消息格式都可以发送消息属性
//发送消息属性 for(int i=1;i<=3;i++){ TextMessage textMessage = session.createTextMessage("msg02--" + i); //添加消息属性,可以是别的基本类型 textMessage.setStringProperty("tezheng","tezheng"); producer.send(textMessage); } //接收消息属性 if(message!=null&&message instanceof TextMessage){ TextMessage textMessage=(TextMessage)message; try { System.out.println("接收到了消息:"+textMessage.getText()+"特征为:"+textMessage.getStringProperty("tezheng")); } catch (JMSException e) { e.printStackTrace(); } }
-
- 消息头(标题)
2、消息的可靠性
-
队列持久化
//队列部分,默认策略为执行持久化 //取消持久化。直接将信息删除。 producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT); //执行持久化。没有被消费过的消息,当activemq被关掉以后也还会存在于虚拟机中。 producer.setDeliveryMode(DeliveryMode.PERSISTENT);
-
主题持久化
由于普通的主题进行持久化是没有效果的,因为普通consumer的就是当我程序在启动的状态下,会去接收procedure发送来的消息。所以将consumer改变为subscribe。
import org.apache.activemq.ActiveMQConnectionFactory; import javax.jms.*; import javax.xml.soap.Text; /** * @author 徐敬杰 */ public class TopicProcedurePersistent { public static void main(String[] args) throws JMSException { ActiveMQConnectionFactory activeMQConnectionFactory=new ActiveMQConnectionFactory("tcp://192.168.216.128:61616"); Connection connection=activeMQConnectionFactory.createConnection(); Session session=connection.createSession(false,Session.AUTO_ACKNOWLEDGE); Topic topic=session.createTopic("topic01"); MessageProducer messageProducer = session.createProducer(topic); //整体流程大致一样,只是将connection.start()往后了几步 connection.start(); for(int i=1;i<=3;i++){ TextMessage message = (TextMessage) session.createTextMessage("topic" + i); messageProducer.send(message); } messageProducer.close(); session.close(); connection.close(); System.out.println("消息发布成功"); } }
import org.apache.activemq.ActiveMQConnectionFactory; import javax.jms.*; /** * @author 徐敬杰 */ public class TopicConsumerPersistent { public static void main(String[] args) throws JMSException { ActiveMQConnectionFactory activeMQConnectionFactory=new ActiveMQConnectionFactory("tcp://192.168.216.128:61616"); Connection connection = activeMQConnectionFactory.createConnection(); //设置我的id,这样子就算我下线了,也会保存记录,方便我下次上线的时候将我订阅的信息发送给我 connection.setClientID("张三"); Session session=connection.createSession(false,Session.AUTO_ACKNOWLEDGE); Topic topic=session.createTopic("topic01"); //不能使用普通的consumer,需要改变为subscribe TopicSubscriber topicSubscriber=session.createDurableSubscriber(topic,"备注......"); connection.start(); TextMessage textMessage=(TextMessage) topicSubscriber.receive(); while(textMessage!=null){ System.out.println("接收到了消息:"+textMessage.getText()); textMessage=(TextMessage) topicSubscriber.receive(); } session.close(); connection.close(); } }
-
事务
生产者:
false:只要执行send就会进入到队列。关闭事务,那第二个签收参数的设置需要为有效。
true:先执行send再执行commit,消息才会被真正的提交到队列中。消息需要批量发送,需要缓冲区处理。既能commit,也能rollback。
消费者:
false:只要读取了,就会自动将消息给移出队列。
true:必须要执行commit,否则读取以后也不会将消息移出队列。
-
签收
消费者:
AUTO_ACKNOWLEDGE:自动签收,当读取了信息以后,自动将信息从队列中取出。
CLIENT_ACKNOWLEDGE:手动签收,当读取完信息以后,需要调用消息.acknowledge()方法才能将消息从队列中出队。
-
签收和事物的关系:事务大于签收
在事务性会话中,当一个事务被成功提交则消息被自动签收。如果事务回滚,则消息被再次传送。
非事务性会话中,消息何时被确认取决于创建会话时的应答模式。
3、点对点总结
点对点模型是基于队列的,生产者发消息到队列,消费者从队列接收消息,队列的存在使得消息的异步传输成为可能。者和我们平时给朋友发送短信类似。
如果在Session关闭时有部分消息已被收到但还没有被签收,那当消费者下次连接到相同的队列时,这些消息还会被再次接收。
队列可以长久的保存消息直到消费者收到消息。消费者不需要因为担心而时刻和队列保持激活的连接状态,充分体现了异步传输模式的优势。
4、发布订阅总结
JMS Pub/Sub 模型定义了如何向一个内容节点发布和订阅消息,这些节点被称作为topic。
主题可以被认为是消息的传输中介,发布者发布消息到主题。订阅者从主题订阅消息。
主题使得消息订阅者和消息发布者保持相互独立,不需要接触即可保证消息的传送。
非持久订阅:非持久订阅只有当客户端处于激活状态,也就是和MQ保持连接状态才能收到发送到某个主题的消息。如果消费者处于离线模式,生产者发送的主题消息将会丢失作废,消费者永远不会收到。
持久订阅:客户端首先向MQ注册一个自己的身份ID识别号,当这个客户端处于离线状态时,生产者会为这个ID保存所有发送到主题的消息,当客户再次连接到MQ时,会根据客户的ID得到所有当自己处于离线时生产者发送到主题的消息。
非持久订阅状态下,不能恢复或重新派送一个未签收的消息。
持久订阅才能恢复或重新派送一个未签收的消息。
4、Broker
是什么:相当于一个ActiveMQ的服务器实例。Broker其实就是实现了用代码的形式启动ActiveMQ将MQ嵌入到Java代码中,以便随时用随时启动,在用的时候再去启动这样能节省资源,也保证可靠性。
在activemq的conf目录下有配置文件
可以通过以下的命令来启动
./activemq start xbean:file:/soft/activemq/apache-activemq-5.15.10/conf/activemq02.xml
java自启动一个模拟的activeMQ
<!--需要引入的依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.5</version>
</dependency>
import org.apache.activemq.broker.BrokerService;
/**
* @author 徐敬杰
*/
public class Broker {
public static void main(String[] args) throws Exception {
BrokerService brokerService=new BrokerService();
brokerService.setUseJmx(true);
brokerService.addConnector("tcp://localhost:61616");
brokerService.start();
}
}
5、Spring整合ActiveMQ
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xjj</groupId>
<artifactId>SpringActiveMQ</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--activemq的依赖-->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.15.9</version>
</dependency>
<!--和spring整合的基础包-->
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-spring</artifactId>
<version>3.16</version>
</dependency>
<!--配置迷你版的ActiveMQ-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.5</version>
</dependency>
<!--Spring对JMS的支持-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>4.3.23.RELEASE</version>
</dependency>
<!--activemq所需要的pool包-->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-pool</artifactId>
<version>5.15.9</version>
</dependency>
<!--Spring所需要的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.2</version>
</dependency>
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_2</version>
</dependency>
<!--下面是junit/log4j等基础通用配置-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:lang="http://www.springframework.org/schema/lang"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang.xsd">
<context:component-scan base-package="com.xjj.activemq"/>
<bean id="jmsFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop">
<property name="connectionFactory">
<bean class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://192.168.216.128:61616"/>
</bean>
</property>
<property name="maxConnections" value="100"/>
</bean>
<bean id="destinationQueue" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg index="0" value="spring-active-queue"/>
</bean>
<bean id="destinationTopic" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg index="0" value="spring-active-topic"/>
</bean>
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="jmsFactory"/>
<!-- 根据不同情况,选择注入queue还是topic -->
<property name="defaultDestination" ref="destinationTopic"/>
<property name="messageConverter">
<bean class="org.springframework.jms.support.converter.SimpleMessageConverter"></bean>
</property>
</bean>
</beans>
package com.xjj.activemq;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import javax.jms.TextMessage;
/**
* @author 徐敬杰
*/
@Component
public class SpringActiveMQ_Producer {
@Autowired
private JmsTemplate jmsTemplate;
public static void main(String[] args){
ApplicationContext ctx=new ClassPathXmlApplicationContext("application.xml");
SpringActiveMQ_Producer springActiveMQ_producer=(SpringActiveMQ_Producer)ctx.getBean("springActiveMQ_Producer");
springActiveMQ_producer.jmsTemplate.send(new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
TextMessage textMessage=session.createTextMessage("spring和activemq的整合");
return textMessage;
}
});
System.out.println("添加成功");
}
}
package com.xjj.activemq;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Component;
/**
* @author 徐敬杰
*/
@Component
public class SpringActiveMQ_Consumer {
@Autowired
private JmsTemplate jmsTemplate;
public static void main(String[] args){
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("application.xml");
SpringActiveMQ_Consumer springActiveMQ_consumer=(SpringActiveMQ_Consumer)applicationContext.getBean("springActiveMQ_Consumer");
String value =(String) springActiveMQ_consumer.jmsTemplate.receiveAndConvert();
System.out.println("收到的消息为:"+value);
}
}
实现消费者无需启动,自动监听队列或者主题
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:lang="http://www.springframework.org/schema/lang"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang.xsd">
<context:component-scan base-package="com.xjj.activemq"/>
<bean id="jmsFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop">
<property name="connectionFactory">
<bean class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://192.168.216.128:61616"/>
</bean>
</property>
<property name="maxConnections" value="100"/>
</bean>
<bean id="destinationQueue" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg index="0" value="spring-active-queue"/>
</bean>
<bean id="destinationTopic" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg index="0" value="spring-active-topic"/>
</bean>
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="jmsFactory"/>
<property name="defaultDestination" ref="destinationQueue"/>
<property name="messageConverter">
<bean class="org.springframework.jms.support.converter.SimpleMessageConverter"></bean>
</property>
</bean>
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="jmsFactory"/>
<!-- 注意监听地址和模板的地址的一致性 -->
<property name="destination" ref="destinationQueue"/>
<property name="messageListener" ref="activeMQMessageListener"/>
</bean>
</beans>
package com.xjj.activemq;
import org.springframework.stereotype.Component;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
/**
* @author 徐敬杰
*/
@Component
public class ActiveMQMessageListener implements MessageListener {
@Override
public void onMessage(Message message) {
if(message!=null && message instanceof TextMessage){
TextMessage textMessage=(TextMessage)message ;
try {
System.out.println("OK,获取的信息为:"+((TextMessage) message).getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
6、SpringBoot整合ActiveMQ
1、生产者端的写法
-
创建SpringBoot选择
-
server: port: 7777 spring: activemq: broker-url: tcp://192.168.216.128:61616 user: admin password: admin jms: pub-sub-domain: false #false为queue,true为topic myqueue: SpringBoot-ActiveMQ-Queue
-
package com.xjj.springbootactivemq.config; import org.apache.activemq.command.ActiveMQQueue; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.jms.annotation.EnableJms; import org.springframework.stereotype.Component; import javax.jms.Queue; /** * @author 徐敬杰 */ @Component @EnableJms public class Config { @Value("${myqueue}") private String myQueue; @Bean public Queue queue(){ return new ActiveMQQueue(myQueue); } }
-
package com.xjj.springbootactivemq.util; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jms.core.JmsMessagingTemplate; import org.springframework.stereotype.Component; import javax.jms.Queue; import java.util.UUID; /** * @author 徐敬杰 */ @Component public class Procedure { @Autowired private JmsMessagingTemplate jmsMessagingTemplate; @Autowired private Queue queue; //需要使用Junit等单元测试方法调用使用 public void produceMsg(){ jmsMessagingTemplate.convertAndSend(queue,"SpringBoot和ActiveMQ整合"+ UUID.randomUUID().toString().substring(0,6)); } }
2、消费者的写法
-
创建SpringBoot选择
-
server: port: 7777 spring: activemq: broker-url: tcp://192.168.216.128:61616 user: admin password: admin jms: pub-sub-domain: false #false为queue,true为topic myqueue: SpringBoot-ActiveMQ-Queue
-
package com.xjj.springbootactivemq.util; import org.springframework.jms.annotation.JmsListener; import org.springframework.stereotype.Component; import javax.jms.JMSException; import javax.jms.TextMessage; @Component public class Consumer { //直接启动主启动类即可开启监听功能 @JmsListener(destination = "${myqueue}") public void receive(TextMessage message) throws JMSException{ try { System.out.println("接收到了信息:"+message.getText()); } catch (JMSException e) { e.printStackTrace(); } } }
3、Topic端的procedure
server:
port: 7777
spring:
activemq:
broker-url: tcp://192.168.216.128:61616
user: admin
password: admin
jms:
pub-sub-domain: true #false为queue,true为topic
mytopic: SpringBoot-ActiveMQ-Topic
package com.xjj.springbootactivemq.config;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.command.ActiveMQTopic;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.stereotype.Component;
import javax.jms.Queue;
import javax.jms.Topic;
/**
* @author 徐敬杰
*/
@Component
@EnableJms
public class Config {
@Value("${mytopic}")
private String myTopic;
@Bean
public Topic topic(){
return new ActiveMQTopic(myTopic);
}
}
package com.xjj.springbootactivemq.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Component;
import javax.jms.Topic;
/**
* @author 徐敬杰
*/
@Component
public class Procedure {
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
@Autowired
private Topic topic;
//需要使用junt等方法来调用测试
public void sendMsg(){
jmsMessagingTemplate.convertAndSend(topic,"SpringBoot和ActiveMQ的Topic的demo.....");
System.out.println("发送成功");
}
}
4、topic端的consumer
server:
port: 6666
spring:
activemq:
broker-url: tcp://192.168.216.128:61616
user: admin
password: admin
jms:
pub-sub-domain: true #false为queue,true为topic
mytopic: SpringBoot-ActiveMQ-Topic
package com.xjj.springbootactivemq.util;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import javax.jms.JMSException;
import javax.jms.TextMessage;
@Component
public class Consumer {
//直接启动主启动类即可使用
@JmsListener(destination = "${mytopic}")
public void listen(TextMessage textMessage) throws JMSException {
if(textMessage!=null&&textMessage instanceof TextMessage){
System.out.println("收到的消息为:"+textMessage.getText());
}
}
}
7、通讯协议
ActiveMQ支持的client-broker通讯协议有:TCP、NIO、UDP、SSL、Http(s)、VM。
其中的配置Transport Connector在配置文件的标签中
- TCP,ActiveMQ中默认的消息协议就是openwire
- 这是默认的Broker配置,TCP的Client监听端口为61616
- 在网络传输数据前,必须序列化数据,消息是通过一个叫wire protocol的来序列化为字节流。默认情况下ActiveMQ把wire protocol叫做OpenWire,他的目的是促使网络上的效率和数据快速交互。
- TCP连接的URI形式如:tcp://hostname:port?key=value&key=value,后面的参数为可选项
- TCP传输的优点为:
- TCP协议传输可靠性高,稳定性强
- 高性能,字节流方式传递,效率很高
- 有效性,可用性,应用广泛,支持任何平台
- NIO
-
NIO协议和TCP协议类似,但是NIO更加侧重于底层的访问操作。它允许开发人员对同一资源可有更多的client调用和服务端有更多的负载。
-
适合使用NIO的场景
- 可能有大量的Client去连接到Broker上1,一般情况下,大量的Client去连接Broker是被操作系统的线程所限制的。因此,NIO的实现比TCP需要更少的线程去运行,所以建议使用NIO协议
- 可能对于Broker有一个很迟钝的网络传输,NIO比TCP提供更好的性能。
-
NIO的连接形式:nio://hostname:port?key=value
-
修改为NIO连接有以下的步骤
- 往config文件中添加一行网络连接配置
- 将destination的地址中tcp改为nio,61616改为61618
-
一阶段为BIO+TCP也就是默认设置,二阶段为NIO+TCP也就是加上了NIO阶段,三阶段为NIO+TCP/Mqtt/stomp实现如下:然后java代码中destination中tcp或者nio可以随意写,而端口号则为61608
<transportConnector name="auto+nio" uri="auto+nio://0.0.0.0:61608?maximumConnections=1000&wireFormat.maxFrameSize=104857600&org.apache.activemq.transport.nio.SelectorManager.corePoolSize=20&org.apache.activemq.transport.nio.SelectorManager.maximumPoolSize=50"/>
-
8、消息持久化
前面的消息的可靠性中有三点:持久、事务、签收。但这都是ActiveMQ自带的,如果ActiveMQ死了,那么这些都无用。所以外接消息持久化功能,持久到数据库里面。
为了避免意外宕机以后丢失信息,需要做到重启以后可以恢复消息队列,消息系统一般都采用持久化机制。ActiveMQ的消息持久化机制有JDBC、AMQ、KahaDB和LevelDB,无论使用哪种持久化方式,消息的存储逻辑都是一致的。
就是在发送者将消息发送出去后,消息中心首先将消息存储到本地数据文件、内存数据库或者远程数据库等再试图将信息发送给接收者,成功则将消息从存储中删除,失败则继续尝试发送。
消息中心启动以后首先要检查指定的存储位置,如果有未发送成功的消息,则需要把消息发送出去。
1、连接Mysql
-
先在activemq/lib目录下新增一个mysql的连接驱动包
-
修改配置文件
<persistenceAdapter> <jdbcPersistenceAdapter dataSource="#mysql-ds"/> </persistenceAdapter>
dataSource指定要将引用的持久化数据库的bean名称,createTableOnStartup是否在启动的时候创建数据表,默认值是true,这样每次启动都会去创建数据表了,一般是第一次启动的时候设置为true之后改成false
-
配置MySQL连接池(粘贴在与之间)
<bean id="mysql-ds" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://192.168.216.7:3306/activemq?relaxAutoCommit=true"/> <property name="username" value="root"/> <property name="password" value="1234"/> <property name="poolPreparedStatements" value="true"/> </bean>
-
如果出现问题可以去data目录下的日志文件中查看
遇到了以下问题:windows下的数据库不允许远程访问,即拒绝了activemq的连接请求
通过修改本地mysql来解决问题
use mysql; update user set host = ‘%’ where user = ‘root’; flush privileges; 然后重启mysql和activemq即可
-
出现以下三张表
ACTIVEMQ_MSGS:消息表
列名 备注 ID 自增的数据库主键 CONTAINER 消息的Destination MSGID_PROD 消息发送者的主键 MSG_SEQ 是发送消息的顺序,MSGID_PROD+MSG_SEQ可以组成JMS的MessageID EXPIRATION 消息的过期时间,存储的是从1970-01-01到现在的毫秒数 MSG 消息本体的Java序列化对象的二进制数据 PRIORITY 优先级,从0-9,数字越大,优先级越高 ACTIVEMQ_ACKS:用户存储订阅关系。如果是持久化Topic,订阅者和服务器的订阅关系在这个表保存。
列名 备注 CONTAINER 消息的Destination SUB_DEST 如果是使用Static集群,这个字段会有集群其他系统的信息 CLIENT_ID 每个订阅者都必须有一个唯一的客户端ID用以区分 SUB_NAME 订阅者名称 SELECTOR 选择器,可以选择只消费满足条件的消息。条件可以用自定义属性实现,可支持多属性AND和OR操作。 LAST_ACKED_ID 记录消费过的消息的ID。 ACTIVEMQ_LOCK:表activemq_lock在集群环境中才有用,只有一个Broker可以获得消息,称为Master Broker,其他的只能作为备份等待MasterBroker不可用,才可能成为下一个Maste Broker。这个表用于记录哪个Broker是当前的Master Broker。
2、持久化到数据库
在队列模型中:
当DeliveryMode设置为NON_PERSISTENCE时,消息被保存在内存中
当DeliveryMode设置为PERSISTENCE时,消息保存在broker的相应的文件或者数据库中。当数据被消费掉时,便会自动从broker中删除掉。改变的为msg表。
在主题队列中:
修改的为msg(传入的信息)和ack(订阅者的信息)表,同时信息被消费掉以后也不会被删除。
可能会遇到的问题:
- 数据库jar包:记得需要使用到的相关jar文件放置到ActiveMQ安装目录的lib目录下。mysql-jdbc驱动的jar包和对应的数据库连接池jar包。
- createTablesOnStartup属性:在jdbcPersistenceAdapter标签中设置了createTablesOnStartup属性为true时在第一次启动ActiveMQ时,ActiveMQ服务节点会自动创建所需要的数据表。在启动完成后可以去掉这个属性,或者更改createTablesOnStartup属性为false。
- 下划线问题:“java.lang.IllegalStateException:BeanFactory not initialized or already closed”这是因为操作系统的机器名中有"_"符号。请更改机器名并且重启后即可解决问题。
3、journal
这种方式克服了JDBC Store的不足,JDBC每次消息过来,都需要去写库和读库。
ActiveMQ Journal,使用了高速缓存写入技术,大大提高了性能。
当消费者的消费速度能够及时跟上生产者消息的生产速度时,journal文件能够大大减少需要的写入到DB中的消息。
当生产者生产了1000条信息,这1000条消息会被保存到journal中,如果消费者的消费速度很快的情况下,在journal文件还没有同步到DB之前,消费者已经消费了90%以上的消息,那么这个时候只需要同步剩余的10%的消息到DB。如果消费者的消费速度很慢,那么这个时候journal文件可以使消息以批量方式写道DB。
<persistenceFactory>
<journalPersistenceAdapterFactory
journalLogFiles="4"
journalLogFileSize="32768"
useJournal="true"
useQuickJournal="true"
dataSource="#mysql-ds"
dataDirectory="activemq-data"/>
</persistenceFactory>
改的为msg(传入的信息)和ack(订阅者的信息)表,同时信息被消费掉以后也不会被删除。
可能会遇到的问题:
- 数据库jar包:记得需要使用到的相关jar文件放置到ActiveMQ安装目录的lib目录下。mysql-jdbc驱动的jar包和对应的数据库连接池jar包。
- createTablesOnStartup属性:在jdbcPersistenceAdapter标签中设置了createTablesOnStartup属性为true时在第一次启动ActiveMQ时,ActiveMQ服务节点会自动创建所需要的数据表。在启动完成后可以去掉这个属性,或者更改createTablesOnStartup属性为false。
- 下划线问题:“java.lang.IllegalStateException:BeanFactory not initialized or already closed”这是因为操作系统的机器名中有"_"符号。请更改机器名并且重启后即可解决问题。