http://activemq.apache.org/producer-flow-control.html

翻译:

流量控制是指:如果broker检测到destination的内存限制、temp文件限制、file store限制被超过了,就会减慢消息的流动。producer会被阻塞直到有可用资源,或者收到一个JMSException:这些行为是可以配置的。

值得一提的是:默认的<systemUsage>设置会导致producer阻塞,当达到memoryLimit或<systemUsage>的限制值时,这种阻塞行为有时被误认为是一个"挂起的producer",而事实上producer只是在等待可用空间。

同步发送的消息会自动使用按producer的流量控制;通常会应用到同步发送持久化消息,除非你开启useAsyncSend标志。

使用异步发送的producer(通常讲,发送非持久化消息的producer)不需要等待来自broker的确认;所以,当达到memory limit时,你不会被通知到。如果你想知道broker的限制值达到了,你需要配置 ProducerWindowSize 连接参数,然后异步的消息也能按producer控制流量了。

ActiveMQConnectionFactory connctionFactory = ... 
connctionFactory.setProducerWindowSize(1024000);

ProducerWindowSize 是producer发送的最大字节数,在等待来自broker的消息确认前。 如果你在发送非持久化消息(默认异步发送),并且希望知道queue或topic的memory limit是否达到了。那么你需要设置connection factory 为 'alwaysSyncSend'。但是,这会降低速度,它能保证你的producer马上知道内存问题。 如果你喜欢,你可以关掉指定jms queue和topic的流量控制,例如:

<destinationPolicy>
<policyMap>
<policyEntries>
<policyEntry topic="FOO.>" producerFlowControl="false"/>
</policyEntries>
</policyMap>
</destinationPolicy>

注意,在ActiveMQ 5.x中引入了新的file cursor,非持久化消息会被刷到临时文件存储中来减少内存使用量。所以,你会发现queue的memoryLimit永远达不到,因为file cursor花不了多少内存,如果你真的要把所有非持久化消息保存在内存中,并且当memoryLimit达到时停止producer,你应该配置<vmQueueCursor>。

<policyEntry queue=">" producerFlowControl="true" memoryLimit="1mb">
<pendingQueuePolicy>
<vmQueueCursor/>
</pendingQueuePolicy>
</policyEntry>

上面的片段能保证,所有的消息保存在内存中,并且每一个队列只有1Mb的限制。

How Producer Flow Control works

如果你在发送持久化消息,broker会发送一个ProducerAck消息给producer,它告知producer前一个发送窗口已经被处理了,所以producer现在可以发送下一个窗口。这和consumer的消息确认很像。当没有空间可用时,调用send()操作会无限阻塞,另一种方法是在客户端抛出异常。通过设置sendFailIfNoSpace为true,broker会导致send()抛出javax.jms.ResourceAllocationException,异常会传播到客户端。下面是配置示例:

<systemUsage>
<systemUsage sendFailIfNoSpace="true">
<memoryUsage>
<memoryUsage limit="20 mb"/>
</memoryUsage>
</systemUsage>
</systemUsage>

这种做法的好处是,客户端会捕获一个javax.jms.ResourceAllocationException异常,等一会然后重试send(),而不再是无限地等待。

从5.3.1开始,加入了sendFailIfNoSpaceAfterTimeout属性,如果broker在配置的时间内仍然没有空余空间,此时send()才会失败,并且把异常传递到客户端,下面是配置:

<systemUsage>
<systemUsage sendFailIfNoSpaceAfterTimeout="3000">
<memoryUsage>
<memoryUsage limit="20 mb"/>
</memoryUsage>
</systemUsage>
</systemUsage>

关闭流量控制

通常的需求是关闭流量控制,这样消息分发可以一直进行直到磁盘空间被 pending messages 耗尽。

通过配置<systemUsage>元素的某些属性,你可以降低producer的速率。

<systemUsage>
<systemUsage>
<memoryUsage>
<memoryUsage limit="64 mb" />
</memoryUsage>
<storeUsage>
<storeUsage limit="100 gb" />
</storeUsage>
<tempUsage>
<tempUsage limit="10 gb" />
</tempUsage>
</systemUsage>
</systemUsage>

<memoryUsage>对应NON_PERSISTENT消息的内存容量,<storeUsage> 对应PERSITENT消息的磁盘容量,<tempUsage>对应临时文件的磁盘容量。

