ActiveMQ的初夜
Producer Flow Control
mq自己实现了Flow Control(流量控制,默认开启),在mq的版本中,4.x和5.x流量控制实现原理并不相同,前者通过 TCP Flow Control 实现流量控制,只能针对链接,而5.x之后的PFC,能针对某个特定的producer,这里只讨论5.x。在5.0中,broker通过检测内存或者文件大小,判断是否已经达到容量上限,如果到达上限,broker就会减慢对消息的处理。默认情况下,producer会阻塞,不会再将消息发送到broker,直到broker有空闲容量。如果发现管理后台消息消费比较慢,甚至有消息堆积,可以尝试从这方面入手。
需要注意的是,默认情况下,broker流量控制的整个过程producer端并不会有异常日志,加大了对这种异常情况排查的难度。可以在broker的配置文件中设置sendFailNoSpaceAfterTimeout或sendFailNoSpace,设置之后,如果broker容量不足,producer端就能捕获到javax.jms.ResourceAllocationException,配置方法如下:
<systemUsage>
<systemUsage sendFailIfNoSpace="true">
<memoryUsage>
<memoryUsage limit="20 mb"/>
</memoryUsage>
</systemUsage>
</systemUsage>
<systemUsage>
<systemUsage sendFailIfNoSpaceAfterTimeout="3000">
<memoryUsage>
<memoryUsage limit="20 mb"/>
</memoryUsage>
</systemUsage>
</systemUsage>
当producer端采用异步发送时,并不会等待broker的确认消息,所以默认情况下,即使达到容量上限,producer仍然没法知晓,可以通过改变connection的producerWindowSize属性修改默认配置:
// 通过代码配置
ActiveMQConnectionFactory connctionFactory = ...
connctionFactory.setProducerWindowSize(1024000);
// 通过url配置
tcp://127.0.0.1:61616?jms.useAsyncSend=true&jms.producerWindowSize=1024000
producerWindowSize官方解释如下:
The ProducerWindowSize is the maximum number of bytes of data that a producer will transmit to a broker before waiting for acknowledgment messages from the broker that it has accepted the previously sent messages.
即,在producer发送producerWindowSize字节的数据后,broker会回包通知producer,在此之前的消息都已经被broker接收。
如果使用同步发送,或者异步发送但配置了producerWindowSize属性,一旦达到容量上限,broker会阻塞当前producer,而不是整个链接,当有空闲容量时,broker会回一个ProducerAck。如果producer继续发送消息,broker将阻塞整个connection,倘若此时的consumer和producer共用同一个connection,将会导致死锁。
异步和同步发送
mq提供消息同步和异步发送两种方式,如果能接受少量消息丢失,可以采用异步发送,否则用同步。这里大致介绍了mq同步发送消息的处理流程。
Prefetch limit
机制介绍
mq为了提高吞吐量,采取prefetch 机制,即consumer端会在内存中维护一个消息的缓冲区,存放需要消费的消息,通过这种方式,consumer并不需要每条消息都主动请求(poll)broker,而是broker会push定量的消息到consumer端,以此降低频繁网络请求导致的性能损耗。
这种机制存在风险,尤其是consumer的消息处理速度跟不上broker push的速度时,这会导致大量消息充斥consumer的缓冲区,可能出现一个consumer繁忙,另一个consumer空闲的情况,并不利于消息及时处理。因此,mq提供prefetch limit参数限制每次push到consumer的消息数量。当达到prefetch limit上限,consumer不会再收到消息,直到consumer给broker发送确认消息。
如果consumer消息处理足够快,可以调大limit上限,这样整个系统的性能会比较可观。当该参数设置为0时,表示关闭prefect,consumer每次主动从broker拉取(poll)消息,而不是broker将消息push到consumer。mq不同服务的默认prefetch limit值如下:
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)
我一般都是通过broker url指定limit大小:
tcp://localhost:61616?jms.prefetchPolicy.queuePrefetch=100
将每个队列prefetch limit设置为100,即broker每次都会将100个消息push到consumer端。
当使用DefaultMessageListenerContainer时,prefetch可能会引起问题,官方文档是这么说的:
Consuming messages from a pool of consumers an be problematic due to prefetch. Unconsumed prefetched messages are only released when a consumer is closed, but with a pooled consumer the close is deferred (for reuse) till the consumer pool closes. This leaves prefetched messages unconsumed till the consumer is reused. This feature can be desirable from a performance perspective but it can lead to out-of-sequence messages when there is more than one consumer in the pool. For this reason, the org.apache.activemq.pool.PooledConnectionFactory does not pool consumers. The problem is visible with the Spring DMLC when the cache level is set to CACHE_CONSUMER and there are multiple concurrent consumers. One solution to this problem is to use a prefetch of 0 for a pooled consumer, in this way, it will poll for messages on each call to receive(timeout). Another option is to enable the AbortSlowAckConsumerStrategy on the broker to disconnect consumers that have not acknowledged a Message after some configurable time period.
测试demo
为了测试prefetch对consumer的影响,写了一些测试代码,具体项目代码在这里。
// applicationContext-without-dmlc.xml
<bean id="normalSendConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory"
destroy-method="stop">
<property name="connectionFactory">
<bean class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL">
<value>${brokerUrl}</value>
</property>
</bean>
</property>
</bean>
<bean id="normalJmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="normalSendConnectionFactory"></property>
<property name="receiveTimeout" value="600"></property>
<property name="sessionAcknowledgeMode">
<value>2</value>
</property>
<property name="deliveryPersistent">
<value>true</value>
</property>
</bean>
由于需要提前添加大量消息到broker,所以applicationContext-without-dmlc.xml 文件中并没有设置DefaultMessageListenerContainer节点。
// applicationContext-with-sleep-receiver.xml
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL">
<value>${brokerUrl}?jms.prefetchPolicy.queuePrefetch=100</value>
</property>
</bean>
<bean id="example.MyQueue" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg index="0" value="example.MyQueue" />
</bean>
<!-- dead letter queue -->
<bean id="ActiveMQ.DLQ" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg index="0" value="ActiveMQ.DLQ" />
</bean>
<!-- this is the Message Driven POJO (MDP), singleton -->
<bean id="messageListener" class=" afred.jms.activeMQDemo.example.prefetch.SleepMQMsgReceiver" />
<bean id="myExceptionListener" class="afred.jms.activeMQDemo.receive.exceptionhandler.MQExceptionListener"></bean>
<bean id="jmsContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="destination" ref="example.MyQueue" />
<property name="messageListener" ref="messageListener" />
<property name="maxConcurrentConsumers" value="2"></property>
<property name="exceptionListener" ref="myExceptionListener"></property>
<!--<property name="sessionTransacted" value="true" />-->
<!--<property name="transactionManager" ref="local.transactionManager" />-->
</bean>
applicationContext-with-sleep-receiver.xml文件是consumer端的配置文件,messageListener是单例,并将最大consumer并发数设置2,默认是1,由于consumer由DMLC管理,测试过程中只能通过日志框架打印线程ID,以此观察两个consumer的消息处理过程。在brokerURL中添加了jms.prefetchPolicy.queuePrefetch=100属性,测试代码如下:
@Before
public void addMessages() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-without-dmlc.xml");
JmsTemplate jmsTemplate = (JmsTemplate) context.getBean("normalJmsTemplate");
int times = 150;
ISendTest normalTest = new NormalMQSend("example.MyQueue", times, jmsTemplate);
normalTest.run();
System.out.println("add message to activemq finished.");
}
@org.junit.Test
public void fetchMessage() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-with-sleep-receiver.xml");
context.start();
try {
TimeUnit.MINUTES.sleep(5);
} catch (Exception e) {
e.fillInStackTrace();
}
// assertEquals(true, true);
}
在consumer消费消息之前,向mq中添加150条消息,依据prefetch limit为100,猜想broker push到两个consumer的消息数量应该分别是100和50,运行测试用例后,根据logback打印的线程id消费记录,证实了猜想。
也就是说,broker会一次性将prefetch limit数量的消息push到一个consumer,这会导致当前consumer压力增大,同时其他consumer由于broker没有可用消息而空闲,这显然不符合常规。
Spring + JmsTemplate 收发消息
现在线上使用JmsTemplate.send发送消息到broker,并使用DefaultMessageListenerContainer接收消息,并没有发现异常,相比之前的版本,总结了使用过程中应该注意的点。
使用JmsTemplate 需要注意的点
mq官网关于JmsTemplate使用过程中应该注意的点有详细说明。我这里自己再总结一次。
每次调用JmsTemplate的发送和接收方法都会创建connection、session、producer或consumer
这种特性在消息量并不大的时候并不会对服务产生较大影响,一旦消息量增大,由于需要频繁的创建网络链接,从而导致服务性能急剧下降,甚至影响到正常业务。之前一次支付改版,就是因为没有池化网络链接,在下班高峰期producer端几近崩溃,所以这里需要做两件事:
a. producer端的connectionFactory由普通的ActiveMQConnectionFactory改为PooledConnectionFactory或CachingConnectionFactory,同时注意调整这两个connectionFactory的参数设置。
b. 将consumer端改为DefaultMessageListenerContainer接收。JmsTemplate.send 默认采用同步发送
即使按照上一条,使用PooledConnectionFactory或CachingConnectionFactory后,测试发现producer端的性能提升并不明显,问题在于,默认情况下,send方法会调用ActiveMQConnection.syncSendPacket,也就是走的同步发送。producer在发送消息到broker,broker将消息持久化,并返回ProducerAck后,此次send操作才算完成,如果不计较少量消息丢失,可以配置brokerURL,使用异步发送:tcp://127.0.0.1:61616?jms.useAsyncSend=true&jms.producerWindowSize=1024000
尽量避免使用JmsTemplate.receive方法
如果当前broker中没有消息,consumer端调用JmsTemplate.receive方法会阻塞,虽然可以设置receive的超时时间,但是根据调用一次,创建一个新connection、session和consumer的尿性,原生的recevie方法简直鸡肋。 另外,由于prefetch机制的作用,同时receive方法每次调用之后就会close掉当前的connection、session和consumer,会浪费网络带宽,比如,设置prefetch limit为1000,当broker中消息较多时( > 1000),broker将会push 1000条消息到consumer端,由于receive每次处理一条消息,剩下的999条消息即使到达consumer端的缓冲区中,仍然无法消息,broker仍然需要重递这些消息(可以根据这些消息的JMSXDeliveryCount属性值判断该消息是否被重递),如此反复,显然会浪费很多资源。
DefaultMessageListenerContainer的使用
DefaultMessageListenerContainer支持动态扩容,另外,使用DMLC时,不要使用PooledConnectionFactory或CachingConnectionFactory,而应该将资源管理交给它自己处理。详细说明可以参考官方文档。
如果并不想在Spring配置文件中初始化DMLC,而偏向于在代码中创建,那么在创建之后需要初始化,否则container并不能接收消息,正确的做法是调用container的afterPropertiesSet和start方法。
ActiveMQ的初夜的更多相关文章
- JMS服务器ActiveMQ的初体验并持久化消息到MySQL数据库中
JMS服务器ActiveMQ的初体验并持久化消息到MySQL数据库中 一.JMS的理解JMS(Java Message Service)是jcp组织02-03年定义了jsr914规范(http://j ...
- apache ActiveMQ之初体验
版权声明: https://blog.csdn.net/zdp072/article/details/27237549 一. 开篇语 继上一篇weblogic中使用jms发送和接受消息的文章后, 本文 ...
- MySQL初夜(乱码问题,命令行客户端使用)
一.乱码问题 装好MySQL,并且将数据从SQLServer导入到MySQL之后,程序一直报错. 解决方案: 首先,输入命令: show variables like "character_ ...
- spring整合JMS - 基于ActiveMQ实现
一. 开篇语 继上一篇apache ActiveMQ之初体验后, 由于近期一直在复习spring的东西, 所以本文就使用spring整合下JMS. 二. 环境准备 1. ActiveMQ5.2.0 ( ...
- [转]论acm与泡妞
abstract :本文从各个方面讨论了泡妞与做题之间的相似之处与不同点,尽量的站在一个公平的角度阐述这一问题,所得的研究成果填补了国内外的理论空白. - 泡了一个好妞就好像做了一道难题一样快感都是相 ...
- 夺命雷公狗---微信开发17----自定义菜单的事件推送,响应菜单的CLICK
废话不多说,index.php 代码如下所示: <?php /** * wechat php test */ //define your token require_once "com ...
- [bilibili]弹幕屏蔽列表
<filters> <item enabled="true">t=定单身</item> <item enabled="true& ...
- 晋城6397.7539(薇)xiaojie:晋城哪里有xiaomei
晋城哪里有小姐服务大保健[微信:6397.7539倩儿小妹[晋城叫小姐服务√o服务微信:6397.7539倩儿小妹[晋城叫小姐服务][十微信:6397.7539倩儿小妹][晋城叫小姐包夜服务][十微信 ...
- ActiveMQ初体验(转)
转载地址:http://www.cnblogs.com/diorlv/p/3328712.html 做了修改 首先介绍下MQ,MQ英文名MessageQueue,中文名也就是大家用的消息队列,干嘛用的 ...
随机推荐
- Codeforces Round #284 (Div. 2) C题(计算几何)解题报告
题目地址 简要题意: 给出两个点的坐标,以及一些一般直线方程Ax+B+C=0的A.B.C,这些直线作为街道,求从一点走到另一点需要跨越的街道数.(两点都不在街道上) 思路分析: 从一点到另一点必须要跨 ...
- (BFS)poj3669-Meteor Shower
题目地址 为判断某时刻能否走到某位置,建立shi数组,记录某位置最早t时刻就不能走.(初始化为-1)之后开始从(0,0)出发bfs,用bu数组记录走到某一位置时花费的步数,并且需要用vi数组记录是否走 ...
- 为bootstrap添加更多自定义图标
From: http://blog.csdn.net/mengxiangfeiyang/article/details/45224731 Twitter Bootstrap 真是前端开发的瑞士军刀,作 ...
- 在Django中进行注册用户的邮件确认
之前利用Flask写博客时(http://hbnnlove.sinaapp.com),我对注册模块的逻辑设计很简单,就是用户填写注册表单,然后提交,数据库会更新User表中的数据,字段主要有用户名,哈 ...
- $scope 的生命周期
当Angular关心的事件发生在浏览器中时,比如用户在通过ng-model属性监控的输入字段中输入,或者带有ng-click属性的按钮被点击时,Angular的事件循环都会启动.这个事件将在Angul ...
- JavaScript中作用域和作用域链解析
学习js,肯定要学习作用域,js作用域和其他的主流语言的作用域还存在很大的区别. 一.js没有块级作用域. js没有块级作用域,就像这样: if(){ : console.log(a) //输出100 ...
- ORA-12514: TNS: 监听程序当前无法识别连接描述符中请求的服务解决
先看oracle的监听和oracle的服务是否都启动了. 启动oracle监听:cmd命令行窗口下,输入lsnrctl start,回车即启动监听. 查看oracle的sid叫什么,比如创建数据库的时 ...
- cookie怎么用
cookie是什么? cookie是浏览器提供的一种机制,它将document 对象的cookie属性提供给JavaScript.可以由JavaScript对其进行控制,而并不是JavaScript本 ...
- Remove openjdk in Ubuntu/Configure jdk and running adb in 64-bit Ubuntu
sudo apt-get autoremove openjdk-7-jre sudo apt-get purge openjdk* java -version No openjdk available ...
- SQLSERVER排查CPU占用高的情况
SQLSERVER排查CPU占用高的情况 今天中午,有朋友叫我帮他看一下数据库,操作系统是Windows2008R2 ,数据库是SQL2008R2 64位 64G内存,16核CPU 硬件配置还是比较高 ...