問題描述
查閱了Azure的官方文檔( 將事件發送到特定分區: https://docs.azure.cn/zh-cn/event-hubs/event-hubs-availability-and-consistency?tabs=java#send-events-to-a-specific-partition),在工程里引用組件“azure-spring-cloud-stream-binder-eventhubs”來連接EventHub發送和消費消息事件。在發送端一個For循環中發送帶順序號的消息,編號從0開始,並且在消息的header中指定了 "Partition Key",相同PartitionKey的消息會被發送到相同的Partition,來保證這些消息的順序。
但是在消費端的工程中消費這些消息時,看到打印到日志中的結果並不是從0遞增的。所以想知道是發送端在發送時就已經亂序發送了?還是消息到達EventHub后亂序保存了?還是消費端的消費方式的問題,導致打印出的結果是亂序的?
下面是發送端的代碼:
public void testPushMessages(int mcount, String partitionKey) { String message = "Message "; for (int i=0; i <mcount; i++) { source.output().send(MessageBuilder.withPayload(partitionKey + mcount + i).setHeaderIfAbsent(AzureHeaders.PARTITION_KEY,partitionKey).build()); } }
下面是消費端代碼:
@StreamListener(Sink.INPUT) public void onEvent(String message, @Header(AzureHeaders.CHECKPOINTER) Checkpointer checkpointer, @Header(AzureHeaders.RAW_PARTITION_ID) String rawPartitionId, @Header(AzureHeaders.PARTITION_KEY) String partitionKey) { checkpointer.success() .doOnSuccess(s -> log.info("Message '{}' successfully check pointed.rawPartitionId={},partitionKey={}", message, rawPartitionId, partitionKey)) .doOnError(s -> log.error("Checkpoint message got exception.")) .subscribe();
下面是打印的日志
......,"data":"Message 'testKey4testMessage1' successfully check pointed.rawPartitionId=1,partition<*****>","xcptn":""} ......,"data":"Message 'testKey5testMessage29' successfully check pointed.rawPartitionId=1,partition<*****>","xcptn":""} ......,"data":"Message 'testKey5testMessage27' successfully check pointed.rawPartitionId=1,partition<*****>","xcptn":""} ......,"data":"Message 'testKey5testMessage26' successfully check pointed.rawPartitionId=1,partition<*****>","xcptn":""} ......,"data":"Message 'testKey5testMessage25' successfully check pointed.rawPartitionId=1,partition<*****>","xcptn":""} ......,"data":"Message 'testKey5testMessage28' successfully check pointed.rawPartitionId=1,partition<*****>","xcptn":""} ......,"data":"Message 'testKey5testMessage14' successfully check pointed.rawPartitionId=1,partition<*****>","xcptn":""} ......,"data":"Message 'testKey5testMessage13' successfully check pointed.rawPartitionId=1,partition<*****>","xcptn":""} ......,"data":"Message 'testKey5testMessage15' successfully check pointed.rawPartitionId=1,partition<*****>","xcptn":""} ......,"data":"Message 'testKey5testMessage5' successfully check pointed.rawPartitionId=1,partition<*****>","xcptn":""} ......,"data":"Message 'testKey5testMessage7' successfully check pointed.rawPartitionId=1,partition<*****>","xcptn":""} ......,"data":"Message 'testKey5testMessage20' successfully check pointed.rawPartitionId=1,partition<*****>","xcptn":""} ......,"data":"Message 'testKey5testMessage19' successfully check pointed.rawPartitionId=1,partition<*****>","xcptn":""} ......,"data":"Message 'testKey5testMessage18' successfully check pointed.rawPartitionId=1,partition<*****>","xcptn":""} ......,"data":"Message 'testKey5testMessage0' successfully check pointed.rawPartitionId=1,partition<*****>","xcptn":""} ......,"data":"Message 'testKey5testMessage9' successfully check pointed.rawPartitionId=1,partition<*****>","xcptn":""} ......,"data":"Message 'testKey5testMessage12' successfully check pointed.rawPartitionId=1,partition<*****>","xcptn":""} ......,"data":"Message 'testKey5testMessage8' successfully check pointed.rawPartitionId=1,partition<*****>","xcptn":""}
從日志中可以看到,消息確實都被發送到了同一個分區(rawPartitionId=1),但是從消息體的序號上看,是亂序的
問題分析
這個是和這個配置相關的fixedDelay,指定默認輪詢器的固定延遲,是一個周期性觸發器,之前代碼會根據這個輪詢器進行發送和接受消息的。使用Send發送的方法,現在最新的SDK 不使用這個方法,所以需要使用新的sdk 發送數據測試一下。
新sdk 參考文檔您可以參考一下:https://github.com/Azure/azure-sdk-for-java/tree/master/sdk/spring/azure-spring-boot-samples/azure-spring-cloud-sample-eventhubs-binder
SDK版本為
<dependency> <groupId>com.azure.spring</groupId> <artifactId>azure-spring-cloud-stream-binder-eventhubs</artifactId> <version>2.4.0</version> </dependency>
在參考官網的示例后,使用Supplier方法發送消息,代替Send。經過多次測試,指定partitionkey 之后,發送消息是順序發送的,消費的時候也是按照順序消費的,下面是測試的代碼和結果
發送端的代碼
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.azure.spring.sample.eventhubs.binder; import com.azure.spring.integration.core.EventHubHeaders; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import org.springframework.messaging.Message; import org.springframework.messaging.support.MessageBuilder; import java.util.function.Supplier; import static com.azure.spring.integration.core.EventHubHeaders.SEQUENCE_NUMBER; @Configuration public class EventProducerConfiguration { private static final Logger LOGGER = LoggerFactory.getLogger(EventProducerConfiguration.class); private int i = 0; @Bean public Supplier<Message<String>> supply() { return () -> { //LOGGER.info("Sending message, sequence " + i); String partitionKey="info"; LOGGER.info("Send message " + MessageBuilder.withPayload("hello world, "+i).setHeaderIfAbsent(EventHubHeaders.PARTITION_KEY, partitionKey).build()); return MessageBuilder.withPayload("hello world, "+ i++). setHeaderIfAbsent(EventHubHeaders.PARTITION_KEY, partitionKey).build(); }; } }
接收端的代碼
package com.ywt.demoEventhub; import com.azure.spring.integration.core.EventHubHeaders; import com.azure.spring.integration.core.api.reactor.Checkpointer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.integration.annotation.ServiceActivator; import org.springframework.messaging.Message; import java.util.function.Consumer; import static com.azure.spring.integration.core.AzureHeaders.CHECKPOINTER; @Configuration public class EventConsume { private static final Logger LOGGER = LoggerFactory.getLogger(EventConsume.class); @Bean public Consumer<Message<String>> consume() { return message -> { Checkpointer checkpointer = (Checkpointer) message.getHeaders().get(CHECKPOINTER); LOGGER.info("New message received: '{}', partition key: {}, sequence number: {}, offset: {}, enqueued time: {}", message.getPayload(), message.getHeaders().get(EventHubHeaders.PARTITION_KEY), message.getHeaders().get(EventHubHeaders.SEQUENCE_NUMBER), message.getHeaders().get(EventHubHeaders.OFFSET), message.getHeaders().get(EventHubHeaders.ENQUEUED_TIME) ); checkpointer.success() .doOnSuccess(success -> LOGGER.info("Message '{}' successfully checkpointed number '{}' ", message.getPayload(), message.getHeaders().get(EventHubHeaders.CHECKPOINTER))) .doOnError(error -> LOGGER.error("Exception found", error)) .subscribe(); }; } }
發送消息的日志
消費消息的日志
參考資料
Azure Spring Cloud Stream Binder for Event Hub Code Sample shared library for Java:https://github.com/Azure/azure-sdk-for-java/tree/master/sdk/spring/azure-spring-boot-samples/azure-spring-cloud-sample-eventhubs-binder
How to create a Spring Cloud Stream Binder application with Azure Event Hubs - Add sample code to implement basic event hub functionality : https://docs.microsoft.com/en-us/azure/developer/java/spring-framework/configure-spring-cloud-stream-binder-java-app-azure-event-hub#add-sample-code-to-implement-basic-event-hub-functionality
[END]