结合代码分析:

client-side: org.apache.activemq.ActiveMQMessageProducer.send

broker-side:从 org.apache.activemq.broker.region.Queue.send 开始

//org.apache.activemq.broker.region.Queue
public void send(final ProducerBrokerExchange producerExchange, final Message message) throws Exception {
final ConnectionContext context = producerExchange.getConnectionContext();
// There is delay between the client sending it and it arriving at the
// destination.. it may have expired.
message.setRegionDestination(this);
ProducerState state = producerExchange.getProducerState();
if (state == null) {
LOG.warn("Send failed for: {}, missing producer state for: {}", message, producerExchange);
throw new JMSException("Cannot send message to " + getActiveMQDestination() + " with invalid (null) producer state");
}
final ProducerInfo producerInfo = producerExchange.getProducerState().getInfo();
//是否发送ProducerAck
final boolean sendProducerAck = !message.isResponseRequired() && producerInfo.getWindowSize() > 0
&& !context.isInRecoveryMode();
if (message.isExpired()) {
// message not stored - or added to stats yet - so check here
broker.getRoot().messageExpired(context, message, null);
if (sendProducerAck) {
ProducerAck ack = new ProducerAck(producerInfo.getProducerId(), message.getSize());
context.getConnection().dispatchAsync(ack);
}
return;
}
if (memoryUsage.isFull()) { //如果内存耗尽
// 尽管这里有大段代码,但是调试没进这儿
}
doMessageSend(producerExchange, message);
if (sendProducerAck) {
ProducerAck ack = new ProducerAck(producerInfo.getProducerId(), message.getSize());
context.getConnection().dispatchAsync(ack);
}
}

把消息放进 broker 的 pendingList 之前,会检查可用空间:

//org.apache.activemq.broker.region.Queue
void doMessageSend(final ProducerBrokerExchange producerExchange, final Message message)
throws IOException, Exception {
final ConnectionContext context = producerExchange.getConnectionContext();
ListenableFuture<Object> result = null;
boolean needsOrderingWithTransactions = context.isInTransaction(); producerExchange.incrementSend();
//检查使用空间
checkUsage(context, producerExchange, message);
sendLock.lockInterruptibly();
try {
if (store != null && message.isPersistent()) {
try {
message.getMessageId().setBrokerSequenceId(getDestinationSequenceId());
if (messages.isCacheEnabled()) {
result = store.asyncAddQueueMessage(context, message, isOptimizeStorage());
result.addListener(new PendingMarshalUsageTracker(message));
} else {
store.addMessage(context, message);
}
if (isReduceMemoryFootprint()) {
message.clearMarshalledState();
}
} catch (Exception e) {
// we may have a store in inconsistent state, so reset the cursor
// before restarting normal broker operations
resetNeeded = true;
throw e;
}
}
// did a transaction commit beat us to the index?
synchronized (orderIndexUpdates) {
needsOrderingWithTransactions |= !orderIndexUpdates.isEmpty();
}
if (needsOrderingWithTransactions ) {
// If this is a transacted message.. increase the usage now so that
// a big TX does not blow up
// our memory. This increment is decremented once the tx finishes..
message.incrementReferenceCount(); registerSendSync(message, context);
} else {
// Add to the pending list, this takes care of incrementing the
// usage manager.
sendMessage(message);
}
} finally {
sendLock.unlock();
}
if (!needsOrderingWithTransactions) {
messageSent(context, message);
}
if (result != null && message.isResponseRequired() && !result.isCancelled()) {
try {
result.get();
} catch (CancellationException e) {
// ignore - the task has been cancelled if the message
// has already been deleted
}
}
}

对持久化消息和非持久化消息分类检查:

