本文涵蓋 Azure 服務總線 Java 客戶端庫中各種認證類型的故障調查技術、並行運作、常見錯誤,以及緩解這些錯誤的步驟。
啟用和設定記錄
適用於 Java 的 Azure SDK 提供一致的記錄案例,可協助針對應用程式錯誤進行疑難解答,並協助加速解決。 記錄產生時會在應用程式到達終止狀態前捕捉其流程,以協助找出根本問題。 如需記錄的指引,請參閱 在 Azure SDK for Java 中設定記錄和 疑難解答概觀。
除了啟用記錄之外,將記錄層級設定為 VERBOSE 或 DEBUG 可以深入解析程式庫的狀態。 下列各節提供「log4j2」和「logback」的範例組態,以減少在啟用詳細記錄時出現過多的訊息。
設定 Log4J 2
使用下列步驟來設定Log4J 2:
- 在 [Log4j2 所需的相依性] 區段中,使用 記錄範例 pom.xml中的相依性,在 pom.xml 中新增相依性。
- 將 log4j2.xml 新增至 src/main/resources 資料夾。
設定記錄備份
使用下列步驟來設定 Logback:
- 在 [記錄備份所需的相依性] 區段中,使用 記錄範例 pom.xml中的相依性,在 pom.xml 中新增相依性。
- 將 logback.xml 新增至 src/main/resources 資料夾。
啟用AMQP傳輸記錄
如果啟用客戶端記錄不足以診斷您的問題,您可以啟用記錄至基礎 AMQP 連結庫中的檔案,Qpid Proton-J。 Qpid Proton-J 使用 java.util.logging。 您可以使用下一節中顯示的內容來建立組態檔來啟用記錄。 或者,您可以設定 proton.trace.level=ALL,並選擇您想用於 java.util.logging.Handler 實作的各種組態選項。 如需實作類別及其選項,請參閱 Java 8 SDK 檔中的 套件 java.util.logging。
若要追蹤AMQP傳輸框架,請設定 PN_TRACE_FRM=1 環境變數。
範例 logging.properties 檔案
下列組態檔會將 TRACE 層級輸出從 Proton-J 記錄到檔案 proton-trace.log:
handlers=java.util.logging.FileHandler
.level=OFF
proton.trace.level=ALL
java.util.logging.FileHandler.level=ALL
java.util.logging.FileHandler.pattern=proton-trace.log
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.SimpleFormatter.format=[%1$tF %1$tr] %3$s %4$s: %5$s %n
減少砍伐
減少記錄的其中一種方式是變更詳細資訊。 另一種方式是新增篩選,從記錄器名稱套件中排除記錄檔,例如 com.azure.messaging.servicebus 或 com.azure.core.amqp。 如需範例,請參閱 設定Log4J 2 和 設定logback 小節中的 XML 檔案。
當您提交 Bug 時,下列套件中類別的記錄訊息很有趣:
com.azure.core.amqp.implementationcom.azure.core.amqp.implementation.handler- 例外狀況是您可以忽略
onDelivery中的ReceiveLinkHandler訊息。
- 例外狀況是您可以忽略
com.azure.messaging.servicebus.implementation
ServiceBusProcessorClient 中的並發處理
ServiceBusProcessorClient 可讓應用程式設定應該同時對訊息處理程式進行多少次呼叫。 此設定可讓您平行處理多個訊息。 對於從非會話實體取用訊息的 ServiceBusProcessorClient,應用程式可以使用 maxConcurrentCalls API 來設定所需的並行性。 針對已啟用工作階段的實體,所需的並發性是 maxConcurrentSessions 倍 maxConcurrentCalls。
如果應用程式觀察到對訊息處理程式的並行呼叫次數少於所設定的並行數,這可能是因為執行緒池的大小設定不當。
ServiceBusProcessorClient 使用 Reactor 全域 boundedElastic 線程集區中的背景線程來調用訊息處理程式。 此集區中並行線程的數目上限受限於上限。 根據預設,此上限是可用 CPU 核心數的十倍。 若要讓 ServiceBusProcessorClient 有效地支援應用程式所需的並行處理(maxConcurrentCalls 或 maxConcurrentSessions 次 maxConcurrentCalls),您的 boundedElastic 集區上限值必須高於所需的並行程度。 您可以設定系統屬性 reactor.schedulers.defaultBoundedElasticSize來覆寫預設上限。
您應該依案例調整線程集區和CPU配置。 不過,當您超過資源池上限時,請將並行執行緒限制為每個 CPU 核心大約 20 到 30 個。 我們建議您將每個 ServiceBusProcessorClient 實例的所需並發量限制在大約 20-30。 分析並測量您的特定使用案例,然後相應調整並行方面的參數。 針對高負載案例,請考慮執行多個 ServiceBusProcessorClient 實例,其中每個實例都是從新的 ServiceBusClientBuilder 實例建置。 此外,請考慮將每個 ServiceBusProcessorClient 執行在單獨的主機上,例如使用容器或虛擬機(VM),以避免一台主機的停機時間影響到整體訊息處理。
請記住,在具有少數 CPU 核心的主機上設定集區上限的高值會產生負面影響。 CPU 資源不足或 CPU 數量較少但佇列中的線程過多時,可能出現一些跡象:經常逾時、鎖定遺失、死鎖或吞吐量降低。 如果您要在容器上執行 Java 應用程式,建議您使用兩個以上的 vCPU 核心。 在容器化環境中執行 Java 應用程式時,不建議選取小於 1 個 vCPU 核心的任何專案。 如需資源方面的深入建議,請參閱 將 Java 應用程式容器化。
連接共享瓶頸
從共用 ServiceBusClientBuilder 實例建立的所有客戶端都會共用與服務總線命名空間相同的連線。
使用共享連線可在一個連線上的用戶端之間進行多任務處理作業,但如果有許多用戶端,或用戶端一起產生高負載,共用也可能成為瓶頸。 每個連線都有與其相關聯的 I/O 線程。 共享連線時,用戶端會將工作放入此共用 I/O 線程的工作佇列中,而每個客戶端的進度取決於其工作在佇列中的及時完成。 I/O 執行緒會以序列方式處理在佇列中的工作。 也就是說,如果共用連線的 I/O 線程工作佇列最終有許多待處理工作,則徵兆類似於 CPU 性能低落的情況。 此條件會在上一節中說明並行存取 - 例如,用戶端停滯、逾時、遺失鎖定或復原路徑變慢。
服務總線 SDK 會針對連線 I/O 線程使用 reactor-executor-* 命名模式。 當應用程式遇到共享連線瓶頸時,它可能會反映在 I/O 線程的 CPU 使用量中。 此外,在堆積記憶體轉儲或即時記憶體中,物件 ReactorDispatcher$workQueue 是 I/O 執行緒的工作佇列。 瓶頸期間記憶體快照中的長工作佇列可能表示共用 I/O 執行緒已經因未處理的工作而負荷過重。
因此,如果應用程式對服務總線端點的負載在傳送接收的訊息數量或載荷大小方面相當高,您應該為所建置的每個用戶端使用個別的建構器實例。 例如,針對每個實體 - 佇列或主題 - 您可以建立新的 ServiceBusClientBuilder,並從中建置用戶端。 如果對特定實體的負載極高,您可能想要為該實體建立多個用戶端實例,或在多個主機中執行用戶端,例如容器或 VM,以進行負載平衡。
用戶端在使用應用程式閘道自訂端點時停止
自定義端點位址是指應用程式提供的 HTTPS 端點位址可解析至服務總線,或設定為將流量路由傳送至服務總線。 Azure 應用程式閘道可讓您輕鬆地建立 HTTPS 前端,以將流量轉送至服務總線。 您可以設定服務總線 SDK,讓應用程式使用應用程式閘道前端 IP 位址作為連線至服務總線的自定義端點。
應用程式閘道提供數個支援不同 TLS 通訊協定版本的安全策略。 有預先定義的原則強制執行 TLSv1.2 作為最低版本,也有舊原則,TLSv1.0 作為最低版本。 HTTPS 前端會套用 TLS 原則。
現在,服務總線 SDK 無法辨識應用程式閘道前端的特定遠端 TCP 終止,它會使用 TLSv1.0 作為最低版本。 例如,如果前端傳送 TCP FIN,ACK 封包會在更新其屬性時關閉連線,SDK 就無法偵測到它,因此它不會重新連線,而且客戶端無法再傳送或接收訊息。 只有在使用 TLSv1.0 作為最低版本時,才會發生這樣的停止。 若要緩和,請使用具有 TLSv1.2 或更高版本的安全策略作為應用程式閘道前端的最低版本。
所有 Azure 服務中 TLSv1.0 和 1.1 的支援已 宣佈 到 2024 年 10 月 31 日結束,因此強烈建議您將轉換至 TLSv1.2。
訊息或會話鎖定丟失
服務總線佇列或主題訂閱在資源層級設定鎖定時間。 當接收者用戶端從資源提取訊息時,服務總線訊息代理程式會將初始鎖定套用至訊息。 初始鎖定會持續至資源層級設定的鎖定持續時間。 如果訊息鎖定在到期之前未更新,則服務總線訊息代理程式會釋出訊息,使其可供其他接收者使用。 如果應用程式在鎖定到期後嘗試完成或放棄訊息,API 呼叫會失敗,錯誤 com.azure.messaging.servicebus.ServiceBusException: The lock supplied is invalid. Either the lock expired, or the message has already been removed from the queue。
服務總線用戶端支援執行背景鎖定更新工作,在訊息鎖定到期前每次都會持續更新。 根據預設,鎖定更新任務會執行 5 分鐘。 您可以使用 ServiceBusReceiverClientBuilder.maxAutoLockRenewDuration(Duration)來調整鎖定更新間隔。 如果您傳遞 Duration.ZERO 值,則會停用鎖更新任務。
下列清單描述一些可能導致鎖定遺失錯誤的使用模式或主機環境:
鎖定更新工作已停用,且應用程式的訊息處理時間超過資源層級設定的鎖定持續時間。
應用程式的訊息處理時間超過設定的鎖定更新工作持續時間。 請注意,如果未明確設定鎖定更新持續時間,則預設為 5 分鐘。
應用程式已開啟 Prefetch 功能,方法是使用
ServiceBusReceiverClientBuilder.prefetchCount(prefetch)將預先擷取值設定為正整數。 啟用預先擷取功能時,用戶端將從服務匯流排實體(佇列或主題)擷取與預先擷取數量相等的訊息,並將其儲存在記憶體中的預先擷取緩衝區中。 訊息會保留在預先擷取緩衝區中,直到它們被應用程式接收為止。 用戶端不會在訊息位於預先擷取緩衝區時延長鎖定時間。 如果應用程式處理需要這麼長的時間,訊息鎖定會在保留預先擷取緩衝區時過期,則應用程式可能會取得具有過期鎖定的訊息。 如需詳細資訊,請參閱 為什麼預先擷取不是默認選項?主機環境偶爾會有網路問題,例如暫時性網路失敗或中斷,導致鎖更新任務無法按時更新鎖。
主機環境缺少足夠的CPU,或間歇性地缺少CPU週期,導致鎖定更新工作無法準時執行。
主機時間不正確,例如時鐘異常,導致鎖定更新任務延遲,無法按時運行。
連線 I/O 線程負載過重,影響了其按時執行續約鎖定的網路呼叫的能力。 下列兩個案例可能會導致此問題:
- 應用程式執行了太多個共用相同連線的接收客戶端。 如需詳細資訊,請參閱 連線共用瓶頸 一節。
- 應用程式已將
ServiceBusReceiverClient.receiveMessages或ServiceBusProcessorClient設定為具有大型maxMessages或maxConcurrentCalls值。 如需詳細資訊,請參閱 ServiceBusProcessorClient 中的並行存取一節。
常見的應用程式模式,可能增加發生鎖定遺失錯誤的機會,包括排程長時間執行的鎖定續約工作,例如,持續時間跨越數小時的工作。 如先前所述,不在服務總線用戶端控制範圍內的各種因素可能會干擾成功的鎖定更新,因此應用程式設計應避免假設在長時間內能夠保證更新。 若要避免重新處理長時間執行的作業,請考慮將工作分成較小的區塊或實作等冪檢查點邏輯。
用戶端中的鎖定更新工作數目等於為 maxMessages 或 maxConcurrentCalls所設定的 ServiceBusProcessorClient 或 ServiceBusReceiverClient.receiveMessages 參數值。 進行多個網路呼叫的大量鎖定更新工作,也可能對服務總線命名空間節流產生負面影響。
如果主機資源不足,即使只有少數更新鎖的任務正在執行,鎖仍可能丟失。 如果您要在容器上執行 Java 應用程式,建議您使用兩個以上的 vCPU 核心。 在容器化環境中執行 Java 應用程式時,不建議選取小於 1 個 vCPU 核心的任何專案。 如需資源方面的深入建議,請參閱 將 Java 應用程式容器化。
關於鎖定的相同說明也適用於已啟用會話的服務總線佇列或主題訂閱。 當接收客戶端連線到資源中的會話時,代理會將初始鎖定套用至會話。 若要維護會話的鎖定,用戶端中的鎖定更新工作必須在會話鎖定到期之前持續更新鎖定。 針對已啟用連線的資源,底層分區有時會移動以在服務總線節點之間進行負載平衡,例如,當新增節點以共用負載時。 發生這種情況時,會話鎖定可能會遺失。 如果應用程式在工作階段鎖定遺失後嘗試完成或放棄訊息,API 呼叫會失敗,並發生錯誤 com.azure.messaging.servicebus.ServiceBusException: The session lock was lost. Request a new session receiver。
後續步驟
如果您在使用適用於 Java 的 Azure SDK 用戶端連結庫時,本文中的疑難解答指引無法解決問題,建議您