【Azure 服务总线】详解Azure Service Bus SDK中接收消息时设置的maxConcurrentCalls,prefetchCount参数
(Azure Service Bus服务总线的两大类消息处理方式: 队列Queue和主题Topic)
问题描述
使用Service Bus作为企业消息代理,当有大量的数据堆积再Queue或Topic中时,如何来优化接收端处理消息的能力呢?
详细解释
在接收端(Receover)的代码中,有两个属性与处理消息的能力有关。一是maxConcurrentCalls(最大并发处理数), 二是prefetchCount (预提取消息数)。 在Service Bus的SDK(azure-messaging-servicebus:7.0.0.0)中,他们的描述如下:
maxConcurrentCalls |
接收端所定义的ServiceBusProcessorClient处理的最大并发消息数。
The max concurrent messages that should be processed by the processor. |
package com.azure.messaging.servicebus.implementation.models; ... ... public final class ServiceBusProcessorClientOptions { |
prefetchCount |
接收端要预先提取的消息数 The number of messages to prefetch |
package com.azure.messaging.servicebus; |
在初始化ServiceBusProcessorClient对象时,可以设置maxConcurrentCalls和prefetchCount的值。如
int concall=5;
int prefetch =10;
// Create an instance of the processor through the ServiceBusClientBuilder
ServiceBusProcessorClient processorClient = new ServiceBusClientBuilder().connectionString(connectionString)
.processor().topicName(topicName).subscriptionName(subName).processMessage(messageProcessor)
.processError(errorHandler).maxConcurrentCalls(concall).prefetchCount(prefetch).buildProcessorClient();
System.out.println("Starting the processor");
System.out.println("Set Processor: maxConcurrentCalls = "+concall+", prefetchCount = "+prefetch);
实验验证
在本次的实验中,如何来验证maxConcurrentCalls值启作用了呢?如何判断prefetchCount是否获取了消息呢?
- 针对maxConcurrentCalls,可以在处理消息的代码中[processMessage(messageProcessor)]打印出当前线程的ID[Thread.currentThread().getId()]。
- 针对prefetchCount,可以从侧面来验证,即获取message的DeliveryCount来判断已经预提取了多少次
本次实验的代码参考Azure Service Bus的快速入门文档所编写,文末包含全部的代码和POM.XML文件。 从订阅接收消息:https://docs.azure.cn/zh-cn/service-bus-messaging/service-bus-java-how-to-use-topics-subscriptions#receive-messages-from-a-subscription
首先在代码中设置concall和prefetch值。默认情况下为1.本次实验也从1开始,在设定的10秒钟之内查看消费消息的数量。
int concall=1;
int prefetch =1;
// Create an instance of the processor through the ServiceBusClientBuilder
ServiceBusProcessorClient processorClient = new ServiceBusClientBuilder().connectionString(connectionString)
.processor().topicName(topicName).subscriptionName(subName).processMessage(messageProcessor)
.processError(errorHandler).maxConcurrentCalls(concall).prefetchCount(prefetch).buildProcessorClient();
System.out.println("Starting the processor");
System.out.println("Set Processor: maxConcurrentCalls = "+concall+", prefetchCount = "+prefetch); processorClient.start();
然后再处理消息的对象中,打印出当前处理消息的次序,消息ID,Delivery次数,处理消息的线程ID。
Consumer<ServiceBusReceivedMessageContext> messageProcessor = context -> {
ServiceBusReceivedMessage message = context.getMessage(); ordernumber++; System.out.println(ordernumber + " Message ID:" + message.getMessageId() + ",Current Delivery Count:"
+ message.getDeliveryCount() + ",Current Thread ID:" + Thread.currentThread().getId());
};
第一次实验:处理消息的线程号只有一个:21, 在10秒时间中处理23条消息
Hello World! |
第二次实验:处理消息的线程号有5个:21,21,23,24,25, 在10秒时间中处理42条消息
Hello World! |
第三次实验:处理消息的线程号有10个:21,21 ... 30, 在10秒时间中处理46条消息
Hello World! |
三次测试的结论
在测试中,由于测试的时长只有10秒,所以无法得出一个合理的maxConcurrentCalls和prefetchCount值。至少maxCouncurrentCalls的值能大幅度提升接收端(Receiver)处理消息的能力。在第三次的的测试中,我们发现Delivery Count的计数变为了1,这是因为在第二次测试中,我们设置的预提取数量为10,每次提取的数量大于了接收端能处理的数量。在10秒钟的测试中,并没有完全处理完所有提取出来的消息,以致于在第三次测试中,这些消息的Delivery次数从0变成了1。
优化建议
预提取可加快消息流程,方法是在应用程序请求消息时及请求消息前,准备好消息用于本地检索。
- 通过 ReceiveAndDelete 接收模式,预提取缓存区获取的所有消息在队列中不再可用,仅保留在内存中预提取缓存区,直到应用程序通过 Receive/ReceiveAsync 或 OnMessage/OnMessageAsync API 接收到它们 。 如果在应用程序接收到消息前终止应用程序,这些消息将丢失,且不可恢复。
- 在 PeekLock 接收模式下,提取到预提取缓存区的消息将以锁定状态进入缓存区,并且将超时时钟用于锁定计时。 如果预提取缓存区很大,且处理所需时间过长,以致消息锁定在驻留于预提取缓存区,甚至应用程序还在处理消息时就到期,可能出现一些令人困惑的事件要应用程序处理。
如果消息处理需要高度的可靠性,且处理需要大量精力和时间,则建议谨慎使用或者丝毫不用预提取功能。
如果需要较高吞吐量且消息处理通常比较便宜,则预提取会产生显著的吞吐量优势。
需要均衡对队列或订阅配置的最大预提取数和锁定持续时间,以便锁定超时至少超出最大预提取缓存区大小外加一条消息的累积预期消息处理时间。 同时,锁定超时不应过长,防止消息在被意外丢弃后超出其最大 TimeToLive,因此需要消息的锁定在重新传送消息前到期。
附录一:使用Service Bus Explorer工具快速生成大量消息
附录二:测试实例pom.xml内容
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>testgroupid</groupId>
<artifactId>testartifactid</artifactId>
<version>1.0-SNAPSHOT</version> <name>testartifactid</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties> <dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-messaging-servicebus</artifactId>
<version>7.0.0</version>
</dependency> <dependency>
<groupId>com.azure</groupId>
<artifactId>azure-messaging-servicebus</artifactId>
<version>7.0.0-beta.7</version>
</dependency>
</dependencies> <build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
附录三:App.java代码
package com.servicebus.test; import com.azure.messaging.servicebus.*;
import com.azure.messaging.servicebus.models.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.sql.Date;
import java.util.Arrays;
import java.util.List; /**
* Hello world!
*
*/
public class App { static String connectionString = "Endpoint=sb://xxxxxxxx.servicebus.chinacloudapi.cn/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
static String topicName = "thisistest";
static String subName = "lubusb1"; static int ordernumber = 0; public static void main(String[] args) throws InterruptedException {
System.out.println("Hello World!"); // sendMessage();
// sendMessageBatch(); receiveMessages(); System.out.println("Done World!");
} // handles received messages
static void receiveMessages() throws InterruptedException {
// Consumer that processes a single message received from Service Bus
Consumer<ServiceBusReceivedMessageContext> messageProcessor = context -> {
ServiceBusReceivedMessage message = context.getMessage(); ordernumber++; System.out.println(ordernumber + " Message ID:" + message.getMessageId() + ",Current Delivery Count:"
+ message.getDeliveryCount() + ",Current Thread ID:" + Thread.currentThread().getId());
}; // Consumer that handles any errors that occur when receiving messages
Consumer<Throwable> errorHandler = throwable -> {
System.out.println("Error when receiving messages: " + throwable.getMessage());
if (throwable instanceof ServiceBusReceiverException) {
ServiceBusReceiverException serviceBusReceiverException = (ServiceBusReceiverException) throwable;
System.out.println("Error source: " + serviceBusReceiverException.getErrorSource());
}
}; int concall=10;
int prefetch =30;
// Create an instance of the processor through the ServiceBusClientBuilder
ServiceBusProcessorClient processorClient = new ServiceBusClientBuilder().connectionString(connectionString)
.processor().topicName(topicName).subscriptionName(subName).processMessage(messageProcessor)
.processError(errorHandler).maxConcurrentCalls(concall).prefetchCount(prefetch).buildProcessorClient();
System.out.println("Starting the processor");
System.out.println("Set Processor: maxConcurrentCalls = "+concall+", prefetchCount = "+prefetch); processorClient.start(); TimeUnit.SECONDS.sleep(10);
System.out.println("Total Process Message Count = "+ordernumber+" in 10 seconds.");
System.out.println("Stopping and closing the processor");
processorClient.close();
} static void sendMessage() {
// create a Service Bus Sender client for the queue
ServiceBusSenderClient senderClient = new ServiceBusClientBuilder().connectionString(connectionString).sender()
.topicName(topicName).buildClient(); // send one message to the topic
senderClient.sendMessage(new ServiceBusMessage("Hello, World!"));
System.out.println("Sent a single message to the topic: " + topicName);
} static List<ServiceBusMessage> createMessages() {
// create a list of messages and return it to the caller
ServiceBusMessage[] messages = { new ServiceBusMessage("First message"),
new ServiceBusMessage("Second message"), new ServiceBusMessage("Third message") };
return Arrays.asList(messages);
} static void sendMessageBatch() {
// create a Service Bus Sender client for the topic
ServiceBusSenderClient senderClient = new ServiceBusClientBuilder().connectionString(connectionString).sender()
.topicName(topicName).buildClient(); // Creates an ServiceBusMessageBatch where the ServiceBus.
ServiceBusMessageBatch messageBatch = senderClient.createMessageBatch(); // create a list of messages
List<ServiceBusMessage> listOfMessages = createMessages(); // We try to add as many messages as a batch can fit based on the maximum size
// and send to Service Bus when
// the batch can hold no more messages. Create a new batch for next set of
// messages and repeat until all
// messages are sent.
for (ServiceBusMessage message : listOfMessages) {
if (messageBatch.tryAddMessage(message)) {
continue;
} // The batch is full, so we create a new batch and send the batch.
senderClient.sendMessages(messageBatch);
System.out.println("Sent a batch of messages to the topic: " + topicName); // create a new batch
messageBatch = senderClient.createMessageBatch(); // Add that message that we couldn't before.
if (!messageBatch.tryAddMessage(message)) {
System.err.printf("Message is too large for an empty batch. Skipping. Max size: %s.",
messageBatch.getMaxSizeInBytes());
}
} if (messageBatch.getCount() > 0) {
senderClient.sendMessages(messageBatch);
System.out.println("Sent a batch of messages to the topic: " + topicName);
} // close the client
senderClient.close();
}
}
参考资料
Service Bus Explorer:https://github.com/paolosalvatori/ServiceBusExplorer
预提取 Azure 服务总线消息:https://docs.azure.cn/zh-cn/service-bus-messaging/service-bus-prefetch#if-it-is-faster-why-is-prefetch-not-the-default-option
向 Azure 服务总线队列发送消息并从中接收消息 (Java):https://docs.azure.cn/zh-cn/service-bus-messaging/service-bus-java-how-to-use-queues
【Azure 服务总线】详解Azure Service Bus SDK中接收消息时设置的maxConcurrentCalls,prefetchCount参数的更多相关文章
- 【Azure 服务总线】Azure Service Bus中私信(DLQ - Dead Letter Queue)如何快速清理
在博文ServiceBus 队列中死信(DLQ - Dead Letter Queue)问题一文中,介绍了服务总线产生私信的原因及可以通过代码的方式来清楚私信队列中的消息,避免长期占用空间(因为私信中 ...
- Windows Azure 服务总线和物联网
机器到机器 (M2M) 计算正迅速成为一种技术,所有开发人员和架构师需要拥抱. 许多研究表明一个未来世界的数百亿美元的设备 (在地球上的每一个人的出现).MSDN杂志有2篇文章讨论Azure服务总线和 ...
- 【Android】详解Android Service
目录结构: contents structure [+] Service简单概述 Service在清单文件中的声明 Service启动服务 Service绑定服务 扩展Binder类 使用Messen ...
- 2-4、nginx特性及基础概念-nginx web服务配置详解
Nginx Nginx:engine X 调用了libevent:高性能的网络库 epoll():基于事件驱动event的网络库文件 Nginx的特性: 模块化设计.较好扩展性(不支持模块动态装卸载, ...
- Linux:SSH服务配置文件详解
SSH服务配置文件详解 SSH客户端配置文件 /etc/ssh/ssh——config 配置文件概要 Host * #选项“Host”只对能够匹配后面字串的计算机有效.“*”表示所有的计算机. For ...
- Nginx服务优化详解
Nginx服务优化详解 1.隐藏Nginx版本信息 编辑主配置文件nginx.conf,在http标签中添加代码 server_tokens off;来隐藏软件版本号. 2.更改Nginx服务启动的默 ...
- (转)Nginx静态服务配置---详解root和alias指令
Nginx静态服务配置---详解root和alias指令 原文:https://www.jianshu.com/p/4be0d5882ec5 静态文件 Nginx以其高性能著称,常用与做前端反向代理服 ...
- 【转】AXI_Lite 总线详解
目录: · 1.前言 · 2.AXI总线与ZYNQ的关系 · 3 AXI 总线和 AXI 接口以及 AXI 协议 · 3.1 AXI 总线概述 · 3.2 AXI 接口介绍 ·3.3 AXI 协议概述 ...
- Nginx静态服务配置---详解root和alias指令
Nginx静态服务配置---详解root和alias指令 静态文件 Nginx以其高性能著称,常用与做前端反向代理服务器.同时nginx也是一个高性能的静态文件服务器.通常都会把应用的静态文件使用ng ...
随机推荐
- 快速计算类似斐波那契数列数列的数列的第N项,矩阵快速幂
这个题有循环节,可以不用这么做,这个可以当一个模板 #include <iostream> #include <cstdio> using namespace std; typ ...
- ysoserial Commons Collections1反序列化研究
Apache Commons Collections1反序列化研究 环境准备 Apache Commons Collections 3.1版本 IDEA 需要一些java基础,反射.类对象.Class ...
- 解决debian (Friendly ARM 嵌入式板)的sudo等一部分命令无法TAB补全
TAB对于比较长的命令在使用时是十分方便的,最近就遇到TAB 键无法补全sudo后跟的命令的情况因此去网上取经.在一篇博客中找到解决问题的方法,觉得大牛们写的太精炼然后自己做如下总结方便自已以后解决类 ...
- Get your site working on Google Search Console , 在 Google Search Console中运行您的网站, Google Search Console
1 1 https://support.google.com/webmasters/topic/4564315? Search Console Help SEARCH CONSOLEHELP FORU ...
- cursor CSS属性定义鼠标指针悬浮在元素上时的外观。
1 1 cursor CSS属性定义鼠标指针悬浮在元素上时的外观. https://developer.mozilla.org/zh-CN/docs/Web/CSS/cursor 概述 cursor ...
- React useEffect in depth
React useEffect in depth useEffect class DogInfo extends React.Component { controller = null state = ...
- how to import a SQL file in MySQL command line
how to import a SQL file in MySQL command line execute .sql file, macOS $mysql> source \home\user ...
- js navigator.wakeLock 保持屏幕唤醒状态
let lock; btn.addEventListener("click", async () => { try { if (lock) { lock.release(); ...
- Linux 内核和 Windows 内核有什么区别?
Windows 和 Linux 可以说是我们比较常见的两款操作系统的. Windows 基本占领了电脑时代的市场,商业上取得了很大成就,但是它并不开源,所以要想接触源码得加入 Windows 的开发团 ...
- spring学习路径
1.https://zhuanlan.zhihu.com/p/72581899 spring 要点记录: (1)Web服务器的作用说穿了就是:将某个主机上的资源映射为一个URL供外界访问. (2)通过 ...