// org.apache.activemq.broker.region.Queue
private void checkUsage(ConnectionContext context,ProducerBrokerExchange producerBrokerExchange, Message message)
throws ResourceAllocationException, IOException, InterruptedException {
if (message.isPersistent()) { // 持久化消息
if (store != null && systemUsage.getStoreUsage().isFull(getStoreUsageHighWaterMark())) {
final String logMessage = "Persistent store is Full, " + getStoreUsageHighWaterMark() + "% of "
+ systemUsage.getStoreUsage().getLimit() + ". Stopping producer ("
+ message.getProducerId() + ") to prevent flooding "
+ getActiveMQDestination().getQualifiedName() + "."
+ " See http://activemq.apache.org/producer-flow-control.html for more info"; waitForSpace(context, producerBrokerExchange, systemUsage.getStoreUsage(), getStoreUsageHighWaterMark(), logMessage);
}
} else if (messages.getSystemUsage() != null && systemUsage.getTempUsage().isFull()) {
// 非持久化消息
final String logMessage = "Temp Store is Full ("
+ systemUsage.getTempUsage().getPercentUsage() + "% of " + systemUsage.getTempUsage().getLimit()
+"). Stopping producer (" + message.getProducerId()
+ ") to prevent flooding " + getActiveMQDestination().getQualifiedName() + "."
+ " See http://activemq.apache.org/producer-flow-control.html for more info"; waitForSpace(context, producerBrokerExchange, messages.getSystemUsage().getTempUsage(), logMessage);
}
}

最后进入具体的处理逻辑:

// org.apache.activemq.broker.region.BaseDestination
protected final void waitForSpace(ConnectionContext context, ProducerBrokerExchange producerBrokerExchange,
Usage<?> usage, int highWaterMark, String warning)
throws IOException, InterruptedException, ResourceAllocationException {
// 如果配置了sendFailIfNoSpace="true"
if (!context.isNetworkConnection() && systemUsage.isSendFailIfNoSpace()) {
getLog().debug("sendFailIfNoSpace, forcing exception on send, usage: {}: {}", usage, warning);
throw new ResourceAllocationException(warning);
}
if (!context.isNetworkConnection() && systemUsage.getSendFailIfNoSpaceAfterTimeout() != 0) {
if (!usage.waitForSpace(systemUsage.getSendFailIfNoSpaceAfterTimeout(), highWaterMark)) {
getLog().debug("sendFailIfNoSpaceAfterTimeout expired, forcing exception on send, usage: {}: {}", usage, warning);
throw new ResourceAllocationException(warning);
}
} else {
long start = System.currentTimeMillis();
long nextWarn = start;
producerBrokerExchange.blockingOnFlowControl(true);
destinationStatistics.getBlockedSends().increment();
while (!usage.waitForSpace(1000, highWaterMark)) {
if (context.getStopping().get()) {
throw new IOException("Connection closed, send aborted.");
} long now = System.currentTimeMillis();
if (now >= nextWarn) {
getLog().info("{}: {} (blocking for: {}s)", new Object[]{ usage, warning, new Long(((now - start) / 1000))});
nextWarn = now + blockedProducerWarningInterval;
}
}
long finish = System.currentTimeMillis();
long totalTimeBlocked = finish - start;
destinationStatistics.getBlockedTime().addTime(totalTimeBlocked);
producerBrokerExchange.incrementTimeBlocked(this,totalTimeBlocked);
producerBrokerExchange.blockingOnFlowControl(false);
}
}

如果配置了sendFailIfNoSpace="true",并且抛出异常了,处理异常的调用栈如下:

//org.apache.activemq.broker.TransportConnection
public void serviceException(Throwable e) {
if (...) {
...
}
else if (!stopping.get() && !inServiceException) {
inServiceException = true;
try {
SERVICELOG.warn("Async error occurred: ", e);
ConnectionError ce = new ConnectionError();
ce.setException(e);
if (pendingStop) {
dispatchSync(ce);
} else {
dispatchAsync(ce);
}
} finally {
inServiceException = false;
}
}
}

那么 producer 是如何处理 ConnectionError 消息呢?

在org.apache.activemq.ActiveMQConnection.onCommand(Object o) 方法中:

public Response processConnectionError(final ConnectionError error) throws Exception {
executor.execute(new Runnable() {
@Override
public void run() {
onAsyncException(error.getException());
}
});
return null;
}

