一、queue browser
可以查看队列中的消息而不消费,没有订阅的功能。
1.1 代码
发送者:
消费者:
三、Hawtio
3.1 独立jar包的形式运行、
- java -jar
- hawtio单程序运行,可以对多个远程ActiveMQ服务器进行监控
- http://localhost:8080/hawtio/jvm/connect访问:http://localhost:8080/hawtio/jvm/connect
- 添加connect
3.2 嵌入ActiveMQ
- 下载war包
- 复制到webapps下
- jetty.xml bean标签下添加代码块a
- ActiveMQ.bat下添加代码块b
代码块a:
<bean class="org.eclipse.jetty.webapp.WebAppContext">
<property name="contextPath" value="/hawtio" />
<property name="war" value="${activemq.home}/webapps/hawtio.war" />
<property name="logUrlOnStart" value="true" />
</bean>
代码块b:
if "%ACTIVEMQ_OPTS%" == "" set ACTIVEMQ_OPTS=-Xms1G -Xmx1G -Dhawtio.realm=activemq -Dhawtio.role=admins -Dhawtio.rolePrincipalClasses=org.apache.activemq.jaas.GroupPrincipal -Djava.util.logging.config.file=logging.properties -Djava.security.auth.login.config="%ACTIVEMQ_CONF%\login.config"
配置完成后,如果点activemq.bat启动不了,可以用以下方法启动:
cd E:\Java_Software\apache-activemq-5.15.15\bin\win64
activemq start
二、JMSCorrelationID
用于消息之间的关联,给人一种会话的感觉。
类似于客户端与服务端直接的sessionId。
JMSCorrelationID可以用于单独一条消息直接的关联。也可用于消息分类。将同一类消息设置成同一个JMSCorrelationID。
2.1 demo
三、JMSReplyTo
发送方可以接受到消息消费确认的地址。
四、QueueRequestor同步消息
可以发送同步消息
本质违背了mq的异步通讯原则
但是mq还是能够提供应用解耦、异构系统的特性
因为使用QueueRequestor发送消息后,会等待接收端的回复,如果收不到回复就会造成死等现象!而且该方法没有设置超时等待的功能
设置QueueRequestor同步消息,服务端会一直阻塞,直到客户端消费完成并且成功ack给broker,且服务端接收到客户端回执的tempD队列,才结束阻塞。
与JMSReplyTo有点像,但JMSReplyTo是异步的,它是同步等待的。
4.1 demo
发送者:
点进去QueueRequestor#request方法,查看它的内部原理:
接收者:
测试后结果:
服务端发送消息后,一直处于阻塞状态。客户端消费完消息,服务端才结束阻塞状态。
五、生产环境中影响性能的几个因素
5.1 Out of memory
activemq启动脚本中配置内存:
%ACTIVEMQ_OPTS%" == "" set ACTIVEMQ_OPTS=-Xms1G -Xmx1G
以及配置文件中的百分比 :
<memoryUsage percentOfJvmHeap="70" />
SystemUsage配置设置了一些系统内存和硬盘容量,当系统消耗超过这些容量设置时,amq会“slow down producer”,还是很重要的。
5.2 持久化和非持久化
5.3 消息异步发送
建议使用默认,强制开启有可能丢失消息
异步发送丢失消息的场景是:生产者设置UseAsyncSend=true,使用producer.send(msg)持续发送消息。由于消息不阻塞,生产者会认为所有send的消息均被成功发送至MQ。如果服务端突然宕机,此时生产者端内存中尚未被发送至MQ的消息都会丢失。
new ActiveMQConnectionFactory("tcp://locahost:61616?jms.useAsyncSend=true");
((ActiveMQConnectionFactory)connectionFactory).setUseAsyncSend(true);
((ActiveMQConnection)connection).setUseAsyncSend(true)
5.4 批量确认
ActiveMQ缺省支持批量确认消息,批量确认可以提高系统性能。
5.4.1 关闭方法
new ActiveMQConnectionFactory("tcp://locahost:61616?jms.optimizeAcknowledge=false");
((ActiveMQConnectionFactory)connectionFactory).setOptimizeAcknowledge(fase);
((ActiveMQConnection)connection).setOptimizeAcknowledge(true);
六、消费缓冲与消息积压prefetchSize
消费者端,一般来说消费的越快越好,broker的积压越小越好。
但是考虑到事务性和客户端确认的情况,如果一个消费者一次获取到了很多消息却都不确认,这会造成事务上下文变大,broker端这种“半消费状态”的数据变多,所以ActiveMQ有一个prefetchSize参数来控制未确认情况下,最多可以预获取多少条记录。
Pre-fetch默认值
consumer type | default value |
---|---|
queue | 1000 |
queue browser | 500 |
topic | 32766 |
durable topic | 1000 |
6.1 可以通过3中方式设置prefetchSize
6.1.1 创建连接时整体设置
ActiveMQConnectionFactory connectio nFactory = new ActiveMQConnectionFactory(
"admin",
"admin",
"tcp://localhost:5671?jms.prefetchPolicy.all=50"
);
6.1.2 创建连接时对topic和queue单独设置
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
"admin",
"admin",
"tcp://localhost:5671?jms.prefetchPolicy.queuePrefetch=1&jms.prefetchPolicy.topicPrefetch=1"
);
6.1.3 针对destination单独设置
Destination topic = session.createTopic("user?consumer.prefetchSize=10");
注意:对destination设置prefetchsize后会覆盖连接时的设置值
七、消息到底是推还是拉?
发送消息时是推向broker
获取消息时:
- 默认是一条一条的推
- 当customer的prefetchSize满的时候停止推消息
- 当customer的prefetchSize ==0时 拉取消息
7.1 设置了prefetchSize后,推消息|拉消息的流程
- 推消息,首先作一个判断,查看comsumer一次能接受多少条消息。如果broker一次性把所有的消息推给comsumer,comsumer也接不住啊。
- 所以comsumer有一个缓冲的概念,叫prefetchSize——预处理的大小。
- comsumer生命缓冲区的大小,然后broker作计数,声明方式有三种,下面会介绍。
- broker如何作计数呢?推一条计数 :count+1;收到ack: count-1
- 什么情况下是推消息呢?满足两个条件:一是设置了prefethSize>0,二是comsumer没有满。当推的消息计数count==prefetchSize时,就不再推消息了。
prefetchSize相当于是消费者内存中的消息,当消费速度够快,来一条就消费一条。
如果不设置prefechSize的值,那么broker就不会主动推消息给comsumer,而是comsumer去拉消息。拉消息不需要缓冲。
7.2 源码
7.2.1 broker端推消息源码
①循环消息,②循环consumer
再②下面有判断
7.2.2 consumer端拉消息源码
当prefetchSize==0且没有消费的消息,才发送拉取消息的指令给broker。
7.3 消息异步操作流程
- Broker会记录每个comsumer的prefetchSize,prefetchSize是comsumer与broker建立连接的时候提交上来的。
- broker会看comsumer的prefetchSize是不是等于0,如果是等于0,那就不推了。如果prefetchSize满了,也不推了。
- 如果2步骤都不成立,那么给当前的comsumer的prefetchSize执行+1操作,再给comsumer推送消息。comsumer得到消息后,在它自己的缓冲区里多了一条消息,这条消息是未消费的。
- 当comsumer启动消费的时候,会从缓冲区取这条消息,消费完成后,会向broker发送ack
- 收到ack后,broker会把当前comsumer的prefetchSize执行-1操作
- 如果开启了持久化,删除该条消息
八、消息发送采用异步,如何避免消息丢失
采用异步回调:
public class SenderQueue2 {
public static void main(String[] args) throws Exception{
// 1.获取连接工厂
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
"admin",
"admin",
"nio://localhost:5671"
);
Connection connection = connectionFactory.createConnection();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
connection.start();
Queue queue = session.createQueue("xxoo");
ActiveMQMessageProducer producer = (ActiveMQMessageProducer)session.createProducer(queue);
final CountDownLatch countDownLatch = new CountDownLatch(1000);
Message message = session.createTextMessage("Message from ServerA xxx" );
for (int i = 0; i < 1000; i++) {
producer.send(message,new AsyncCallback() {
public void onException(JMSException exception) {
// TODO Auto-generated method stub
exception.printStackTrace();
// logger.xxoo....
}
public void onSuccess() {
// TODO Auto-generated method stub
// 来自数据库,成功送出去 补偿
countDownLatch.countDown();
}
});
}
countDownLatch.await();
System.out.println("System exit....");
}
}
onException:回调异常。
onSuccess:回调成功。
异步,发送者发送1w条发过去,broker拿到了,而不需要broker等待确认(ack),消费者直接可以拿消息了。producer只有等待broker回调就行,异常了再重新发到broker。
九、EIP Enterprise Integration Patterns.
EIP系统是以数据为基础,应用为核心,以实现业务及业务流程的自动化为目的多功能企业信息平台。为企业的信息化建设提供一种循序渐进,逐步优化的路径
一个围绕消息集成的企业应用集成场景基本在上面的图中描述的比较清楚的,简单说明如下
- 消息发送方和接收方:可以是异构的业务系统,但是都需要提供Endpoint实现集成。
- 消息本身:两个应用系统通过channel连接,实现了消息本身的发送和接收操作
- 消息Channel:即消息传输的通道,消息本身必须要通过channel来实现传输,从源到达目标。
- 消息路由:当有多个目标接收方的时候,如果根据消息的特征来确定究竟发送到哪个接收方?
- 消息转换:消息在传输过程中是否需要进行转换和数据映射,包括报文格式转换和内容转换映射。
- Pipe and Filter:在执行复杂的消息流处理时,如何维护消息本身的独立性和灵活性。
常用实现Camel
支持ActiveMQ、RabbitMQ、kafka、WebService
camel实现了客户端与服务端的解耦, 生产者和消费者的解耦。
十、拓展
ActiveMQ配置策略 - JinLoooong - 博客园
ActiveMQ 到底是推还是拉? - 偶尔发呆 - 博客园