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的配置文件中设置sendFailNoSpaceAfterTimeoutsendFailNoSpace,设置之后,如果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使用过程中应该注意的点有详细说明。我这里自己再总结一次。

  1. 每次调用JmsTemplate的发送和接收方法都会创建connection、session、producer或consumer 
    这种特性在消息量并不大的时候并不会对服务产生较大影响,一旦消息量增大,由于需要频繁的创建网络链接,从而导致服务性能急剧下降,甚至影响到正常业务。之前一次支付改版,就是因为没有池化网络链接,在下班高峰期producer端几近崩溃,所以这里需要做两件事:
    a. producer端的connectionFactory由普通的ActiveMQConnectionFactory改为PooledConnectionFactoryCachingConnectionFactory,同时注意调整这两个connectionFactory的参数设置。
    b. 将consumer端改为DefaultMessageListenerContainer接收。

  2. JmsTemplate.send 默认采用同步发送
    即使按照上一条,使用PooledConnectionFactoryCachingConnectionFactory后,测试发现producer端的性能提升并不明显,问题在于,默认情况下,send方法会调用ActiveMQConnection.syncSendPacket,也就是走的同步发送。producer在发送消息到broker,broker将消息持久化,并返回ProducerAck后,此次send操作才算完成,如果不计较少量消息丢失,可以配置brokerURL,使用异步发送:

    tcp://127.0.0.1:61616?jms.useAsyncSend=true&jms.producerWindowSize=1024000

  3. 尽量避免使用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时,不要使用PooledConnectionFactoryCachingConnectionFactory,而应该将资源管理交给它自己处理。详细说明可以参考官方文档

如果并不想在Spring配置文件中初始化DMLC,而偏向于在代码中创建,那么在创建之后需要初始化,否则container并不能接收消息,正确的做法是调用container的afterPropertiesSetstart方法。

ActiveMQ的初夜的更多相关文章

  1. JMS服务器ActiveMQ的初体验并持久化消息到MySQL数据库中

    JMS服务器ActiveMQ的初体验并持久化消息到MySQL数据库中 一.JMS的理解JMS(Java Message Service)是jcp组织02-03年定义了jsr914规范(http://j ...

  2. apache ActiveMQ之初体验

    版权声明: https://blog.csdn.net/zdp072/article/details/27237549 一. 开篇语 继上一篇weblogic中使用jms发送和接受消息的文章后, 本文 ...

  3. MySQL初夜(乱码问题,命令行客户端使用)

    一.乱码问题 装好MySQL,并且将数据从SQLServer导入到MySQL之后,程序一直报错. 解决方案: 首先,输入命令: show variables like "character_ ...

  4. spring整合JMS - 基于ActiveMQ实现

    一. 开篇语 继上一篇apache ActiveMQ之初体验后, 由于近期一直在复习spring的东西, 所以本文就使用spring整合下JMS. 二. 环境准备 1. ActiveMQ5.2.0 ( ...

  5. [转]论acm与泡妞

    abstract :本文从各个方面讨论了泡妞与做题之间的相似之处与不同点,尽量的站在一个公平的角度阐述这一问题,所得的研究成果填补了国内外的理论空白. - 泡了一个好妞就好像做了一道难题一样快感都是相 ...

  6. 夺命雷公狗---微信开发17----自定义菜单的事件推送,响应菜单的CLICK

    废话不多说,index.php 代码如下所示: <?php /** * wechat php test */ //define your token require_once "com ...

  7. [bilibili]弹幕屏蔽列表

    <filters> <item enabled="true">t=定单身</item> <item enabled="true& ...

  8. 晋城6397.7539(薇)xiaojie:晋城哪里有xiaomei

    晋城哪里有小姐服务大保健[微信:6397.7539倩儿小妹[晋城叫小姐服务√o服务微信:6397.7539倩儿小妹[晋城叫小姐服务][十微信:6397.7539倩儿小妹][晋城叫小姐包夜服务][十微信 ...

  9. ActiveMQ初体验(转)

    转载地址:http://www.cnblogs.com/diorlv/p/3328712.html 做了修改 首先介绍下MQ,MQ英文名MessageQueue,中文名也就是大家用的消息队列,干嘛用的 ...

随机推荐

  1. React Native 一个组件styles BUG

    'use strict'; var React = require('react-native'); var { StyleSheet, PanResponder, View, Text } = Re ...

  2. Eclipse中的一些快捷键的使用

    Eclipse是一款强大的编程工具,在使用的过程中,若能够有效的使用其快捷键,效率会得到很大的提升,下面是一些常用的eclipse快捷键,可谓是键键精彩. 1.成单词:Alt+/ 2 重构之重命名:S ...

  3. Apache MiNa 实现多人聊天室

    Apache MiNa 实现多人聊天室 开发环境: System:Windows JavaSDK:1.6 IDE:eclipse.MyEclipse 6.6 开发依赖库: Jdk1.4+.mina-c ...

  4. Scala的下一步

    第七步:带类型的参数化数组 Scala里可以使用new实例化对象或类实例.当你在Scala里实例化对象,可以使用值和类型把它参数化:parameterize.参数化的意思是在你创建实例的时候“设置”它 ...

  5. x-forward-for详解

    转载:http://www.360doc.com/content/14/0110/17/15459414_344165975.shtml 如今利用nginx做负载均衡的实例已经很多了,针对不同的应用场 ...

  6. hibernate开发(2)

    1 hibernate 的缓存机制 在程序运行中,hibernate要不断访问物理数据库,为了降低访问频率,提升性能,会复制一部分数据到缓存中,使得hibernate可以从缓存中读写数据,然后在特定时 ...

  7. Kindle使用的一些方法

    最大的好处就是方便,买书便宜,到手我就买了六部书,十块钱不到,以纸书的价格一本都买不到,能够买一些一直想读一下,但又担心读不下去的书.而且买了之后完全不用担心书柜收纳不下了.另外很轻便,放在包里上下班 ...

  8. 《C++primer》v5 第4章 表达式 读书笔记 习题答案

    4.1 105 4.2 *vec.begin()=*(vec.begin())//先调用点运算符,再解引用 *vec.begin()+1=(*vec.begin())+1//先解引用,再加一 4.3略 ...

  9. 2016年Q2《网络安全创新500强》榜单解读

    近日,美国投资咨询机构Cybersecurity Ventures发布了2016 Q2<网络安全创新500强>企业榜单,新兴安全公司root9B异军突起,国内4家企业上榜. 关于Cyber ...

  10. iOS的触摸事件的用法以及和手势识别器的区别

    1.首先来介绍下触摸事件和手势识别器的利与弊 触摸事件和手势识别器二者之间有直接的关系 手势识别器是在触摸事件的基础上演变过来的 当我们用到触摸事件时 默认的uiview是没有什么效果的 只能自定义v ...