ActiveMQ producer 流量控制的更多相关文章

  1. ActiveMQ producer同步/异步发送消息

    http://activemq.apache.org/async-sends.html producer发送消息有同步和异步两种模式,可以通过代码配置: ((ActiveMQConnection)co ...

  2. ActiveMQ producer 提交事务时突然宕机,会发生什么

    producer 在提交事务时,发生宕机,commit 的命令没有发送到 broker,这时会发生什么? ActiveMQ 开启事务发送消息的步骤: session.getTransactionCon ...

  3. ActiveMQ producer不断发送消息,会导致broker内存耗尽吗?

    http://activemq.apache.org/my-producer-blocks.html 回答了这个问题: ActiveMQ 5.x 支持Message Cursors,它默认把消息从内存 ...

  4. ActiveMQ 事务、集群、持久订阅者、ActiveMQ监控

    JMS介绍 JMS是什么? JMS的全称Java Message Service,既Java消息服务. JMS是SUN提供的旨在统一各种MOM(Message-Oriented Middleware) ...

  5. Spring mvc4 + ActiveMQ 整合

    一.配置部分 二.代码部分 三.页面部分 四.Controller控制器 五.效果展示 六.加入监听器 七.最最重要的,别忘了打赏 一.配置部分 ActiveMQ的安装这就不说了,很简单, 这个例子采 ...

  6. Web项目容器集成ActiveMQ & SpringBoot整合ActiveMQ

    集成tomcat就是随项目启动而启动tomcat,最简单的方法就是监听器监听容器创建之后以Broker的方式启动ActiveMQ. 1.web项目中Broker启动的方式进行集成 在这里采用Liste ...

  7. ActiveMQ之topic主题模式

    开发环境我们使用的是ActiveMQ 5.11.1 Release的Windows版,官网最新版是ActiveMQ 5.12.0 Release,大家可以自行下载,下载地址.需要注意的是,开发时候,要 ...

  8. activemq学习笔记2

    基本步骤: ConnectionFactory factory = new ActiveMQConnectionFactory("tcp://127.0.0.1:61616"); ...

  9. 用maven和spring搭建ActiveMQ环境

    前面搭建过了简单的环境,这次用稍微实际一点的maven+spring+activemq来进行搭建 准备:win7,eclipse,jdk1.8,tomcat8,maven3.5.2,activemq5 ...

随机推荐

  1. transient关键字详解

    作用 1,一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问. 2,transient关键字只能修饰变量,而不能修饰方法和类.注意,本地变量是不能被tr ...

  2. vscode已有64位版本。

    我的操作系统是win10 Family版本. vscode不知道什么鬼,只要开启没动任何操作,cpu就占到30%. 于是我打开任务管理器,选中vscode进程->转到详细信息->结束cpu ...

  3. Codeforces 786 A. Berzerk

    题目链接:http://codeforces.com/problemset/problem/786/A 这个题出做$DIV2$的$C$以及$DIV1$的A会不会难了一点啊... 做法和题解并不一样,只 ...

  4. 关于spring boot启动监控端点的方法(spring-boot-starter-actuator)

    前言: 在spring boot的旧版本中,监控端点(如/env)是默认开启的,所以只要项目正常启动,就能通过url获取信息.可是在2.0版本以后,由于安全性考虑,除了/health和/info的端点 ...

  5. oracle中索引的使用

    声明:以下内容是自己跟着教学视屏学习之后整理而来(主要是自用),如有侵权请告知,将尽快删除. 一.索引 1. 概述:数据库对象之一索引用于提高查询效率索引的内建工作对用户是透明的,由数据库自行维护,我 ...

  6. Android主页Activity对多个Fragment实现不同的沉浸式标题(图片或者文字标题)

    提示:讲解的该例实现是 FragmentTabHost + Fragment 实现: 1.示例效果图: 2.场景需求: 如示例图所示,在首页实现轮播图的沉浸,而 “发现” 和“我的”页是标题的沉浸. ...

  7. 力扣(LeetCode) 905. 按奇偶排序数组

    给定一个非负整数数组 A,返回一个由 A 的所有偶数元素组成的数组,后面跟 A 的所有奇数元素. 你可以返回满足此条件的任何数组作为答案. 示例: 输入:[3,1,2,4] 输出:[2,4,3,1] ...

  8. BFC(块级格式化上下文)

    转载自:https://www.cnblogs.com/asheng2016/p/7281784.html https://blog.csdn.net/jiaojsun/article/details ...

  9. Qt5获取本机网络信息

    获取本机网络信息 在pro文件中加入如下代码 QT += network widget.h中的代码如下 #ifndef WIDGET_H #define WIDGET_H #include <Q ...

  10. Unity --- 如何降低UI的填充率

    1.首先简单介绍一下什么叫填充率: Fill Rate(填充率)是指显卡每帧或者说每秒能够渲染的像素数.在每帧绘制中,如果一个像素被反复绘制的次数越多,那么它占用的资源也必然更多.目前在移动设备上,F ...