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,中文名也就是大家用的消息队列,干嘛用的 ...
随机推荐
- iOS 支付宝第三方使用步骤
使用支付宝进行一个完整的支付功能,大致有以下步骤: 1 与支付宝进行签约,获得商户ID(partner)和账号ID(seller) 2 下载相应的公钥私钥文件(加密签名用) 3 下载支付宝SDK 4 ...
- Bootstrap<基础十> 响应式实用工具
Bootstrap 提供了一些辅助类,以便更快地实现对移动设备友好的开发.这些可以通过媒体查询结合大型.小型和中型设备,实现内容对设备的显示和隐藏. 需要谨慎使用这些工具,避免在同一个站点创建完全不同 ...
- Redis并发锁控制
为了防止用户在页面上重复点击或者同时发起多次请求,请求处理需要操作redis缓存,这个时候需要对并发边界进行并发锁控制,实现思路: 由于每个页面发起的请求带的token具备唯一性,可以将token作为 ...
- 黑马程序员——OC语言基础语法 面向对象的思想
Java培训.Android培训.iOS培训..Net培训.期待与您交流! (以下内容是对黑马苹果入学视频的个人知识点总结)(一)基础语法 1)关键字 @interface.@implementati ...
- c++中变量声明和变量定义的区别。2016年12月6日
整个流程: 1.程序告诉cpu,程序将要使用一个变量.(暂时不一定用到,先说一下.) 2.程序告诉CPU,程序现在就要使用一个变量.(现在就用) 3.cpu按照这个变量的类型,把内存划分出几个单位(b ...
- unity代码加密for Android,mono编译
uinty3d加密推荐几篇比较好的博客链接: http://www.cppcourse.com/u3d-encryption.html http://www.xuanyusong.com/archiv ...
- odi 12.2.1中访问excel文件
由于在odi 12.2.1中,必须使用jdk1.8,而jdk1.8中jdbc-odbc bridge已经不再支持,因此,可以使用Progress DataDirect SequeLink来充当jdbc ...
- run方法和start方法的不同
run 方法只不过是对对象方法的简单调用,在主线程中的执行时间是固定的 而start方法是开启一个线程,起执行时间是不固定的.
- putty连接报NetWork error:connection refused
首先通过物理终端进入到linux上,手工检查ssh发现没运行 /etc/init.d/sshd status 使用rpm -V 命令可检查到ssh的软件包正常 /rpm -V openssh-serv ...
- ubuntu上用eclipse搭建java、python开发环境
上一篇文章讲到如何在windwos上用eclipse搭建java.python开发环境,这一讲将关注如何在ubuntu上实现搭建,本人使用虚拟机安装的ubuntu系统,系统版本为:14.04 lts ...