[Hangfire] 任務佇列重試策略

前面幾篇有提到 Job Enqueue Retry,對他的理解好像不夠,今天花一點時間研究研究,然後一整天就不見了...

開發環境

VS 2019

NET Framework 4.8

開啟一個空白的 Web Application 專案

安裝以下套件

Install-Package Microsoft.AspNet.WebApi.OwinSelfHost
Install-Package Microsoft.Owin.Diagnostics
Install-Package Microsoft.Owin.Host.SystemWeb
Install-Package Hangfire
Install-Package Hangfire.Console
Install-Package Hangfire.MemoryStorage
Install-Package Swagger-Net

 

除了,SwaggerConfig.Register 記得要改成用 OWIN 的 HttpConfiguration,其他的,設定可以參考 https://dotblogs.com.tw/yc421206/2019/12/25/desktop_app_async_task_via_hangfire

 

實作

替每一個 Job,對應一個 Swagger API,讓我可以模擬參數的傳入,然後觀察 Hangfire 的變化

Swagger UI 位置:/swagger

Hangfire Dashboard 位置:/hangfire

 

等待逾時

DisableConcurrentExecutionAttribute

timeoutInSeconds = 5,等五秒鐘還沒有執行就是 TimeOut,這個任務會被判定錯誤

[JobDisplayName("WaitTimeout - {0}")]
[DisableConcurrentExecution(5)]
public void WaitTimeout(string msg, PerformContext context, IJobCancellationToken cancelToken)
{
    if (msg == "1")
    {
        context.WriteLine($"執行中,目前時間:{DateTime.Now}");
        Thread.Sleep(60000);
    }
 
    context.WriteLine($"執行完畢:目前時間{DateTime.Now}");
}

 

演練步驟,先傳 "1" 給 WaitTimeout 方法,再傳 "2" 給WaitTimeout 方法,WaitTimeout - 2 的 Queue 已經被移到 Failded,WaitTimeout - 1 還在 Retry,如下圖:

 

執行逾時

MemoryStorageOptions

MemoryStorageOptions.FetchNextJobTimeout = new TimeSpan(0, 0, 5),等待 5 秒,還沒有完成,這個任務會被移到 Failed

GlobalConfiguration.Configuration
                   .UseMemoryStorage(new MemoryStorageOptions
                   {
                       FetchNextJobTimeout = new TimeSpan(0, 0, 5)
                   })
                   .UseConsole()
;

 

這裡為了演示所以使用 MemoryStorageOptions,線上環境你可以換成持久化的設定

 

重試時重新排隊

AutomaticRetryAttribute 

當例外發生時會觸發自動重試,比如網路不穩定,導致訪問不到遠端服務,這時可以使用 AutomaticRetryAttribute

重試時,工作會離開當前 Queue (佇列),重新排隊,不會堵塞整個 Queue,讓下一個工作可以運作。

兩種用法,一種是掛在 Job Method 上面,一種是註冊 Filter,GlobalJobFilters.Filters.Add(new AutomaticRetryAttribute { Attempts = 3 }),套用所有服務,兩者可以混用,當有衝突時以 Job Method 為主

  • Attempts:重試次數, 0 為不重試。
  • DelaysInSeconds:延遲時間,指定重試需要延遲多久才執行。
  • OnAttemptsExceeded:超過重試次數要歸類到 Delete、Fail 狀態
[AutomaticRetry(Attempts = 3, DelaysInSeconds = new[] {10, 20, 30},OnAttemptsExceeded = AttemptsExceededAction.Delete)]
public void Retry(string msg, PerformContext context, IJobCancellationToken cancelToken)
{
    //TODO....
}

 

重試超過次數後要歸類的狀態,如下圖:

代碼如下:

[JobDisplayName("AutoRetry - {0}")]
[AutomaticRetry(Attempts           = 3,
                DelaysInSeconds    = new[] {5, 10, 15},
                OnAttemptsExceeded = AttemptsExceededAction.Delete)]
[DisableConcurrentExecution(30)]
public void AutoRetry(string msg, PerformContext consoleLog, IJobCancellationToken cancelToken)
{
    if (msg == "1")
    {
        consoleLog.WriteLine($"執行中,目前時間:{DateTime.Now}");
        Thread.Sleep(5000);
        throw new Exception("噴錯了~");
    }

    consoleLog.WriteLine($"執行完畢,目前時間:{DateTime.Now}");
}

 

演練步驟,先傳 "1" 給AutoRetry 方法,再傳 "2",AutoRetry - 1 的 Queue 發生錯誤重試,重新排隊,AutoRetry - 2 已經成功,如下圖:

 

重試時不重新排隊

目前我找不到 Hangfire 的相關設定可以滿足這個情境,我想要借助 Polly 來幫我在原本的 Queue 重試,不進入排隊流程

[HttpGet]
[Route("PollyRetry")]
public void PollyRetry(string msg)
{
    this._client.Enqueue(() => this._job.PollyRetry(msg, null, null));
}

 

Job Client 呼叫 Polly,Retry 的工作交給 Polly,Polly 也有延遲重試

[JobDisplayName("PollyRetry - {0}")]
[AutomaticRetry(Attempts = 0, OnAttemptsExceeded = AttemptsExceededAction.Delete)]
[DisableConcurrentExecution(120)]
public void PollyRetry(string msg, PerformContext consoleLog, IJobCancellationToken cancelToken)
{
    var retryPolicy = Policy.Handle<Exception>()
                            .WaitAndRetry(new[]
                                          {
                                              TimeSpan.FromSeconds(5),
                                              TimeSpan.FromSeconds(10),
                                              TimeSpan.FromSeconds(15)
                                          },
                                          (exception, retryTime, context) =>
                                          {
                                              consoleLog.WriteLine($"延遲重試:{retryTime},目前時間:{DateTime.Now}");
                                          });
    retryPolicy.Execute(() => this.PollyAction(msg, consoleLog, cancelToken));
    consoleLog.WriteLine($"執行完畢,目前時間:{DateTime.Now}");
}
private void PollyAction(string msg, PerformContext consoleLog, IJobCancellationToken cancelToken)
{
    if (msg == "1")
    {
        consoleLog.WriteLine($"PollyAction 執行中,目前時間:{DateTime.Now}");
        Thread.Sleep(5000);
        throw new Exception("噴錯了~");
    }
 
    consoleLog.WriteLine($"執行完畢:目前時間{DateTime.Now}");
}

 

執行結果如下圖:

 

演練步驟,先傳 "1" 給 PollyRetry 方法,再傳 "2" 給 PollyRetry 方法,PollyRetry - 1 的 Queue 發生錯誤重試,不重新排隊,直到 PollyRetry - 1 重試完成才換 PollyRetry - 2 執行,截圖看不出效果,就不截了

 

範例專案

https://github.com/yaochangyu/sample.dotblog/tree/master/Hangfire/Lab.HangfireEnqueueRetry

 

延伸閱讀

[Hangfire] 如何對 Hangfire Job 撰寫測試
[Hangfire] Hangfire OWIN-Host 搭配 SQLite Storage
[Hangfire] ASP.NET Core Hangfire 排程管理 - Hangfire.Dashboard.Management
[Hangfire] 使用 Hangfire OWIN 建立非同步任務
 

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo