本文將探討故障調查技巧、並發性,以及Azure 服務匯流排 Java客戶端庫中的常見錯誤。 請利用本指南找出根本原因,並套用緩解措施,以更快解決 Azure 服務匯流排 問題。
啟用和設定記錄
適用於 Java 的 Azure SDK 提供一致的記錄案例,可協助針對應用程式錯誤進行疑難解答,並協助加速解決。 你產生的日誌記錄應用程式在進入終端狀態前的流程,幫助找出根本問題。 如需記錄的指引,請參閱 在 Azure SDK for Java 中設定記錄和 疑難解答概觀。
除了啟用記錄之外,將記錄層級設定為 VERBOSE 或 DEBUG 可以深入解析程式庫的狀態。 下列各節提供「log4j2」和「logback」的範例組態,以減少在啟用詳細記錄時出現過多的訊息。
設定 Log4J 2
使用下列步驟來設定Log4J 2:
- 使用「Log4j2 所需的相依性」一節中 logging sample pom.xml 的相依性,將其加入您的 pom.xml。
- 將 log4j2.xml 新增至 src/main/resources 資料夾。
設定記錄備份
使用下列步驟來設定 Logback:
- 在你的 pom.xml 中,加入 logging sample pom.xml 中「Logback 所需的相依性」一節的相依性。
- 將 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 協定版本。 有預先定義的政策強制執行 TLS 1.2 作為最低版本,也有較舊的政策以 TLS 1.0 作為最低版本。 你要套用 TLS 政策到 HTTPS 前端。
目前,服務匯流排 SDK 無法辨識應用閘道前端的某些遠端 TCP 終止,該介面使用 TLS 1.0 作為最低版本。 例如,如果前端在更新屬性時發送 TCP FIN 和 ACK 封包來關閉連線,SDK 就無法偵測這些封包。 所以它無法重新連線,客戶端也無法再傳送或接收訊息。 這種暫停只會在使用 TLS 1.0 作為最低版本時發生。 為了減輕此問題,請使用將 TLS 1.2 或更高版本設定為應用閘道前端最低版本的安全政策。
TLS 1.0 和 1.1 在所有 Azure 服務上的支援已經宣布將於 2024 年 10 月 31 日前結束,因此請轉用 TLS 1.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分鐘。
應用程式會使用
ServiceBusReceiverClientBuilder.prefetchCount(prefetch)將 prefetch 值設為正整數,以啟用 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。
後續步驟
如果本文的故障排除指引無法幫助你解決使用Azure SDK Java客戶端函式庫的問題,請在Java GitHub儲存庫的Azure SDK中提出問題。