0、ActiveMQ 的安装
演示环境: Centos7、jdk8、activemq5.15.8
下载地址: http://activemq.apache.org/activemq-5158-release.html
创建目录:mkdir /usr/local/activemq/
解压: tar -zxvf apache-activemq-5.15.8-bin.tar.gz -C /usr/local/activemq
启动: ./bin/activemq start
停止:./bin/activemq stop
1、ActiveMQ 服务配置
1.1 创建一个systemd服务文件
vi /usr/lib/systemd/system/activemq.service
1.2 填入内容
[Unit]
Description=ActiveMQ service
After=network.target
[Service]
Type=forking
ExecStart=/var/activemq/bin/activemq start
ExecStop=/var/activemq/bin/activemq stop
User=root
Group=root
Restart=always
RestartSec=9
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=activemq
[Install]
WantedBy=multi-user.target
1.3 找到java命令所在的目录 whereis java
1.4 设置activemq配置文件/var/activemq/bin/env中的JAVA_HOME
# Location of the java installation
# Specify the location of your java installation using JAVA_HOME, or specify the
# path to the "java" binary using JAVACMD
# (set JAVACMD to "auto" for automatic detection)
JAVA_HOME="/usr/local/java/jdk1.8.0_181"
JAVACMD="auto"
1.5 通过systemctl管理activemq启停
- 启动activemq服务: systemctl start activemq
- 查看服务状态: systemctl status activemq
- 创建软件链接:ln -s /usr/lib/systemd/system/activemq.service /etc/systemd/system/multi-user.target.wants/activemq.service
- 开机自启: systemctl enable activemq
- 检测是否开启成功(enable): systemctl list-unit-files |grep activemq
1.6 防火墙配置,Web管理端口默认为8161(admin/admin),通讯端口默认为61616
或者直接关闭防火墙: systemctl stop firewalld.service
firewall-cmd --zone=public --add-port=8161/tcp --permanent
firewall-cmd --zone=public --add-port=61616/tcp --permanent
systemctl restart firewalld.service
1.7 修改web管理系统的部分配置,配置文件在/var/activemq/conf
<bean id="jettyPort" class="org.apache.activemq.web.WebConsolePort" init-method="start">
<!-- the default port number for the web console -->
<property name="host" value="0.0.0.0"/>
<!--此处即为管理平台的端口-->
<property name="port" value="8161"/>
</bean>
<bean id="securityConstraint" class="org.eclipse.jetty.util.security.Constraint">
<property name="name" value="BASIC" />
<property name="roles" value="user,admin" />
<!-- 改为false即可关闭登陆 -->
<property name="authenticate" value="true" />
</bean>
1.8 创建或修改用户名密码 /var/activemq/conf/jetty-realm.properties
## ---------------------------------------------------------------------------
# 在此即可维护账号密码,格式:
# 用户名:密码,角色
# Defines users that can access the web (console, demo, etc.)
# username: password [,rolename ...]
admin: admin, admin
user: 123, user
1.9、 JAVA客户端的使用
通过 Maven 导入 activemq 的 jar 包
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.15.8</version>
</dependency>
Spring 中使用如下 jar 包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-broker</artifactId>
<version>5.15.8</version>
<exclusions>
<exclusion>
<artifactId>geronimo-jms_1.1_spec</artifactId>
<groupId>org.apache.geronimo.specs</groupId>
</exclusion>
</exclusions>
</dependency>
2、ActiveMQ 支持的协议和传输方式
它支持多种协议:AUTO、OpenWire、AMQP、MQTT、Stomp 等
也支持多种的传输模式:VM(虚拟机模式,即在JVM中内嵌一个 ActiveMQ 的服务实例),TCP(NIO),SSL,UDP,Peer,Multicast,HTTP 等以及更高级的 Failover,Fanout,Discovery,ZeroConf 方式
连接配置在安装目录下的 conf/activemq.xml 中,通过 <transportConnectors> 来配置:
2.1 TCP 配置参数说明
在服务器端配置时,参数要以 transport. 开头
tcp://localhost:61616?transport.threadName&transport.trace=false&transport.soTimeout=60000
在客户端连接时,参数省略 transport. 前缀
tcp://localhost:61616?threadName&trace=false&soTimeout=60000
可配置的参数有:
2.2 SSL
需要一个安全连接的时候可以考虑使用 SSL,适用于 client 和 broker 在公网的情况,如使用 aws 云平台等
http://activemq.apache.org/ssl-transport-reference.html
以下是它在 Spring 中的配置实例:
2.3 NIO
使用 Java 的 NIO 方式对连接进行改进,因为NIO使用线程池,可以复用线程,所以可以用更少的线程维持更多的连接。
NIO 的配置格式,配置参数和TCP 相同
nio://hostname:port?key=value
NIO 是 OpenWire 协议的传输方式,其他协议例如 AMQP,MQTT,Stomp 也都有 NIO 的实现,通常在协议前缀中加 "+nio" 来表示:
mqtt+nio://localhost:1883
NIO 传输线程使用情况配置:
上面的这些配置可以配置在 ActiveMQ 安装目录下的 bin/env 中:
2.4 UDP
与面向连接、可靠的字节流服务的 TCP 不同,UDP 是一个面向连接的简单传输连接,没有 TCP 的三次握手,所以性能大大强于 TCP,但是以牺牲可靠性为前提,适用于丢失也无所谓的消息。
udp://localhost:8123
下面是它的一些配置参数:
2.5 HTTP(S)
需要穿越防火墙,可以考虑使用 HTTP(S),但由于 HTTP 是短连接,每次创建连接的成本较高,所以性能最差。它的配置格式为:
http://localhost:8080?param1=val1¶m2=val2
2.6 VM
虚拟机协议,使用场景是 client 和 broker 在同一个 java 虚拟机内嵌的情况,无需网络通信的开销,它的配置格式为:
vm://brokerName?marshal=false&broker.persistent=false
它的配置参数有:
3、OpenWire 协议
OpenWire 协议支持 TCP、SSL、NIO、UDP、VM等传输方式,直接配置这些连接,就是使用的 OpenWire 协议,OpenWire 有自己的配置参数,客户端和服务器端配置的参数名都通过前缀 "wireFormat." 来表示:
tcp://localhost:61616?wireFormat.cacheEnabled=false&wireFormat.tightEncodingEnabled=false
它的配置参数有:
4、ActiveMQ 简单示例
4.1 队列模式下生产者示例:
public class Producer {
public static void main(String[] args) {
//当然这里也可以使用其他的协议例如 udp,nio,ssl 等
new ProducerThread("tcp://yourhost:61616", "queue1").start();
}
static class ProducerThread extends Thread {
String brokerUrl;
String destinationUrl;
public ProducerThread(String brokerUrl, String destinationUrl) {
this.brokerUrl = brokerUrl;
this.destinationUrl = destinationUrl;
}
@Override
public void run() {
ActiveMQConnectionFactory connectionFactory;
Connection conn;
Session session;
try {
// 1、创建连接工厂
connectionFactory = new ActiveMQConnectionFactory(brokerUrl);
// 如果使用 ssl 的方式需要提供相应的证书
// connectionFactory.setTrustStore("xxx.ts");
// connectionFactory.setTrustStorePassword("...");
// 2、创建连接对象md
conn = connectionFactory.createConnection();
conn.start();
// 3、创建会话
session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 4、创建点对点发送的目标
Destination destination = session.createQueue(destinationUrl);
// 5、创建生产者消息
MessageProducer producer = session.createProducer(destination);
// 设置生产者的模式,有两种可选 持久化 / 不持久化
producer.setDeliveryMode(DeliveryMode.PERSISTENT);
// 6、创建一条文本消息
String text = "Hello world!";
TextMessage message = session.createTextMessage(text);
for (int i = 0; i < 1; i++) {
// 7、发送消息
producer.send(message);
}
// 8、 关闭连接
session.close();
conn.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
4.2 队列模式下消费者示例:
public class Consumer {
public static void main(String[] args) {
new ConsumerThread("tcp://yourhost:61616", "queue1").start();
new ConsumerThread("tcp://yourhost:61616", "queue1").start();
}
}
class ConsumerThread extends Thread {
String brokerUrl;
String destinationUrl;
public ConsumerThread(String brokerUrl, String destinationUrl) {
this.brokerUrl = brokerUrl;
this.destinationUrl = destinationUrl;
}
@Override
public void run() {
ActiveMQConnectionFactory connectionFactory;
Connection conn;
Session session;
MessageConsumer consumer;
try {
// brokerURL http://activemq.apache.org/connection-configuration-uri.html
// 1、创建连接工厂
connectionFactory = new ActiveMQConnectionFactory(this.brokerUrl);
// 2、创建连接对象
conn = connectionFactory.createConnection();
conn.start(); // 一定要启动
// 3、创建会话(可以创建一个或者多个session)
session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 4、创建点对点接收的目标,queue - 点对点
Destination destination = session.createQueue(destinationUrl);
// 5、创建消费者消息 http://activemq.apache.org/destination-options.html
consumer = session.createConsumer(destination);
// 6、接收消息(没有消息就持续等待)
Message message = consumer.receive();
if (message instanceof TextMessage) {
System.out.println("收到文本消息:" + ((TextMessage) message).getText());
} else {
System.out.println(message);
}
consumer.close();
session.close();
conn.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
4.3 发布订阅模式下生产者示例:
public class Producer {
public static void main(String[] args) {
// brokerUrl: http://activemq.apache.org/connection-configuration-uri.html
new ProducerThread("tcp://yourhost:61616", "topic1").start();
}
static class ProducerThread extends Thread {
String brokerUrl;
String destinationUrl;
public ProducerThread(String brokerUrl, String destinationUrl) {
this.brokerUrl = brokerUrl;
this.destinationUrl = destinationUrl;
}
@Override
public void run() {
ActiveMQConnectionFactory connectionFactory;
Connection conn;
Session session;
try {
// 1、创建连接工厂
connectionFactory = new ActiveMQConnectionFactory(brokerUrl);
// 2、创建连接对象md
conn = connectionFactory.createConnection();
conn.start();
// 3、创建会话
session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 4、创建发布的目标 topic
Destination destination = session.createTopic(destinationUrl);
// 5、创建生产者消息
MessageProducer producer = session.createProducer(destination);
// 设置生产者的模式,有两种可选 持久化 / 不持久化
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
// 6、创建一条文本消息
String text = "Hello world!";
TextMessage message = session.createTextMessage(text);
for (int i = 0; i < 1; i++) {
// 7、发送消息
producer.send(message);
}
// 8、 关闭连接
session.close();
conn.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
4.4 发布订阅模式下消费者示例:
// 非持久订阅者
// 非持久订阅只有当客户端处于连接状态才能收到发送到某个主题的消息,
// 而当客户端处于离线状态,这个时间段发到主题的消息它永远不会收到
public class Consumer {
public static void main(String[] args) {
new ConsumerThread("tcp://yourhost:61616", "topic1").start();
new ConsumerThread("tcp://yourhost:61616", "topic1").start();
}
}
class ConsumerThread extends Thread {
String brokerUrl;
String destinationUrl;
public ConsumerThread(String brokerUrl, String destinationUrl) {
this.brokerUrl = brokerUrl;
this.destinationUrl = destinationUrl;
}
@Override
public void run() {
ActiveMQConnectionFactory connectionFactory;
Connection conn;
Session session;
MessageConsumer consumer;
try {
// brokerURL http://activemq.apache.org/connection-configuration-uri.html
// 1、创建连接工厂
connectionFactory = new ActiveMQConnectionFactory(this.brokerUrl);
// 2、创建连接对象
conn = connectionFactory.createConnection();
conn.start(); // 一定要启动
// 3、创建会话(可以创建一个或者多个session)
session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 4、创建订阅的目标 topic 一条消息,多个订阅者接收
Destination destination = session.createTopic(destinationUrl);
// 5、创建消费者消息 http://activemq.apache.org/destination-options.html
consumer = session.createConsumer(destination);
// 6、接收消息(没有消息就持续等待)
Message message = consumer.receive();
if (message instanceof TextMessage) {
System.out.println("收到文本消息:" + ((TextMessage) message).getText());
} else {
System.out.println(message);
}
consumer.close();
session.close();
conn.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
4.5 MQTT 的订阅发布实现
MQTT 服务质量:服务质量(Qos)级别是一种关于发送者和接受者之间信息投递的保证协议。MQTT 中有三种 Qos 级别:
- 至多一次(0)
客户端发送一次消息给服务器之后就不管了,也就是消息顶多会被发送一次
- 至少一次(1)
客户端发送消息给服务器之后,会尝试着从服务器端读取一个回执,如果客户端在一定时间内没有给应答,那么客户端会再次往服务器端发送消息(应答机制);服务器端同样也是这个道理,当它发送消息给客户端,客户端也需要进行应答,否则也会重复发送。这种机制保证了消息到达的可靠性,但是会出现重复发送的情况。
- 只有一次(2)
客户端发送消息给服务器,并且等待接受服务器的应答,然后客户端会再次发送一个删除重复消息的命令,服务器端会响应并且删除掉重复的消息,这种情况下除了保证消息的可靠传送之外,还保证了消息不会重复发送,当然功能越多客户端和服务器的性能也越差。
如果需要启用 MQTT 协议,那么需要在 ActiveMQ 的服务端做如下配置(不过 MQTT 协议的支持是默认就开启的):
MQTT 配置参数:
MQTT 也支持层次结构和通配符,但分隔符与 ActiveMQ 不同:例如主题名为 foo.blah.bar,那么 MQTT 的客户端在订阅时,可以声明为 foo/+/bar,在 JMS 订阅时,可以为 foo.*.bar
Spring 中有集成 MQTT 协议的支持,通过添加对应的 Maven 依赖即可使用:
MQTT 发布订阅的生产者示例:
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
public class MqttProducer {
private static int qos = 1;
private static String broker = "tcp://yourhost:1883";
private static String userName = "admin";
private static String passWord = "admin";
private static MqttClient connect(String clientId, String userName,
String password) throws MqttException {
MemoryPersistence persistence = new MemoryPersistence();
MqttConnectOptions connOpts = new MqttConnectOptions();
connOpts.setCleanSession(true);
connOpts.setUserName(userName);
connOpts.setPassword(password.toCharArray());
connOpts.setConnectionTimeout(10);
connOpts.setKeepAliveInterval(20);
// String[] uris = {"tcp://host1:1883","tcp://host2:1883"};
// connOpts.setServerURIs(uris); //这个是mqtt客户端实现的负载均衡和容错
MqttClient mqttClient = new MqttClient(broker, clientId, persistence);
mqttClient.setCallback(new PushCallback("test"));
mqttClient.connect(connOpts);
return mqttClient;
}
private static void pub(MqttClient sampleClient, String msg, String topic)
throws Exception {
MqttMessage message = new MqttMessage(msg.getBytes());
message.setQos(qos);
message.setRetained(false);
sampleClient.publish(topic, message);
}
private static void publish(String str, String clientId, String topic) throws Exception {
MqttClient mqttClient = connect(clientId, userName, passWord);
if (mqttClient != null) {
pub(mqttClient, str, topic);
System.out.println("pub-->" + str);
}
if (mqttClient != null) {
mqttClient.disconnect();
}
}
public static void main(String[] args) throws Exception {
publish("message content", "producer-client-id-0", "x/y/z");
}
}
class PushCallback implements MqttCallback {
private String threadId;
public PushCallback(String threadId) {
this.threadId = threadId;
}
public void connectionLost(Throwable cause) {
cause.printStackTrace();
}
public void deliveryComplete(IMqttDeliveryToken token) {
System.out.println("服务器是否正确接收---------" + token.isComplete());
}
public void messageArrived(String topic, MqttMessage message) throws Exception {
String msg = new String(message.getPayload());
System.out.println(threadId + " " + msg);
}
}
MQTT 发布订阅的消费者示例:
import org.eclipse.paho.client.mqttv3.IMqttMessageListener;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
public class MqttConsumer {
private static int qos = 2;
private static String broker = "tcp://yourhost:1883";
private static String userName = "admin";
private static String passWord = "admin";
private static MqttClient connect(String clientId) throws MqttException {
MemoryPersistence persistence = new MemoryPersistence();
MqttConnectOptions connOpts = new MqttConnectOptions();
connOpts.setCleanSession(false);
connOpts.setUserName(userName);
connOpts.setPassword(passWord.toCharArray());
connOpts.setConnectionTimeout(10);
connOpts.setKeepAliveInterval(20);
MqttClient mqttClient = new MqttClient(broker, clientId, persistence);
mqttClient.connect(connOpts);
return mqttClient;
}
public static void sub(MqttClient mqttClient, String topic) throws MqttException {
int[] Qos = {qos};
String[] topics = {topic};
mqttClient.subscribe(topics, Qos, new IMqttMessageListener[]{(s, mqttMessage) -> {
System.out.println("收到新消息" + s + " > " + mqttMessage.toString());
}});
}
private static void runsub(String clientId, String topic) throws MqttException {
MqttClient mqttClient = connect(clientId);
if (mqttClient != null) {
sub(mqttClient, topic);
}
}
public static void main(String[] args) throws MqttException {
runsub("consumer-client-id-1", "x/y/z");
}
}
MQTT 发布订阅在 Spring 中的使用示例:
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.paho.client.mqttv3.IMqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.dsl.Pollers;
import org.springframework.integration.dsl.SourcePollingChannelAdapterSpec;
import org.springframework.integration.endpoint.MessageProducerSupport;
import org.springframework.integration.handler.LoggingHandler;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import java.util.function.Consumer;
@SpringBootApplication
public class MqttApplication {
private static final Log LOGGER = LogFactory.getLog(MqttApplication.class);
public static void main(final String... args) {
// https://spring.io/projects/spring-integration
// https://github.com/spring-projects/spring-integration-samples/
SpringApplication.run(MqttApplication.class, args);
}
@Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
MqttConnectOptions options = new MqttConnectOptions();
options.setServerURIs(new String[]{"tcp://yourhost:1883"});
options.setUserName("admin");
options.setPassword("admin".toCharArray());
factory.setConnectionOptions(options);
return factory;
}
// publisher
@Bean
public IntegrationFlow mqttOutFlow() {
// IntegrationFlows.from 数据来源,可以设定为每秒去取数据
return IntegrationFlows.from(() -> "hello mqtt", new Consumer<SourcePollingChannelAdapterSpec>() {
@Override
public void accept(SourcePollingChannelAdapterSpec sourcePollingChannelAdapterSpec) {
sourcePollingChannelAdapterSpec.poller(Pollers.fixedDelay(1000));
}
})
.transform(p -> p + " sent to MQTT")
.handle(mqttOutbound())
.get();
}
@Bean
public MessageHandler mqttOutbound() {
// 创建handller
MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler("client-si-producer-0", mqttClientFactory());
messageHandler.setAsync(true);
messageHandler.setDefaultTopic("x/y/z");
return messageHandler;
}
// consumer
@Bean
public IntegrationFlow mqttInFlow() {
return IntegrationFlows.from(mqttInbound())
.transform(p -> p + ", received from MQTT")
.handle(printHandler())
.get();
}
private MessageHandler printHandler() {
return new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
System.out.println(message.getPayload().toString());
}
};
}
@Bean
public MessageProducerSupport mqttInbound() {
MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter("client-si-consumer-1",
mqttClientFactory(), "x/y/z");
adapter.setCompletionTimeout(5000);
adapter.setConverter(new DefaultPahoMessageConverter());
adapter.setQos(1);
return adapter;
}
}
4.6 基于 Stomp 的 Websocket 实现:
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.transport.stomp.Stomp;
import org.apache.activemq.transport.stomp.StompConnection;
import org.apache.activemq.transport.stomp.StompFrame;
import javax.jms.*;
// http://activemq.apache.org/stomp.html
public class ConsumerAndProducerStomp {
public static void main(String[] args) throws Exception {
// 直接用Stomp代码的方式
ConsumerAndProducerStomp.stompTest();
}
public static void stompTest() throws Exception {
StompConnection connection = new StompConnection();
connection.open("yourhost", 61613);
connection.connect("system", "manager");
// 发送两条数据
connection.begin("tx1");
connection.send("/topic/test-stomp", "message1");
connection.send("/topic/test-stomp", "message2");
connection.commit("tx1");
// 订阅/topic/test-stomp
connection.subscribe("/topic/test-stomp", Stomp.Headers.Subscribe.AckModeValues.CLIENT);
connection.begin("tx2");
// 接收数据并打印
StompFrame message = connection.receive();
System.out.println(message.getBody());
connection.ack(message, "tx2");
// 继续接收
message = connection.receive();
System.out.println(message.getBody());
connection.ack(message, "tx2");
connection.commit("tx2");
connection.disconnect();
}
}
4.7 基于 VM 方式的示例:
import org.apache.activemq.broker.BrokerFactory;
import org.apache.activemq.broker.BrokerService;
import java.net.URI;
// 嵌入式的,或者自身系统中实现队列
// http://activemq.apache.org/run-broker.html
public class VMDemo {
public static void main(String[] args) throws Exception {
// 内嵌activemq服务
new Thread(() -> {
try {
BrokerService broker = new BrokerService();
broker.setBrokerName("vm-activemq"); // 设置名称
broker.addConnector("tcp://localhost:61616"); // 增加connectorbroker.addConnector("vm://vm-activemq");
broker.start();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
// http://activemq.apache.org/broker-configuration-uri.html
// BrokerFactory.createBroker(new URI("xbean:com/test/activemq-配置文件解析.xml"));
// BrokerService broker = BrokerFactory.createBroker(new URI("broker:tcp://localhost:61616?"));
// broker.start();
}
}
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
// // http://activemq.apache.org/vm-transport-reference.html
public class ConsumerAndProducerVM {
public static void main(String[] args) {
ActiveMQConnectionFactory connectionFactory = null;
Connection conn = null;
Session session = null;
MessageConsumer consumer = null;
try {
// 1、创建连接工厂(vm自动内部创建一个broker)
connectionFactory = new ActiveMQConnectionFactory("vm://localhost");
// 2、创建连接对象
conn = connectionFactory.createConnection();
conn.start();
session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 4、创建点对点接收的目标
Destination destination = session.createQueue("queue1");
// 5、创建生产者消息
MessageProducer producer = session.createProducer(destination);
// 设置生产者的模式,有两种可选
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
// 6、创建一条消息
String text = "Hello world!";
TextMessage message = session.createTextMessage(text);
// 7、发送消息
producer.send(message);
// 8、创建消费者消息
consumer = session.createConsumer(destination);
// 9、接收消息
Message consumerMessage = consumer.receive();
if (consumerMessage instanceof TextMessage) {
System.out.println("收到文本消息:" + ((TextMessage) consumerMessage).getText());
} else {
System.out.println(consumerMessage);
}
consumer.close();
session.close();
conn.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
5、ActiveMQ 高级特性
5.1 延时消息
在使用延时消息之前,一定要在 ActiveMQ 的安装目录下的 conf/activemq.xml 中的 broker 节点配置 schedulerSupport,如下所示:
<broker ..... schedulerSupport="true">
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.ScheduledMessage;
import javax.jms.*;
// 延时、调度消息 http://activemq.apache.org/delay-and-schedule-message-delivery.html
// 定时发送邮件通知,或者触发代码执行
// 可以在 activemq 的 web 管理控制台上面对延时消息进行管理
// 一定要记得在 activemq 的配置文件中的 broker 配置的地方加上一个 schedulerSupport="true" 才支持延迟消息
// corn 表达式:格式是 秒 分 时 日 月 周 年
// 如下面的例子中 0 * * * * 表示的是每隔一分钟执行一次,下面是一些特殊字符
//星号(*):可用在所有字段中,表示对应时间域的每一个时刻,例如,*在分钟字段时,表示“每分钟”;
//问号(?):该字符只在日期和星期字段中使用,它通常指定为“无意义的值”,相当于点位符;
//减号(-):表达一个范围,如在小时字段中使用“10-12”,则表示从10到12点,即10,11,12;
//逗号(,):表达一个列表值,如在星期字段中使用“MON,WED,FRI”,则表示星期一,星期三和星期五;
//斜杠(/):x/y表达一个等步长序列,x为起始值,y为增量步长值。如在分钟字段中使用0/15,则表示为0,15,30和45秒,而5/15在分钟字段中表示5,20,35,50,你也可以使用*/y,它等同于0/y;
public class DelayScheduleMessageDemo {
public static void main(String[] args) {
new ProducerThread("tcp://yourhost:61616", "queue1").start();
}
static class ProducerThread extends Thread {
String brokerUrl;
String destinationUrl;
public ProducerThread(String brokerUrl, String destinationUrl) {
this.brokerUrl = brokerUrl;
this.destinationUrl = destinationUrl;
}
@Override
public void run() {
ActiveMQConnectionFactory connectionFactory;
Connection conn;
Session session;
try {
// 1、创建连接工厂
connectionFactory = new ActiveMQConnectionFactory(brokerUrl);
// 2、创建连接对象md
conn = connectionFactory.createConnection();
conn.start();
// 3、创建会话
session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 4、创建点对点发送的目标
Destination destination = session.createQueue(destinationUrl);
// 5、创建生产者消息
MessageProducer producer = session.createProducer(destination);
// 设置生产者的模式,有两种可选 持久化 / 不持久化
producer.setDeliveryMode(DeliveryMode.PERSISTENT);
// 6、示例消息
// 延时 5秒
TextMessage message = session.createTextMessage("Hello world - 1!");
message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, 5 * 1000L);
// 延时 5秒,投递3次,间隔10秒 (投递次数=重复次数+默认的一次)
TextMessage message2 = session.createTextMessage("Hello world - 2!");
message2.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, 5 * 1000L); // 延时
message2.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_PERIOD, 2 * 1000L); // 投递间隔
message2.setIntProperty(ScheduledMessage.AMQ_SCHEDULED_REPEAT, 2); // 重复次数
// CRON 表达式的方式 以及 和上面参数的组合
TextMessage message3 = session.createTextMessage("Hello world - 3!");
message3.setStringProperty(ScheduledMessage.AMQ_SCHEDULED_CRON, "0 * * * *");
// 7、发送消息
producer.send(message);
producer.send(message2);
producer.send(message3);
// 8、 关闭连接
session.close();
conn.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
5.2 持久化
(1)JDBC 的方式:将消息存储到数据库中,例如:mysql,SQL server,Oracle 等,以下是这种方式的优缺点:
(2)kahaDB 方式: 默认的持久化方式,是一种文件形式的数据库结构,默认存储在 ActiveMQ 安装目录下的 /data/kahaDB/ 下面,.log 文件是持久化的数据文件,.data 文件是文件索引,. redo 是重建索引或数据恢复的依据
<broker brokerName="broker">
<persistenceAdapter>
<kahaDB directory="${activemq.data}/kahaDB" journalMaxFileLength="32mb"/>
</persistenceAdapter>
</broker>
以下是几个比较重要的属性:
属性名 | 默认值 | 作用 |
---|---|---|
indexWriteBatchSize | 1000 | 批量写入磁盘的索引数 |
journalMaxFileLength | 32mb | 消息数据文件的最大占用空间 |
indexCacheSize | 10000 | 缓存在内存中的索引页数量 |
journalDiskSyncStrategy | always | 磁盘同步策略,默认是 always,另外还有 periodic以及never |
journalDiskSyncInterval | 1000 | 当磁盘同步策略是 periodic,可以使用这个属性来设定磁盘同步的间隔 |
5.3 事务机制
生产者发送数据到服务器之后需要再向服务器发送提交或者回滚的动作提示,服务器会根据生产者的提交来存储或者回滚来删除消息。
以下是生产者的代码实例:注意到如果需要使用事务的话,客户端创建于服务器的 session 会话时,需要指定开启事务:session=conn.createSession(true,Session.AUTO_ACKNOWLEDGE);另外生产者在发送完数据之后需要使用会话对象 session 来告知服务器消息是否应该提交或者回滚:session.commit()/session.rollback();
/**
* 生产者事务
*
* 生产者开启事务后,消息发送后,提交事务后,broker上的消息才能发到消费者
*/
public class Producer {
public static void main(String[] args) {
ActiveMQConnectionFactory connectionFactory;
Connection conn = null;
Session session = null;
try {
// 1、创建连接工厂
// connectionFactory = new ActiveMQConnectionFactory("admin", "admin", "udp://yourhost:61616");
connectionFactory = new ActiveMQConnectionFactory("admin", "admin", "tcp://yourhost:61616");
// 2、创建连接对象
conn = connectionFactory.createConnection();
conn.start();
// 3、创建会话
// 第一个参数:是否支持事务,如果为true,则会忽略第二个参数,被jms服务器设置为SESSION_TRANSACTED
// 第一个参数为false时,第二个参数的值可为Session.AUTO_ACKNOWLEDGE,Session.CLIENT_ACKNOWLEDGE,DUPS_OK_ACKNOWLEDGE其中一个。
// Session.AUTO_ACKNOWLEDGE为自动确认,客户端发送和接收消息不需要做额外的工作。哪怕是接收端发生异常,也会被当作正常发送成功。
// Session.CLIENT_ACKNOWLEDGE为客户端确认。客户端接收到消息后,必须调用javax.jms.Message的acknowledge方法。jms服务器才会当作发送成功,并删除消息。
// DUPS_OK_ACKNOWLEDGE允许副本的确认模式。一旦接收方应用程序的方法调用从处理消息处返回,会话对象就会确认消息的接收;而且允许重复确认。
session = conn.createSession(true, Session.AUTO_ACKNOWLEDGE);
// 4、创建点对点发送的目标
Destination destination = session.createQueue("queue2");
// 创建发布的目标
// Destination b4_destination = session.createTopic("topic1");
// 5、创建生产者消息
MessageProducer producer = session.createProducer(destination);
// 设置生产者的模式,有两种可选
// DeliveryMode.PERSISTENT 当activemq关闭的时候,队列数据将会被保存
// DeliveryMode.NON_PERSISTENT 当activemq关闭的时候,队列里面的数据将会被清空
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
for (int i = 0; i < 10; i++) {
// 6、创建一条消息
// 有6中消息类型:
// BytesMessage 用来传递字节
// MapMessage 用来传递字节
// ObjectMessage 用来传递序列化对象
// StreamMessage 用来传递文件等
// TextMessage 用来传递字符串
String text = "Hello world! " + i;
TextMessage message = session.createTextMessage(text);
// 7、发送消息
producer.send(message);
if (i % 3 == 0) { // 3的倍数,发送,但回滚
session.rollback();
} else {
// 在开启持久化模式时,commit后,会同步到磁盘
// 所以当一个原子步骤中发送大批量消息,不建议每条消息发送后提交,而是批量发送完后一次性提交,以最大限度地减少磁盘同步产生的延迟.
session.commit();
}
}
} catch (JMSException e) {
e.printStackTrace();
} finally {
if (conn != null) {
try {
conn.close();
} catch (JMSException e1) {
e1.printStackTrace();
}
}
if (session != null) {
try {
session.close();
} catch (JMSException e1) {
e1.printStackTrace();
}
}
}
}
}
服务器向消费者发送消息之后,消费者也需要发送确认/回滚动作给服务器,服务器根据消费者发送过来的确认来删除消息或者回滚来保存消息。
消费者示例:消费者同样要开启事务的支持,另外需要对接收到的消息通过 session 会话进行确认 commit 或者回滚 rollback,如果消费者返回 rollback 的话,服务器会重发这个消息(默认的重发次数是 6 次)直到超过设定的重发次数之后忽略这条消息。
/**
* 消费者事务
*
* 消费者开启事务后,接收到消息后,需要手动提交事务,否则broker上的消息不会真正被消费
*/
// http://activemq.apache.org/destination-options.html
public class Consumer {
public static void main(String[] args) {
ActiveMQConnectionFactory connectionFactory = null;
Connection conn = null;
Session session = null;
MessageConsumer consumer = null;
try {
// brokerURL http://activemq.apache.org/connection-configuration-uri.html
// 1、创建连接工厂
connectionFactory = new ActiveMQConnectionFactory("tcp://yourhost:61616");
// 2、创建连接对象
conn = connectionFactory.createConnection("admin", "admin");
conn.start();
// 3、创建会话
// 第一个参数:是否支持事务,如果为true,则会忽略第二个参数,被jms服务器设置为SESSION_TRANSACTED
// 第一个参数为false时,第二个参数的值可为Session.AUTO_ACKNOWLEDGE,Session.CLIENT_ACKNOWLEDGE,DUPS_OK_ACKNOWLEDGE其中一个。
// Session.AUTO_ACKNOWLEDGE为自动确认,客户端发送和接收消息不需要做额外的工作。哪怕是接收端发生异常,也会被当作正常发送成功。
// Session.CLIENT_ACKNOWLEDGE为客户端确认。客户端接收到消息后,必须调用javax.jms.Message的acknowledge方法。jms服务器才会当作发送成功,并删除消息。
// DUPS_OK_ACKNOWLEDGE允许副本的确认模式。一旦接收方应用程序的方法调用从处理消息处返回,会话对象就会确认消息的接收;而且允许重复确认。
session = conn.createSession(true, Session.AUTO_ACKNOWLEDGE);
// 4、创建点对点接收的目标
Destination destination = session.createQueue("queue2");
// 创建订阅的目标
// Destination b4_destination = session.createTopic("topic1");
// 5、创建消费者消息 http://activemq.apache.org/destination-options.html
consumer = session.createConsumer(destination);
// 6、接收消息
Session finalSession = session;
consumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
if (message instanceof TextMessage) {
try {
System.out.println("收到文本消息:" + ((TextMessage) message).getText());
} catch (JMSException e) {
e.printStackTrace();
}
} else {
System.out.println(message);
}
try {
//正常情况应该是消费完之后就提交
//finalSession.commit();
//如果消息被客户端回滚,那么服务器会重发这条消息
finalSession.rollback();
} catch (JMSException e) {
e.printStackTrace();
}
}
});
System.in.read();
} catch (JMSException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (consumer != null) {
try {
consumer.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
if (session != null) {
try {
session.close();
} catch (JMSException e1) {
e1.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (JMSException e1) {
e1.printStackTrace();
}
}
}
}
}
5.4 消息重发
首先要创建重发策略并且将其设置到给连接工厂
然后创建会话时使用客户端手动确认的模式
最后在收到消息之后由客户端对消息进行确认 acknowledge 或者是告诉 session 要重发 recover
// 创建队列重发策略
RedeliveryPolicy queuePolicy = new RedeliveryPolicy();
queuePolicy.setInitialRedeliveryDelay(0); // 初始重发延迟时间,单位:毫秒
queuePolicy.setRedeliveryDelay(5000); // 第一次以后的延迟时间
queuePolicy.setUseExponentialBackOff(false);// 是否以指数递增的方式增加超时时间
queuePolicy.setMaximumRedeliveries(3); // 最大重发次数,从0开始计数,为-1则不使用最大次数
// brokerURL http://activemq.apache.org/consumer-dispatch-async.html
// 1、创建连接工厂
connectionFactory = new ActiveMQConnectionFactory(brokerUrl);
// 设置重发策略
connectionFactory.setRedeliveryPolicy(queuePolicy);
// 2、创建连接对象
conn = connectionFactory.createConnection();
// 3、启动连接
conn.start(); // 一定要启动
// 4、创建会话(可以创建一个或者多个session)
// 确认模式设置为客户端手动确认
session = conn.createSession(false, Session.CLIENT_ACKNOWLEDGE);
// 5、创建点对点接收的目标,即接收哪个队列的消息
// http://activemq.apache.org/destination-options.html
Destination destination = session.createQueue("queue2");
// 6、创建消费者消息
consumer = session.createConsumer(destination);
// 7、监听消息
Session finalSession = session;
consumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
try {
// 模拟消费者异常
if (((TextMessage) message).getText().endsWith("4")) {
throw new RuntimeException("消息重发");
}
if (message instanceof TextMessage) {
System.out.println(name + " 收到文本消息:" + ((TextMessage) message).getText());
} else {
System.out.println(name + " " + message);
}
// 8、确认收到消息
message.acknowledge();
} catch (JMSException e) {
e.printStackTrace();
} catch (RuntimeException e) {
System.out.println(e.getMessage());
try {
// 消息重发
finalSession.recover();
} catch (JMSException e1) {
e1.printStackTrace();
}
}
}
});
5.5 持久订阅
普通订阅的情况下,如果订阅者不在线,那么所有发送到主题上的消息订阅者都无法接收到,而持久订阅时,客户端会向JMS 注册一个识别自己身份的ID(clientId必须有),当这个客户端处于离线时,JMS Provider 会为这个ID 保存所有发送到主题的消息,当客户再次连接到JMS Provider时,会根据自己的ID 得到所有当自己处于离线时发送到主题的消息。
// 4、创建订阅的目标 topic 一条消息,多个订阅者接收
Topic topic = session.createTopic(destinationUrl);
// 5、创建订阅者
consumer = session.createDurableSubscriber(topic, "xxx");
5.6 消息异步发送
使用同步消息的缺点是:如果向较慢的消费者发送消息时,可能造成生产者阻塞。
从ActiveMQ v4开始,消费者异步调度的配置更加灵活,可以在连接URI、Connection和ConnectionFactory上进行配置,而在以前的版本中,只能在broker服务器上配置。可以在broker的配置中,通过disableAsyncDispatch属性禁用transportConnector上的异步调度,禁用这个传输连接后,在客户端将无法开启。
<transportConnector name="openwire" uri="tcp://0.0.0.0:61616" disableAsyncDispatch="true"/>
通过这种灵活的配置,可以实现为较慢的消费者提供异步消息传递,而为较快的消费者提供同步消息传递。
// 1、创建连接工厂
connectionFactory = new ActiveMQConnectionFactory(brokerUrl);
// 在连接工厂设置是否异步分发,作用于通过此工厂创建的所有连接
//connectionFactory.setDispatchAsync(false);
// 2、创建连接对象
conn = connectionFactory.createConnection();
// 在连接上设置是否异步分发,作用于通过此链接创建的所有session
//((ActiveMQConnection) conn).setDispatchAsync(false);
// 3、启动连接
conn.start(); // 一定要启动
// 4、创建会话(可以创建一个或者多个session)
// 确认模式设置为客户端手动确认
session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 5、创建点对点接收的目标,即接收哪个队列的消息
// 在队列上设置consumer.dispatchAsync是否异步分发,将仅作用于此队列
// http://activemq.apache.org/destination-options.html
Destination destination = session.createQueue("queue1?consumer.dispatchAsync=false");
5.7 消费者优先级
通过在定义目的队列/主题时声明 consumer.priority 来指定消费者的优先级,优先级值的范围是:0-127。最高优先级是127。默认优先级是0
Destination destination = session.createQueue("queue1?consumer.priority=20");
5.8 独占消费
独占消费是在队列层面使用,当某个队列有多个消费者开启独占模式后,此队列将会被第一个消费者占用,只有第一个消费者才能从这个队列获取消息,未开启时,是多个消费者平均消费
Destination destination = session.createQueue("queue1?consumer.exclusive=true");
5.9 消费者分组和生产者分组
所谓的消费者分组就是订阅同一个队列的多个消费者构成的集合,而生产者分组则表示往同一个队列生产消息的生产者集合。同一个组的消息只会发送给消费者组里面的一个消费者。生产者通过设置消息的 JMSXGroupID 的属性来将多个消息设置成同一个组,而设置 JMSXGroupSeq 为 -1 可以关闭消息的分组。
TextMessage message = session.createTextMessage("Hello world groupA " + i);
message.setStringProperty("JMSXGroupID", "GroupA");
if (i == 5) {
// 将JMSXGroupSeq设置为-1后,GroupA将关闭,下次再发送GroupA的消息后,将会是另一个消费者接收
message.setIntProperty("JMSXGroupSeq", -1);
}
// 7、发送消息
producer.send(message);
5.10 消息追溯
所谓的消息追溯就是使消费者可以接收到历史的topic消息,需要在activemq.xml中给主题设置恢复策略配置:
<broker>
<destinationPolicy>
<policyMap>
<policyEntries>
<policyEntry topic=">">
<!-- http://activemq.apache.org/subscription-recovery-policy.html -->
<subscriptionRecoveryPolicy>
<!--
FixedSizedSubscriptionRecoveryPolicy: 保存一定size的消息,broker将为此Topic开辟定额的RAM用来保存
最新的消息。使用maximumSize属性指定保存的size数量
FixedCountSubscriptionRecoveryPolicy: 保存一定条数的消息。 使用maximumSize属性指定保存的size数量
LastImageSubscriptionRecoveryPolicy: 只保留最新的一条数据
QueryBasedSubscriptionRecoveryPolicy: 符合置顶selector的消息都将被保存,具体能够“恢复”多少消息
,由底层存储机制决定;比如对于非持久化消息,只要内存中还
存在,则都可以恢复。
TimedSubscriptionRecoveryPolicy: 保留最近一段时间的消息。使用recoverDuration属性指定保存时间 单位
毫秒
NoSubscriptionRecoveryPolicy: 关闭“恢复机制”。默认值。
-->
<!--恢复最近30分钟内的信息-->
<timedSubscriptionRecoveryPolicy recoverDuration="1800000"/>
</subscriptionRecoveryPolicy>
...
</broker>
另外在创建 destination 时要添加 consumer.retroactive=true 的参数设置:
// 5、创建订阅的主题
// http://activemq.apache.org/destination-options.html
Topic destination = session.createTopic(topic + "?consumer.retroactive=true");
5.11 消息过滤
所谓的消息过滤即消费者接收消息时,支持根据消息属性进行条件过滤,条件为字符串,语法和SQL的where条件类似
生产者通过在发送消息 时给消息添加一些属性:
TextMessage message = session.createTextMessage(text);
// 设置消息属性
message.setIntProperty("age", new Random().nextInt(30));
message.setStringProperty("gender", gender[i % gender.length]);
消费者则在创建的时候指定要消费的消息应该满足的过滤条件:
// 6、创建消费者消息
MessageConsumer consumer = session.createConsumer(destination, "age >= 18 and gender = '女'");
5.12 复合目标
如果需要将一条消息发送到多个不同的目标,可以在创建目标时,通过","来分隔多个目标,例如:queue1,queue2,queue3。
如果需要匹配目标类型,可以通过增加topic://或queue://前缀来实现。例如在队列上发送消息,同时也发送到主题上:queue1,topic://topic1
// 4、创建复合目标,消息将同时发送到FOO.A队列和FOO.B主题上
Destination destination = session.createQueue("FOO.A,topic://FOO.B");
5.13 自动创建和自动清除目标
如果需要activemq启动时自动创建一些队列或主题,可以在activemq.xml的broker中增加配置
<destinations>
<!-- 队列 -->
<queue physicalName="FOO.BAR"/>
<!-- 主题 -->
<topic physicalName="SOME.TOPIC"/>
</destinations>
ActiveMQ可以自动清除非活动的目标,但默认没有开启,可以通过修改配置开启
- schedulePeriodForDestinationPurge:间隔多少毫秒检查一次
- gcInactiveDestinations:非活动队列清楚开关,为true时,inactiveTimoutBeforeGC才会生效
- inactiveTimoutBeforeGC:空队列超过多少毫秒处于非活动时被删除
<broker xmlns="http://activemq.apache.org/schema/core" schedulePeriodForDestinationPurge="10000">
<destinationPolicy>
<policyMap>
<policyEntries>
<policyEntry queue=">" gcInactiveDestinations="true" inactiveTimoutBeforeGC="30000"/>
</policyEntries>
</policyMap>
</destinationPolicy>
</broker>
5.14 镜像队列
如果为queue1队列配置镜像队列后,ActiveMQ会生成一个VirtualTopic.Mirror.queue1的主题,并接收发送到queue1的所有消息,通过订阅此主题,可以使多个用户都到queue1发送的消息,在<broker>元素中配置:
<destinationInterceptors>
<mirroredQueue copyMessage = "true" postfix=".qmirror" prefix=""/>
</destinationInterceptors>
这将为Broker上的每个队列创建一个名为“*.qmirror”的主题。
5.15 虚拟目标
ActiveMQ 使用持久订阅时,遵循了JMS规范,同一个clientId,只能有一个JMS连接处于活动状态,即只有一个线程可以从给定的主题消费。所以,使用持久订阅时会有两个问题:
- 一个应用无法启动多个实例,即应用无法实现高可用;
- 无法实现消息负载均衡。
ActiveMQ 通过一个虚拟目标将queue和topic结合,使queue能收到topic的消息,来实现持久订阅的功能。
ActiveMQ 可以通过配置,为消费者的队列自动创建虚拟一个主题作为虚拟目标,生产者发送消息到主题时,主题名符合虚拟目标的规则,会将消息自动转发到消费者队列中。
首先需要在activemq.xml的broker中增加以下配置:
<destinationInterceptors>
<virtualDestinationInterceptor>
<virtualDestinations>
<!--
name:主题名,可以是通配符
prefix:队列的前缀
selectorAware:表示从Topic中将消息转发给Queue时,是否关注Consumer的selector情况。如果为false,那么Topic中的消息全部转发给Queue,否则只会转发匹配Queue Consumer的selector的消息
-->
<virtualTopic name=">" prefix="VTC.*." selectorAware="false"/>
</virtualDestinations>
</virtualDestinationInterceptor>
</destinationInterceptors>
接着在消费者代码中,将创建topic改为创建queue;创建持久订阅消费者的方法改为创建非持久订阅消费者:
Destination destination = session.createQueue("VTC.c1.FOO.B");// c1相当于clientId,FOO.B为主题名。对FOO.B而言,c1相当于它的订阅者。broker将c1订阅的消息转发到VTC.c1.FOO.B队列中。而且,这个队列完全具有队列的所有特性,它的Consumer可以并行消费。
consumer = session.createConsumer(destination);
5.16 分发优化
(1)消息游标机制 http://activemq.apache.org/message-cursors.html
在ActiveMQ 5.0之前,broker会把正在传输的消息保存在内存中。使用这种内存模型,当一个消费者消费消息的速度跟不上生产者生产消息的速度的时候,会是broker内存中维护的正在传输的消息数量迅速增长,最终到达最大限额。
当到达此最大限额后,broker就不能接受来自客户端的消息,这样生产者就会被阻塞直到broker的内存中有保存消息的空间为止。
从 5.0 版本开始,ActiveMQ 实现了一种新的内存模型以防止慢速的消费者阻塞运行速度更快的生产者。这种内存模型使用了消息游标。
(2)消息消费确认优化 http://activemq.apache.org/optimized-acknowledgement.html
由于5.4.2对批处理优化的应答有一个默认超时,这确保了ack及时,即使用户很慢。
在慢速网络中,超时可能在达到批处理限制之前过期,因此会绕过利用率降低的带宽。
在5.6版本中,超时可以通过 optimizeAcknowledgeTimeOut 属性进行配置。通过连接的URI字符串或在连接工厂上设置超时时间。默认值是300ms,如果值为0则禁用。
需要设置optimizeAcknowledge为true,optimizeAcknowledgeTimeOut才会生效
(3)生产者流量控制 http://activemq.apache.org/producer-flow-control.html
https://blog.csdn.net/neareast/article/details/7581855
在ActiveMQ5.0版本中,可以对生产者连接进行流量控制,当broker检测到目标超出限额时,生产者将会被阻塞直至资源可用。
(4)有序消息 http://activemq.apache.org/total-ordering.html
通常,broker将保证同一生产者发送的所有消息的顺序。但是,由于broker使用多个线程处理,所以来自不同生产者的消息可能以
不同的顺序到达不同的消费者。最后导致消息消费的顺序和发送的顺序不一致
ActiveMQ 支持严格顺序消息,启用严格顺序后,消息将按生产者发送是的顺序发送给消费者,但这会严重影响性能。
通过在activemq.xml中配置开启严格顺序
<destinationPolicy>
<policyMap>
<policyEntries>
<policyEntry topic=">">
<dispatchPolicy>
<strictOrderDispatchPolicy/>
</dispatchPolicy>
</policyEntry>
</policyEntries>
</policyMap>
</destinationPolicy>
6、ActiveMQ 高可用架构
6.1 主从共享数据(文件/数据库)方式 Master-Slave
在做基于数据库的主从共享数据的情况下,首先需要将数据库的驱动包(这里使用的是 mysql 的驱动包)放到 ActiveMQ 安装目录的 /lib/ 目录下,另外还要编辑 ActiveMQ 的配置文件: conf/activemq.xml (其他几个从服务器需要做相同的配置)
首先是开启持久化的支持:
<broker .... persistent="true" ... >
接着要配置持久化的适配器,默认使用的是 kahaDB 的文件形式的数据库,这里需要改成对应 mysql 的数据源:
<persistenceAdapter>
<jdbcPersistenceAdapter dataSource="#mysql-ds" useDatabaseLock="false" transactionIsolation="4"/>
</persistenceAdapter>
然后配置数据源的信息:
<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://localhost:3306/activemq?relaxAutoCommit=true" />
<property name="username" value="****" />
<property name="password" value="****" />
<property name="poolPreparedStatements" value="true" />
</bean>
生产者示例:通过使用 failover 在主从服务器之间进行故障转移
/**
* 简单生产者
*/
public class Producer {
public static void main(String[] args) {
String brokerUrl = "failover:(tcp://host-master:61616,tcp://host-slave:61616)?initialReconnectDelay=100";
new ProducerThread(brokerUrl, "queue1").start();
}
static class ProducerThread extends Thread {
...
}
}
消费者示例:同样是使用 failover 来做故障转移
// http://activemq.apache.org/failover-transport-reference.html
public class ConsumerFailover {
public static void main(String[] args) throws InterruptedException {
// 非failover的公共参数配置通过nested.*,例如 failover:(...)?nested.wireFormat.maxInactivityDuration=1000
// ?randomize=false 随机选择,默认是顺序
// 指定优先切换 failover:(tcp://host1:61616,tcp://host2:61616,tcp://host3:61616)?priorityBackup=true&priorityURIs=tcp://local1:61616,tcp://local2:61616
// maxReconnectDelay重连的最大间隔时间(毫秒)
String brokerUrl = "failover:(tcp://host-master:61616,tcp://host-slave:61616)?initialReconnectDelay=100";
ConsumerThread queue1 = new ConsumerThread(brokerUrl, "queue1");
queue1.start();
queue1.join();
}
}
class ConsumerThread extends Thread {
....
}
总结:这种方式虽然实现了故障转移,但是始终只有一台服务器可以提供服务,因此还无法提供高并发的服务。
6.2 多主集群同步元数据的方式
activemq 内部有内置消费者,它可以订阅与它有网络连接的其他服务器上的队列/主题等元数据信息的主题,因此客户端可以通过访问任意一台服务器来消费某一个队列/主题上的数据
使用多主同步元数据的方式的话,需要修改 ActiveMQ 的配置文件,其实就是配置服务器之间的网络连接,配置的位置在 ActiveMQ 的安装目录下的 conf/activemq.xml 中的 broker 节点下面:
静态IP的方式:
host1 的配置:
<networkConnectors>
<networkConnector uri="static:(tcp://host2:61616)" />
</networkConnectors>
host2 的配置:
<networkConnectors>
<networkConnector uri="static:(tcp://host1:61616)" />
</networkConnectors>
当然也可以使用 ActiveMQ 提供的网络发现机制来动态配置,此种方式需要修改三个地方,第一个地方是 networkConnectors:
<networkConnectors>
<networkConnector uri="multicast://default" />
</networkConnectors>
第二个地方是 transportConnectors:
<transportConnectors>
<transportConnector .... discoveryUri="multicast://default">
<publishedAddressPolicy>
<!-- 确保对方没有配置 hostname 的情况下仍然可以通过ip进行访问 -->
<publishedAddressPolicy publishedHostStrategy="IPADDRESS"></publishedAddressPolicy>
</transportConnector>
</transportConnectors>
这种模式下的生产者示例如下:
/**
* 简单生产者
*/
public class Producer {
public static void main(String[] args) {
// 生产者将数据发送到 host2 上
String brokerUrl = "failover:(tcp://host2:61616)?initialReconnectDelay=100";
new ProducerThread(brokerUrl, "queue1").start();
}
static class ProducerThread extends Thread {
}
}
消费者示例如下:
// http://activemq.apache.org/networks-of-brokers.html
public class ConsumerNetowork {
public static void main(String[] args) throws InterruptedException {
// 消费者连接 host1
String brokerUrl = "failover:(tcp://host1:61616)?initialReconnectDelay=100";
ConsumerThread queue1 = new ConsumerThread(brokerUrl, "queue1");
queue1.start();
queue1.join();
}
}
class ConsumerThread extends Thread {
...
}
总结:这种方式实现了多主集群,可以有一定的负载均衡策略,但是仍然存在单点故障的问题,即如果此时服务器 1 宕机,那么服务器 1 上面所有的队列/主题信息也无法通过服务器 2 进行访问。
6.3 多主集群同步元数据+主从共享数据
这种方式就是前面两种方式的结合,既可以实现故障转移,也可以实现负载均衡
这种方式下,主服务器上需要配置其他服务器的主、从服务器的信息:
静态 IP 的配置方式如下:
<networkConnectors>
<networkConnector uri="masterslave:(tcp://host1_master:61616,tcp://host1_slave_1:61616,tcp://host1_slave_2:61616)" duplex="false" />
<networkConnector uri="masterslave:(tcp://host2_master:61616,tcp://host2_slave_1:61616,tcp://host2_slave_2:61616)" duplex="false" />
</networkConnectors>
6.4 networkConnector 的配置说明