入门
activemq 是对jms的规范实现,jms 模型图
支持的两种基本模型 Point-to-Point(queue),Publish/Subscribe(topic),以及衍生的几种组合模型
jms还有个broker(代理)的概念,一个单纯queue或者topic只对消息进行处理,但是broker在对消息进行处理的同时,还能对消息事故进行监控,记录,还可以自定义对消息的一些处理行为,broker提供一个独立完善的消息编程环境.一个activemq的服务就想当于一个broker(activemq,xml配置文件中有broker节点)
相关名词介绍
- Provider/MessageProvider:生产者
- Consumer/MessageConsumer:消费者
- PTP:Point To Point,点对点通信消息模型
- Pub/Sub:Publish/Subscribe,发布订阅消息模型
- Queue:队列,目标类型之一,和PTP结合
- Topic:主题,目标类型之一,和Pub/Sub结合
- ConnectionFactory:连接工厂,JMS用它创建连接
- Connnection:JMS Client到JMS Provider的连接
- Destination:消息目的地,由Session创建
- Session:会话,由Connection创建,实质上就是发送、接受消息的一个线程,因此生产者、消费者都是Session创建
- SreamMessage -java 原始值的数据流
- MapMessage -Map 键值对
- ObjectMessage -序列化的java对象
- BytesMessage -字节流
启动rabbitmq
启动rabbitmq,以windows为例,
下载windows压缩包
,解压进入bin\win64目录下执行activemq.bat,即可启动,访问http://localhost:8161/admin/ ,登录名与密码均为admin.
win64下有个InstallService.bat表示注册服务,开机启动
activemq内嵌了一个broker,启动方式很简单,(具体一些其他可以自行配置),开发一般不使用内嵌
BrokerService broker = new BrokerService();
// configure the broker
broker.addConnector("tcp://localhost:61616");
broker.start();
编程代码
本次选用采用springboot 开发环境
<?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>zl</groupId>
<artifactId>jms</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>jms</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--spring boot集成activemq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
<!--activemq 所需要jar-->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.15.2</version>
</dependency>
<!--使用内置的broker -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-broker</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!--test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
queue
queue 即消费者,生产者均为queue
p:
package jms.producer;
import javax.jms.Connection;
import javax.jms.DeliveryMode;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.command.ActiveMQQueue;
public class ProducerQueueDurable {
public static void main(String[] args) {
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin","admin","tcp://localhost:61616");
Connection connection = null ;
try {
connection = factory.createConnection();
connection.start();
//ActiveMQSession
Session session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
//Topic topic = session.createTopic("hello");
//ActiveMQQueue
ActiveMQQueue queue = (ActiveMQQueue) session.createQueue("queue_durable");
//ActiveMQMessageProducer
MessageProducer producer = session.createProducer(queue);
System.out.println(producer.getPriority());//4 0-4是普通消息,5-9是加急消息
System.out.println(producer.getTimeToLive());//0-不过期
System.out.println(producer.getDeliveryMode());//2
System.out.println(producer.getDisableMessageID());//false
System.out.println(producer.getDisableMessageTimestamp());//false
for (int i = 0; i < 50; i++) {
TextMessage message = session.createTextMessage("message "+i+" :hello world");
producer.send(message, DeliveryMode.PERSISTENT, 4, 0);
}
// session.commit();//对开启事物才有效
} catch (JMSException e) {
e.printStackTrace();
} finally {
if (connection != null) {
try {
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
}
c:
package jms.consumer;
import java.io.File;
import java.util.Arrays;
import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.MessageConsumer;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.ActiveMQSession;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.commons.io.FileUtils;
public class ConsumerQueueDurable {
public static void main(String[] args) {
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin","admin","tcp://localhost:61616");
Connection connection = null ;
try {
connection = factory.createConnection();
//connection.setClientID("first_client_id");
connection.start();
//ActiveMQSession
ActiveMQSession session = (ActiveMQSession) connection.createSession(false, ActiveMQSession.INDIVIDUAL_ACKNOWLEDGE);
//ActiveMQQueue
ActiveMQQueue queue = (ActiveMQQueue) session.createQueue("queue_durable");
//ActiveMQMessageConsumer
//(MessageListener(监听),MessageSelector)
MessageConsumer consumer = session.createConsumer(queue);
File file = new File("C:\\Users\\Administrator\\Desktop\\temp\\t.txt");
consumer.setMessageListener((message)->{
if (message instanceof TextMessage) {
TextMessage mes = (TextMessage)message;
try {
String text = mes.getText();
System.out.println("consumer收到的消息:" + mes.getText());
FileUtils.writeLines(file, Arrays.asList(text),true);
mes.acknowledge();
} catch (Exception e) {
e.printStackTrace();
}
}
});
/* while(true) {
TextMessage message = (TextMessage) consumer.receive();
//consumer.receive(timeout),consumer.receiveNoWait()
if (message != null) {
message.acknowledge();
System.out.println("收到的消息:" + message.getText());
}
}*/
} catch (JMSException e) {
e.printStackTrace();
} /*finally {
if (connection != null) {
try {
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}*/
}
}
Topic
topic即消费者与生产者均为topic
p:
package jms.producer;
import javax.jms.Connection;
import javax.jms.DeliveryMode;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.command.ActiveMQTopic;
public class ProducerTopic {
public static void main(String[] args) {
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin","admin","tcp://localhost:61616");
Connection connection = null ;
try {
connection = factory.createConnection();
connection.start();
//ActiveMQSession
Session session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
//Topic topic = session.createTopic("hello");
//ActiveMQQueue
ActiveMQTopic queue = (ActiveMQTopic) session.createTopic("queue_topic");
//ActiveMQMessageProducer
MessageProducer producer = session.createProducer(queue);
for (int i = 0; i < 100; i++) {
TextMessage message = session.createTextMessage("message "+i+" :hello world");
producer.send(message, DeliveryMode.PERSISTENT, 4, 0);
}
// session.commit();//对开启事物才有效
} catch (JMSException e) {
e.printStackTrace();
} finally {
if (connection != null) {
try {
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
}
c:
package jms.consumer;
import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.MessageConsumer;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.ActiveMQSession;
import org.apache.activemq.command.ActiveMQTopic;
public class ConsumerTopic {
public static void main(String[] args) {
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin","admin","tcp://localhost:61616");
Connection connection = null ;
try {
connection = factory.createConnection();
connection.setClientID("first_client_id");
connection.start();
//ActiveMQSession
ActiveMQSession session = (ActiveMQSession) connection.createSession(false, ActiveMQSession.INDIVIDUAL_ACKNOWLEDGE);
//ActiveMQQueue
ActiveMQTopic topic = (ActiveMQTopic) session.createTopic("queue_topic");
//ActiveMQMessageConsumer
//(MessageListener(监听),MessageSelector)
MessageConsumer consumer = session.createDurableSubscriber(topic, "comsumer1");
MessageConsumer consumer1 = session.createDurableSubscriber(topic,"comsumer2");
consumer.setMessageListener((message)->{
if (message instanceof TextMessage) {
TextMessage mes = (TextMessage)message;
try {
mes.acknowledge();
System.out.println("consumer收到的消息:" + mes.getText());
} catch (Exception e) {
e.printStackTrace();
}
}
});
consumer1.setMessageListener((message)->{
if (message instanceof TextMessage) {
TextMessage mes = (TextMessage)message;
try {
mes.acknowledge();
System.out.println("consumer1收到的消息:" + mes.getText());
} catch (Exception e) {
e.printStackTrace();
}
}
});
} catch (JMSException e) {
e.printStackTrace();
}
}
}
虚拟topic
虚拟topic,即生产者为topic,消费者为queue
p:
package jms.producer;
import javax.jms.Connection;
import javax.jms.DeliveryMode;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.command.ActiveMQTopic;
public class ProducerVirtual {
public static void main(String[] args) {
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin", "admin", "tcp://localhost:61616");
Connection connection = null;
try {
connection = factory.createConnection();
connection.start();
// ActiveMQSession
Session session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
// Topic topic = session.createTopic("hello");
ActiveMQTopic topic = (ActiveMQTopic) session.createTopic("VirtualTopic.t");
// ActiveMQMessageProducer
MessageProducer producer = session.createProducer(topic);
System.out.println(producer.getPriority());// 4 0-4是普通消息,5-9是加急消息
System.out.println(producer.getTimeToLive());// 0-不过期
System.out.println(producer.getDeliveryMode());// 2
System.out.println(producer.getDisableMessageID());// false
System.out.println(producer.getDisableMessageTimestamp());// false
for (int i = 0; i < 10; i++) {
TextMessage message = session.createTextMessage("message " + i + " :hello world");
producer.send(message, DeliveryMode.PERSISTENT, 4, 0);
}
// session.commit();//对开启事物才有效
} catch (JMSException e) {
e.printStackTrace();
} finally {
if (connection != null) {
try {
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
}
c:
package jms.consumer;
import java.io.File;
import java.util.Arrays;
import javax.jms.JMSException;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.ActiveMQSession;
import org.apache.activemq.RedeliveryPolicy;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.commons.io.FileUtils;
public class ConsumerVirtualTopic {
public static void main(String[] args) throws Exception {
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("admin", "admin", "tcp://localhost:61616");
ActiveMQConnection connection = null;
try {
connection = (ActiveMQConnection) factory.createConnection();
// connection.setClientID("first_client_id");
RedeliveryPolicy policy = connection.getRedeliveryPolicy();
/** destination = null,
* collisionAvoidanceFactor = 0.15,
* maximumRedeliveries = 6,
* maximumRedeliveryDelay = -1,
* initialRedeliveryDelay = 1000,
* useCollisionAvoidance = false,
* useExponentialBackOff = false,
* backOffMultiplier = 5.0,
* redeliveryDelay = 1000
* */
policy.setMaximumRedeliveries(0);//异常不进行重试
connection.start();
// ActiveMQSession
ActiveMQSession session = (ActiveMQSession) connection.createSession(false,
ActiveMQSession.INDIVIDUAL_ACKNOWLEDGE);
// ActiveMQQueue
ActiveMQQueue queueA = (ActiveMQQueue) session.createQueue("Consumer.A.VirtualTopic.t?consumer.prefetchSize=10");
ActiveMQQueue queueB = (ActiveMQQueue) session.createQueue("Consumer.B.VirtualTopic.t?consumer.prefetchSize=10");
// ActiveMQMessageConsumer
// (MessageListener(监听),MessageSelector)
File fileA = new File("C:\\Users\\Administrator\\Desktop\\temp\\A.txt");
File fileB = new File("C:\\Users\\Administrator\\Desktop\\temp\\B.txt");
session.createConsumer(queueA, (message) -> {
if (message instanceof TextMessage) {
TextMessage mes = (TextMessage) message;
try {
String text = mes.getText();
System.out.println("consumerA收到的消息:" + mes.getText());
// TimeUnit.SECONDS.sleep(1);
FileUtils.writeLines(fileA, Arrays.asList(text), true);
System.out.println("excute " + Integer.parseInt(text.charAt(8)+""));
mes.acknowledge();
/* if (Integer.parseInt(text.charAt(8)+"")%2==0) {
} else {
session.recover();
}*/
} catch (Exception e) {
e.printStackTrace();
}
}
});
session.createConsumer(queueB, (message) -> {
if (message instanceof TextMessage) {
TextMessage mes = (TextMessage) message;
try {
String text = mes.getText();
System.out.println("consumerB收到的消息:" + mes.getText());
FileUtils.writeLines(fileB, Arrays.asList(text), true);
mes.acknowledge();
} catch (Exception e) {
e.printStackTrace();
}
}
});
} catch (JMSException e) {
e.printStackTrace();
}
}
}
自定义虚拟topic,activemq.xml的broker中设置
<destinationInterceptors>
<virtualDestinationInterceptor>
<virtualDestinations>
<virtualTopic name=">" prefix="VirtualTopicConsumers.*." selectorAware="false"/>
</virtualDestinations>
</virtualDestinationInterceptor>
</destinationInterceptors>
默认的虚拟topic
name="VirtualTopic.>",
prefix="Consumer.*."
其他属性
activemq 重发
重发触发条件
- 开启事物,进行rollback()
- 开启事物,session关闭,但是还没有commit
- session 使用
CLIENT_ACKNOWLEDGE
并且进行Session.recover()
- 超时
重发主要通过RedeliveryPolicy控制,其相关属性如下
useExponentialBackOff:控制backOffMultiplier(重发间隔递增的倍数)
useCollisionAvoidance:控制collisionAvoidanceFactor(设置重发间隔时间波动范围)
initialRedeliveryDelay:初始重发间隔时间
redeliveryDelay:重发间隔时间(initialRedeliveryDelay=0才会生效)
maximumRedeliveries:重发次数(-1,不限制次数)
maximumRedeliveryDelay:重发最大的时间间隔(-1,不限制)
代码控制
ActiveMQConnection connection ... // Create a connection
RedeliveryPolicy queuePolicy =newRedeliveryPolicy();
queuePolicy.setInitialRedeliveryDelay(0);
queuePolicy.setRedeliveryDelay(1000);
queuePolicy.setUseExponentialBackOff(false);
queuePolicy.setMaximumRedeliveries(2);
RedeliveryPolicy topicPolicy =newRedeliveryPolicy();
topicPolicy.setInitialRedeliveryDelay(0);
topicPolicy.setRedeliveryDelay(1000);
topicPolicy.setUseExponentialBackOff(false);
topicPolicy.setMaximumRedeliveries(3);
// Receive a message with the JMS API
RedeliveryPolicyMap map = connection.getRedeliveryPolicyMap();
map.put(newActiveMQTopic(">"), topicPolicy);
map.put(newActiveMQQueue(">"), queuePolicy);
重发失败后将进入死信队列(默认Activemq.DLQ)
自定义DLQ
<broker>
<destinationPolicy>
<policyMap>
<policyEntries>
<!-- Set the following policy on all queues using the'>'wildcard -->
<policyEntry queue=">">
<deadLetterStrategy>
<!--
Use the prefix'DLQ.'forthe destination name, and make
the DLQ a queue rather than a topic
-->
<individualDeadLetterStrategy queuePrefix="DLQ."useQueueForQueueMessages="true"/>
</deadLetterStrategy>
</policyEntry>
</policyEntries>
</policyMap>
</destinationPolicy>
</broker>
通配符
一般情况下,我们使用层次结构的方式来组织队列,比如A.B.C.D,这样便于归类和管理。
我们也可以使用通配符来配置或是操作多个队列。
通配符有三个:
. 用来分隔路径
* 用来匹配路径中的一节
> 用来匹配任意节的路径
示例:
PRICE.> 以PRICE.开头
自定义分隔符(5.5版本后)
<plugins> ..... <destinationPathSeparatorPlugin/> </plugins>
定义以上FOO.BAR.* 可由 FOO/BAR/*表示
prefetch
prefetch指消息的预获取,指消费者可获取的最大消息量,通过ActiveMQPrefetchPolicy策略控制
默认
- persistent queues (default value: 1000)
- non-persistent queues (default value: 1000)
- persistent topics (default value: 100)
- non-persistent topics (default value: Short.MAX_VALUE - 1)
java代码
private int queuePrefetch=1000;
private int queueBrowserPrefetch=500;
private int topicPrefetch=32767;
private int durableTopicPrefetch=100;
private int optimizeDurableTopicPrefetch=1000;
private int maximumPendingMessageLimit;
可通过
ActiveMQConnectionFactory
或
ActiveMQConnection
设置
对所有consumer有效
tcp://localhost:61616?jms.prefetchPolicy.all=50
对queue有效
tcp://localhost:61616?jms.prefetchPolicy.queuePrefetch=1
通过Destination设置
queue =newActiveMQQueue("TEST.QUEUE?consumer.prefetchSize=10");
consumer = session.createConsumer(queue);
集成jndi
新建jndi.propeties文件
java.naming.factory.initial = org.apache.activemq.jndi.ActiveMQInitialContextFactory
java.naming.provider.url = tcp://localhost:61616
queue.MyQueue = example.MyQueue
topic.MyTopic = example.MyTopic
编码
package jms.jndi;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicSession;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class JmsJndi {
public static void main(String[] args) {
Context ctx = null;
TopicConnection conn=null;
try {
ctx = new InitialContext();// 默认读取jndi.properties可自定义配置文件InitialContext(Hashtable<?,?>environment)
TopicConnectionFactory factory = (javax.jms.TopicConnectionFactory) ctx.lookup("ConnectionFactory");
conn = factory.createTopicConnection("admin", "admin");
conn.start();
Topic mytopic = (javax.jms.Topic) ctx.lookup("MyTopic");
TopicSession session = conn.createTopicSession(false, TopicSession.AUTO_ACKNOWLEDGE);
MessageConsumer consumer = session.createSubscriber(mytopic);
consumer.setMessageListener((message)->{
if (message instanceof TextMessage) {
TextMessage m = (TextMessage)message;
try {
System.out.println("A receive" + m.getText());
} catch (Exception e) {
e.printStackTrace();
}
}
});
MessageConsumer consumer1 = session.createSubscriber(mytopic);
consumer1.setMessageListener((message)->{
if (message instanceof TextMessage) {
TextMessage m = (TextMessage)message;
try {
System.out.println("B receive" + m.getText());
} catch (Exception e) {
e.printStackTrace();
}
}
});
MessageProducer producer = session.createProducer(mytopic);
for (int i = 0; i < 10; i++) {
producer.send(session.createTextMessage(String.format("the %d time hello world",i)));
System.out.println(String.format(" send messgae : the %d time hello world",i));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ctx != null) {
try {
ctx.close();
} catch (NamingException e) {
e.printStackTrace();
}
}
/* if (conn != null) {
try {
conn.close();
} catch (JMSException e) {
e.printStackTrace();
}
}*/
}
}
}
持久化
持久化对象: queue, topic, 持久化subscribe,持久化消息 以及彼此之间的关系.
activemq中的factory,connection,session都是随用随建,用完就关,并不需要持久化.queue,topic类型消息通道实例,大多不轻易改变,有持久化必要,
subscribe(durable,offline durable,non-durable三种)根据实用场景选择是否有必要持久化 , 消息作为最重要的载体,为保证消息不丢失,可选持久化.
activemq 提供了多种
持久化,
KahaDB(推荐)
,
AMQ Message Store(
Replicated LevelDB Store),
JDBC,Memory Message Store(内存,不持久化),前面两种是基于文件存储的快速(High performance journal)存储,而JDBC则处理较慢,但两者可同时使用.
5.3版本之后推荐使用KahaDb(默认的持久化),相比AMQ其性能与恢复能力更强,但AMQ更快,
kahaDb
设置
<broker brokerName="broker">
<persistenceAdapter>
<kahaDB directory="activemq-data" journalMaxFileLength="32mb"/>
</persistenceAdapter>
</broker>
默认的路径${activemq.data}/kahadb,下有4个文件(
参考)
- db.data消息的索引文件,使用B-Tree作为索引指向db-*.log里面存储的消息
- db.redo消息恢复
- db-*.log存储消息(message data,Destinations,订阅关系,事务...)
- lock 锁
重要属性:
- indexWriteBatchSize 默认值1000,当Metadata Cache中更新的索引到达了1000时,才同步到磁盘上的Metadata Store中。不是每次更新都写磁盘,而是批量更新写磁盘,比较写磁盘的代价是很大的
- indexCacheSize 默认值10000,(number of index pages cached in memory),在内存中最多分配多个页面来缓存index。缓存的index越多,命中的概率就越大,检索的效率就越高。
- journalMaxFileLength 默认值32MB,当存储的消息达到32MB时,新建一个新文件来保存消息。这个配置对生产者或消息者的速率有影响。比如,生产者速率很快而消费者速率很慢时,将它配置得大一点比较好。
- enableJournalDiskSyncs 默认值true,默认采用同步写磁盘,即消息先存储到磁盘中再向Producer返回ACK
- cleanupInterval 默认值30000ms,当消息被消息者成功消费之后,Broker就可以将消息删除了。
- checkpointInterval 默认值5s,每隔5s将内存中的Index(Metadata Cache)更新到磁盘的Index文件中(Metadata Store)
游标
之前的在消息分发之前,都是在内存中保持其引用,当大量消息就有可能撑爆内存,5.0版本之后,引入新的内存模式,在空间可用时允许消息从存储设备中以页为单位进入(对持久化消息使用存储游标),Store based,VM Cursor,File based Cursor
5.0之后,activemq对游标的运用分两种,消费者跟的上生产的速度(不使用游标),消费者跟不上生产的速度(使用游标)--消息都会先存储
消费者速率过快(dispatching message for fast consumers)
消费者速率过慢(dispatching message for slowconsumers)
Store Based
默认的存储方式,非持久化消息直接被传递给游标,所以针对这些消息,基于存储的游标嵌入了一个基于文件的游标
VM Cursor
基于内存优势在于快,但是在针对长时间不活跃或者处理非常慢的消费者,效率很低
File based Cursor
基于文件的游标是从虚拟内存游标衍生而来的。当代理的内存达到限度之后,它可以将消息写入磁盘上的临时文件。这种类型的游标适用的场景是,消息存储相对要慢,但是消费者要快一些。通过在磁盘上做缓冲,消息代理可以在应对消息爆发的时候,不需要从慢存储中读取。
配置
topic subscribes
对所有的主题来说,每一个订阅者都有一个分发队列和挂起的游标。可以针对持久订阅者和短暂订阅者分别配置不同的策略,例如:
<destinationPolicy>
<policyMap>
<policyEntries>
<policyEntry topic="org.apache.>" producerFlowControl="false" memoryLimit="1mb">
<dispatchPolicy>
<strictOrderDispatchPolicy />
</dispatchPolicy>
<deadLetterStrategy>
<individualDeadLetterStrategy topicPrefix="Test.DLQ." />
</deadLetterStrategy>
<pendingSubscriberPolicy>
<vmCursor />
</pendingSubscriberPolicy>
<pendingDurableSubscriberPolicy>
<vmDurableCursor/>
</pendingDurableSubscriberPolicy>
</policyEntry>
</policyEntries>
</policyMap>
</destinationPolicy>
有效的订阅者游标类型是vmCursor 和fileCursor。默认情况下是基于存储的游标。
有效的持久订阅者类游标类型是storeDurableSubscriberCursor,vvmDurableCursor 和 fileDurableSubscriberCursor。默认情况下是基于存储的游标。
Queues
有效的持久订阅者类游标类型是storeDurableSubscriberCursor,vvmDurableCursor 和 fileDurableSubscriberCursor。默认情况下是基于存储的游标。
对于队列来说,每一个目的地都有一个单独的分发队列和挂起队列,所以配置略微有些不同:
<destinationPolicy>
<policyMap>
<policyEntries>
<policyEntry queue="org.apache.>">
<deadLetterStrategy>
<individualDeadLetterStrategy queuePrefix="Test.DLQ."/>
</deadLetterStrategy>
<pendingQueuePolicy>
<vmQueueCursor />
</pendingQueuePolicy>
</policyEntry>
</policyEntries>
</policyMap>
</destinationPolicy>
有效的队列游标类型是 vmQueueCursor 和 fileQueueCursor。默认情况下是基于存储的游标
dispatch
queue
<policyEntry /> element. E.g.:
<policyEntry queue=">" strictOrderDispatch="false" />
默认strictOrderDispatch=false则轮流消费(
round robin,每个消费者取prefetchSize量消息),为true表示按序消费,通过设置优先级设置顺序:
queue = new ActiveMQQueue("TEST.QUEUE?consumer.priority=10");
consumer = session.createConsumer(queue);
Topic
topic分发策略则比较丰富,有内置,也可以自定义
- SimpleDispatchPolicy.默认,轮训分发
- PriorityNetworkDispatchPolicy 权重only dispatch to the highest priority network consumer
- PriorityDispatchPolicy 权重匹配( Priority dispatch policy that sends a message to every subscription that matches the message in consumer priority order)
- RoundRobinDispatchPolicy 类似SimpleDispatchPolicy
- StrictOrderDispatchPolicy Dispatch policy that causes every subscription to see messages in the same order.
示例
<destinationPolicy>
<policyMap>
<policyEntries>
<policyEntry topic="FOO.>">
<dispatchPolicy>
<roundRobinDispatchPolicy/>
</dispatchPolicy>
<subscriptionRecoveryPolicy>
<lastImageSubscriptionRecoveryPolicy/>
</subscriptionRecoveryPolicy>
</policyEntry>
<policyEntry topic="ORDERS.>">
<dispatchPolicy>
<strictOrderDispatchPolicy/>
</dispatchPolicy>
<!-- 1 minutes worth -->
<subscriptionRecoveryPolicy>
<timedSubscriptionRecoveryPolicy recoverDuration="60000"/>
</subscriptionRecoveryPolicy>
</policyEntry>
<policyEntry topic="PRICES.>">
<!--
lets force old messages to be discarded for slow consumers
-->
<pendingMessageLimitStrategy>
<constantPendingMessageLimitStrategy limit="10"/>
</pendingMessageLimitStrategy>
<!-- 10 seconds worth -->
<subscriptionRecoveryPolicy>
<timedSubscriptionRecoveryPolicy recoverDuration="10000"/>
</subscriptionRecoveryPolicy>
</policyEntry>
<policyEntry tempTopic="true" advisoryForConsumed="true"/>
<policyEntry tempQueue="true" advisoryForConsumed="true"/>
</policyEntries>
</policyMap>
</destinationPolicy>
自定义分发
Destination Policy
每个destination都可定义其策略,xml定义
<policyEntry queue=">" strictOrderDispatch="false" />
仅列举几处
- useConsumerPriority 默认true,queue是否参考priority
- strictOrderDispatch false 严格分发
- optimizedDispatch false 分发queue是否额外开启线程
- lazyDispatch false queue是否懒分发
- queuePrefetch queue每次prefetch值
- ......
SpringBoot整合activemq
Application
package jms;
import javax.jms.ConnectionFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerContainerFactory;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.support.converter.MappingJackson2MessageConverter;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.jms.support.converter.MessageType;
import jms.vo.Email;
@SpringBootApplication
@EnableJms
public class JmsApplication {
@Bean
public JmsListenerContainerFactory<?> myFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
// This provides all boot's default to this factory, including the message converter
configurer.configure(factory, connectionFactory);//connectionFactory使用的内置broker,可自定义connectionFactory
// You could still override some of Boot's default if necessary.
return factory;
}
@Bean
public MessageConverter jacksonJmsMessageConverter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
return converter;
}
public static void main(String[] args) {
// Launch the application
ConfigurableApplicationContext context = SpringApplication.run(JmsApplication.class, args);
JmsTemplate jmsTemplate = context.getBean(JmsTemplate.class);
// Send a message with a POJO - the template reuse the message converter
System.out.println("Sending an email message.");
jmsTemplate.convertAndSend("mailbox", new Email("info@example.com", "Hello"));
}
}
Receiver
package jms.receiver;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import jms.vo.Email;
@Component
public class Receiver {
@JmsListener(destination = "mailbox", containerFactory = "myFactory")
public void receiveMessage(Email email) {
System.out.println("Received <" + email + ">");
}
}
消息模板
package jms.vo;
public class Email {
private String to;
private String body;
public Email() {
}
public Email(String to, String body) {
this.to = to;
this.body = body;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
@Override
public String toString() {
return String.format("Email{to=%s, body=%s}", getTo(), getBody());
}
}