【Azure 應用服務】由 Azure Functions runtime is unreachable 的錯誤消息推導出 ASYNC(非同步)和 SYNC(同步)混用而引起ThreadPool耗盡問題

問題描述

在Azure Function Portal上顯示: Azure Functions runtime is unreachable,引起的結果是Function App目前不工作,但是此前一直都是正常工作的,且沒有對Azure Function做過任何的改動,那它是為什麼出現這樣的問題呢?

 

問題分析

Azure Functions runtime is unreachable 的錯誤是 」Azure Functions 運行時不可訪問」,此問題的最常見原因是函數應用失去了對其存儲帳戶的訪問許可權。首先我們根據官方文檔( 排查錯誤:「Azure Functions 運行時不可訪問」 )排查以下每一點:

  1. 存儲帳戶已被刪除

  2. 存儲帳戶應用程式設置已被刪除

  3. 存儲帳戶憑據無效

  4. 存儲帳戶不可訪問

  5. 每日執行配額已滿

  6. 應用受防火牆保護

 註:多個Function App之間應盡量避免共享Storage Account,在創建Function App的時候,需要關聯獨立的Storage Account. 

 

在排查外以上每一點後,如果依舊出現 「Azure Functions runtime is unreachable」的問題,那麼此時就需要分析當前所運行的Function是否由異常,是否由出現CPU 100%, Memory 100%,以及執行緒數等情況。

 

在這次的問題中,在Azure Function的日誌中,發現大量的如下兩種異常:

異常一

Microsoft.Azure.ServiceBus.MessageLockLostException : The lock supplied is invalid. Either the lock expired, or the message has already been removed from the queue.

Reference:ae0230de-86a9-xxxx-fdd, TrackingId:eaeef_xxxxxxxxxxxxxxxxxxxx0_B17, SystemTracker:api-11:Topic:inmessage, Timestamp:2021-06-02T06:21:22

   at async Microsoft.Azure.ServiceBus.Core.MessageReceiver.OnRenewLockAsync(String lockToken)

   …..

   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()

   at async Microsoft.Azure.ServiceBus.MessageReceivePump.RenewMessageLockTask(Message message,CancellationToken renewLockCancellationToken)

與Service Bus相關
異常二

Microsoft.Azure.Storage.StorageException : The lease ID specified did not match the lease ID for the blob.

   at async Microsoft.Azure.Storage.Core.Executor.Executor.ExecuteAsync[T](RESTCommand`1 cmd,IRetryPolicy policy,OperationContext operationContext,CancellationToken token)

   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()

….

   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()

   at async Microsoft.Azure.WebJobs.Host.Timers.TaskSeriesTimer.RunAsync(CancellationToken cancellationToken) at C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Timers\TaskSeriesTimer.cs : 147

與Storage Account相關

 

在異常資訊中,並沒有明確的指明當前Azure Funciton運行不正常及Azure Functions runtime is unreachable有關的資訊。但是Service Bus的 MessageLockLostException 異常值得重點分析,因為MessageLockLostException 異常意味著Function在消費Service Bus的消息的時候,由於處理消息的時間過長,超過了Meesage Lock(鎖定,默認30秒),消息無法消費使得回滾會Service Bus的隊列中,然後進行下一輪的消費,如此往返。最終表現就是Azure Funciton運行不正常。為了驗證這一步,需要收集Function的DUMP文件。PS: 如何獲取Windows下Function的DUMP,可參考博文:快速獲取DUMP文件(App Service for Windows(.NET/.NET Core)//www.cnblogs.com/lulight/p/13574331.html

 

檢查記憶體DUMP,確認大量執行緒(基本上是所有執行緒)都在等待執行 調用HttpClient上傳這一步。而執行緒池此時已耗盡,無法創建新的執行緒繼續完成Http請求。導致Azure Function運行不正常,無法消費Service Bus中的消息,也引起了 Microsoft.Azure.ServiceBus.MessageLockLostException 異常

(收集DUMP後可使用Visual Studio 2019查看DUMP中的Stack資訊)

 

綜上,根據DUMP文件中的發現,找到了問題原因:應用程式程式碼在非同步程式碼調用中使用了Wait方法,導致在Service Bus消息的高峰期間,進程池耗盡,無法正常運行並處理消息,也引起 Azure Functions runtime is unreachable,在最佳的操作要求中,有明確的要求:

在 C# 中,請始終避免引用 Result 屬性或在 Task 實例上調用 Wait 方法。 這種方法會導致執行緒耗盡。

 

解決辦法

一:從執行緒池耗盡的方面入手,增加ThreadPool。在FunctionApp上更改平台配置,改為按照64位模式運行。由於32位的程式(x86)的Function最大執行緒數位125,而64位修改後,ThreadPool最大值提升到32767。(如果應用打包時target的x86,則需要重新打包應用target x64)。

 

二:修改程式碼。移除在async方法中所調用的wait()方法,修改位await。PS: 使用非同步程式碼時,裡面應該一路使用async和await。

 

 

 

參考資料

排查錯誤:「Azure Functions 運行時不可訪問」: //docs.azure.cn/zh-cn/azure-functions/functions-recover-storage-account

提高 Azure Functions 的性能和可靠性的最佳做法: //docs.azure.cn/zh-cn/azure-functions/functions-best-practices#avoid-sharing-storage-accounts

 

【完】