只有累積,沒有奇蹟

2019年6月8日 星期六

[NETCore] 使用 Polly 實現重試 (Retry) 策略

介紹
在開發時常常都會遇到串接其他 Server 或是第三方服務 API 的需求,當對方伺服器或是第三方服務發生問題時,或許可以在 Log 中看到回應的 HTTP 狀態碼是 404 (Not Found)、503(Service Unavailable)、504(Gateway Timeout)、500(Too Many Requests)...等當下無法正常執行的錯誤訊息,可能發生原因是因為對方 Server 不穩定,或是正在部署新版應用程式,造成在發送 Request 請求當下沒辦法回應請求內容,在過去可能會使用 while 加上 thread.sleep 達到,代碼可能長得像這樣
private static int retryCount = 3;
private static void doRetry(int second, Action action)
{
    while (true)
    {
        try
        {
            action(); // do something
            break; 
        }
        catch
        {
            if (--retryCount == 0)
                throw;
            Thread.Sleep(1000 * second);
        }
    }
}
這僅是最簡單版的重送機制還不包含 try cacht 處理,如果重送機制更複雜代碼會更多,或者希望用更好的方式來處理 故障 這件事,以上問題可以透過 Polly 類別庫來達到 故障處理 這件事。
Polly 是 .NET Foundation 認可的類別庫之一,在官方 GitHub 對於 Polly 介紹如下
Polly is a .NET resilience and transient-fault-handling library that allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.
Polly 是一套 .NET 處理瞬間故障的函式庫,提供開發人員用 Fluent API 方式處理服務瞬間故障的機制,並提供重試(Retry)、斷路(Circuit-breaker)、超時(Timeout)、隔離(Bulkhead Isolation)、緩存(Cache)、回退(Fallback) 等機制。此篇就針對 Polly 中最常用的重試 (Retry) 策略做簡單分享,若有問題歡迎一起討論。

安裝 Polly
為了方便大家可以更容易了解,會透過建立一個名為 PollyConsole 的 Console 專案作為範例,在建立完畢 Console 專案之後,下一步是在 Nuget 下載 Polly 套件,安裝方式也是與一般套件相同,開啟 nuget 後輸入 Polly 按下載,接著一直按下一步直到安裝完成
身邊有些朋友是不喜歡使用 GUI,也可以透過 Nuget Package Console 輸入下列指令
Install-Package Polly -Version 7.1.0
安裝完畢之後到專案檔底下確認是否有安裝成功
<ItemGroup>
  <PackageReference Include="Polly" Version="7.1.0" />
</ItemGroup>

改寫 while  
在文章的一開始就有提到可能發生的情境,這些故障情境可能是短暫發生的,對方服務(API)會在短時間修復,就可以使用 Retry 機制來重試,透過下列範例與說明,希望讓大家更容易了解

Mock Service
為了 Demo 方便這裡使用 mock REST API 來協助我們建立假的第三方服務,在這網站中可以自訂 Response 的 HTTP Status Code、Content Type 與 Body 內容,客製自己想要的 Response 內容後可以產生屬於自己的測試 REST API,舉例來說,這裡就建立  HTTP Status = 500  的流程機制,Body 為  This is 500 Erro ,按下 Generate HTTP Response 按鈕後得到 http://www.mocky.io/v2/5cfb4d9b3000006e080a8b0a,作為稍後測試使用。
Polly Retry
安裝完 Polly 以及建立好 mock API 之後,我們可以試著用 Polly 來改寫自己定義 while 的 code,使用 Polly API 改寫後代碼如下,當呼叫第三方服務  doMockHTTPrequest  發生異常 (HTTP StatusCode 不為 OK) 時,將會進行重次三次的動作,並將過程用 console.write 輸出在畫面上
static void Main(string[] args)
{
    Policy
   // 故障處理 : 要 handle 什麼樣的異常
   .Handle<HttpRequestException>()
   .OrResult<HttpResponseMessage>(result => result.StatusCode != HttpStatusCode.OK)
   // 重試策略 : 異常發生時要進行的重試次數及重試機制
   .Retry(3, onRetry: (exception, retryCount) =>
   {
       Console.WriteLine($"[App|Polly] : 呼叫 API 異常, 進行第 {retryCount} 次重試, Error :{exception.Result.StatusCode}");
   })
   // 要執行的任務
   .Execute(doMockHTTPRequest);

    Console.WriteLine("結束退出");
    Console.ReadKey();
}

static HttpResponseMessage doMockHTTPRequest()
{
    Console.WriteLine($"[App] {DateTime.Now.ToString(CultureInfo.InvariantCulture)}: 開始發送 Request");

    HttpResponseMessage result;
    using(HttpClient client = new HttpClient())
    {
        result = client.GetAsync("http://www.mocky.io/v2/5cfb4d9b3000006e080a8b0a").Result;
    }

    return result;
}
由於我們建立的 mock API 回傳定義 HTTP Status Code 為 500,因此發送 Request 請求會失敗,代碼執行後結果如下
透過 Polly 來實作重試機制成功,整體感覺使用 Fluent 方式來撰寫代碼也變得更為清晰,增加其可讀性,成功改寫完下一步就來介紹 Polly 使用方式。

注意 : 使用 async / await 會有 Thread lock 問題,造成原因及解法可以參考 Async/Await - Best Practices in Asynchronous Programming

Retry 重試策略 
Polly 主要結構可以分為

  • Handle<T> : 故障時的處理機制,或是指定要處理什麼樣的異常
  • Retry : 重試機制,定義發生故障時要重試的次數或指定工作
  • Execute : 要執行的任務

  • Handle
    故障時的處理機制,或是指定要處理什麼樣的異常,可以根據需求來定義所要故障處理的 exception,如果希望能夠同時 handle 多個例外可以使用  or<T>  來實現
    // Single exception type
    Policy.Handle<HttpRequestException>()
    
    // Multiple exception types
    Policy
      .Handle<HttpRequestException>()
      .Or<OperationCanceledException>()
    在 Polly 中也提供 API 依據回傳的內容 (return results) 來進行故障處理
    // 回傳內容為 404 
    Policy
      .HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.NotFound)
    
    // 回傳內容為 500 & 502
    Policy
      .HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.InternalServerError)
      .OrResult(r => r.StatusCode == HttpStatusCode.BadGateway)
    
    在代碼中定義了要處理什麼樣的故障,下一步就是要針對故障做怎麼樣處理策略,Polly 在重試策略中提供  Retry  與   Wait and Retry  兩種重試 API,分別如下

    Retry
    提供指定重試次數、每次重試時處理等相關 API,廢話不多說直接上 Code
    // 重次 : 預設重試一次
    Policy
      .Handle<SomeExceptionType>()
      .Retry()
    
    // 重次多次
    Policy
      .Handle<SomeExceptionType>()
      .Retry(7)
    
    // 在每次重試時做處理,
    Policy
        .Handle<SomeExceptionType>()
        .Retry(3, onRetry: (exception, retryCount) =>
        {
            // do something 
        });
    如果回傳內容很重要,Polly 也提供持續重試的 API 互相傷害的概念(誤)
    Policy
      .Handle<SomeExceptionType>()
      .RetryForever(onRetry: (exception, context) =>
      {
            // do something       
      });
    

    Wait and Retry
    如果想要自己定義重試的頻率與時間,而非固定的1秒呼叫一次的話,可以使用 wait and retry 來自定義呼叫的時間區間,讓重試機制更有彈性 
    Policy
      .Handle<SomeExceptionType>()
      .WaitAndRetry(new[]
      {
        TimeSpan.FromSeconds(1),
        TimeSpan.FromSeconds(2),
        TimeSpan.FromSeconds(3)
      });
    輸出如下
    在一些情境底中如果短時間的重試可能還是無法正常,也可以考慮使用函數來計算重試的時間,下列範例重試時間為 2、4、8、16、32 秒,用 2 的倍數方式來做為重試時間 
    Policy
      .Handle<SomeExceptionType>()
      .WaitAndRetry(5, retryAttempt => 
     TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)) 
      );

    Retry After
    有些系統或是應用程式有限流機制,例如之前在 ASP.NET Core 介紹過的 ASP.NET Core 限流框架 AspNetCoreRateLimit,可以設定某時間內重試次數達到定義的水位時,系統將會阻擋 Request 無法使用,透過 Chrome 開發者工具可以看到回傳的 HTTP StatusCode 如下
    429 Too Many Requests
    此時系統將會回應 http status code 為 429,並在 Header 中定義 Retry After 訊息
    也就是說是無法使用 Retry 重送,只能透過 Wait and Retry 來達到此需求,在官網中也有提供 Retry after 的範例說明 : 傳送門。 

    Polly Retry flow
    上面介紹了兩種 Retry 的 API 與使用方式,在官網中也有很貼心的提供 Retry 的流程機制

    感想
    原本想要介紹 Polly 的完整功能,但由於時間以及避免這篇太長因此決定先介紹在 Polly 中最常用到的 重試策略,在官方的 GitHub 也提供了相當詳細的 API 說明與 sample code 範例可以使用,這邊僅是擷取自己覺得重要與常用的部分,若想了解更多非常推薦到官網了解,之後若有更多時間會在介紹 Polly 其他功能與範例 (希望有空 XDD),謝謝

    參考
    HTTP Status Code
    ASP.NET Core 微服务初探[2]:熔断降级之Polly
    NET 开源项目 Polly 介绍
    App-vNext/Polly



    2019年6月5日 星期三

    [VisualStudio] Visual studio 2019 中的 Preview Feature

    前言 
    在 ASP.NET Core 3.0 Preview 體驗 文章中有提到,Visual Studio 預設會以穩定的 SDK 版本為主,也就是想使用 Preview 需要透過設定才能看的到,前幾天在 FB 社團看到有人詢問下載完 ASP.NET Core 3.0 後,在 Visual Studio 2019 開新專案卻無法選到 3.0 專案範本,這篇文章簡單記錄新版 VS 2019 如何設定使用 Preview SDK 的方式與 Preview Feature 自己常設定功能的介紹。

    Preview Feature 
    搜尋後發現原來是為了方便設定,將 Preview Feature 整理為一個 Page 頁面好進行設定,此設定頁面也有提到升級後有可能這些設定不一定還會存在,在確定之前可以先在這 Tab 進行設定
    在 Preview Feature 我自己有用到的設定有兩種,分別是

    Use ASP.NET Preview SDK
    原先設定方式在 Tool > Projects and Solutions > .NET Core > 勾選 Use previews of the NET Core SDK,當時使用的是 Visual Studio 2019 版本為16.04,但將 Visual Studio 2019 升級至最新版 16.1 版本,發現上述同樣設定的路徑已消失,確認後發現原來是新版將此功能換位置,新的位置如下
    Step 1 : 選擇 Tools 
    Step 2 : 搜尋 Preview  > 右邊會出現 Use previews of the NET Core SDK
    Step 3 : 打勾按下儲存設定
    在完成設定後,就可以看到新增專案時可以看到 ASP.NET Core 3.0 範本

    Compact Menu & Search Bar
    可以設定是否要開啟 Compact Menu 與 Search Bar
    開啟與關閉的差異
    以上是簡單說明 Visual Studio 2019 中 16.1 版新增的功能 Preview Feature,也希望功能可以確定下來,這樣下次在找設定位置時才不會又沒有頭緒或是消失,Happy Coding :)

    參考
    Microsoft Visual Studio 2019 now available for download

    2019年6月4日 星期二

    [NET] 檢查字串是否為 Json 格式

    前言
    在開發時很常使用到 Json 格式作為設定檔,舉例來說在 ASP.NET Core 設定檔預設也是使用 Json 格式,系統開發中也會將一些常用的設定放在 Json 方便調整,在 ASP.NET 讀取 Json 最常用到的是 Json.Net 套件,使用上可以很方便地將字串轉為定義的類別內容,這篇就來分享怎麼用 Json.net 來判斷要轉換的文字字串是否為 Json 格式,若有問題歡迎留言一起討論。

    代碼
    首先先下載 Json.Net 套件,使用前需要透過 nuget 下載 Json.net 
    Install-Package Newtonsoft.Json -Version 12.0.2
    下列代碼主要是先做檢查輸入字串是否為空與字串前後是否有 {} 與 [] 關鍵字,如果沒有直接不進行 parse 的動作,接著使用 JToken.Parse 加上 try catch 判斷字串為 Json 格式,廢話不多說,直接看代碼
    private static bool IsJsonFormat(string value)
    {
        if (string.IsNullOrWhiteSpace(value))
            return false;
    
        if ((value.StartsWith("{") && value.EndsWith("}")) || 
            (value.StartsWith("[") && value.EndsWith("]"))) 
        {
            try
            {
                var obj = JToken.Parse(value);
                return true;
            }
            catch (JsonReaderException)
            {
                return false;
            }
        }
    
        return false;
    }
    
    使用方式相當容易
    string jsonText = "{\"statusCode\":400,\"error\":\"Error\",\"errorCode\":\"error_code\",\"message\":\"The Message\"}";
    var isJson = IsJsonFormat(jsonText);
    
    Console.WriteLine($"Your input Json Text : {jsonText}");
    Console.WriteLine($"IsJsonFormat : {isJson}");
    Console.Read();
    輸出如下
    驗證無誤,準備收工 (?
    如果希望更方便使用的話,可以使用擴充方法增加方便性

    擴充方法
    public static class ExtensionMethods
    {
        public static bool IsJsonFormat(this string value)
        {
            if (string.IsNullOrWhiteSpace(value))
                return false;
    
            if ((value.StartsWith("{") && value.EndsWith("}")) ||
                (value.StartsWith("[") && value.EndsWith("]")))
            {
                try
                {
                    var obj = JToken.Parse(value);
                    return true;
                }
                catch (JsonReaderException)
                {
                    return false;
                }
            }
    
            return false;
        }
    }
    使用方式如下
    string jsonText = "{\"statusCode\":400,\"error\":\"Error\",\"errorCode\":\"error_code\",\"message\":\"The Message\"}";
    jsonText.IsJsonFormat();
    // result : true

    參考
    JToken Class

    2019年6月3日 星期一

    [C#] 在 C# 使用 HMAC - SHA256 加密

    前言
    最近工作上跟第三方服務串接時用到 HMAC - SHA256 加密方式,過去大多是使用 MD5 或是 SHA-256 加密但 HMAC 沒接觸過小研究了一番,HMAC 全名是 Hash-based message authentication code,是一種金鑰式雜湊演算法,可以結合加密金鑰 (key) 進行加密最後輸出 64 字元的內容,對於演算法內容想了解更多可以參考 wiki : 傳送門,這裡就紀錄在 C# 中如何實現 HMAC-SHA256 的加密,若有問題歡迎留言一起討論。

    代碼
    過去在 C# 實作演算法時都是使用 System.Security.Cryptography 命名空間,HMACSHA256 也不例外可以在此命名空間找到,並提供多載的建構子可以讓開發者選擇金鑰是隨機或是指定方式建立 Instance,不指定金鑰建構式
    public HMACSHA256 ();
    指定金鑰建構式,傳入 byte 作為參數
    public HMACSHA256 (byte[] key);
    代碼如下
    private static string HMACSHA256(string message, string key)
    {
        var encoding = new System.Text.UTF8Encoding();
        byte[] keyByte = encoding.GetBytes(key);
        byte[] messageBytes = encoding.GetBytes(message);
        using (var hmacSHA256 = new HMACSHA256(keyByte))
        {
            byte[] hashMessage = hmacSHA256.ComputeHash(messageBytes);
            return BitConverter.ToString(hashMessage).Replace("-", "").ToLower();
        }
    }
    

    驗證結果
    來做個簡單的測試,message 帶入"TEST",KEY 帶 "KEY" 觀察輸出為何
    HMACSHA256("TEST", "KEY");
    // resulg : 615dac1c53c9396d8f69a419a0b2d9393a0461d7ad5f7f3d9beb57264129ef12
    驗證正確性可以透過 HMAC Generator 網站,加密方式選擇 HMACSHA256 進行確認
    驗證無誤,準備收工 (?
    如果希望更方便使用的話,可以使用擴充方法增加方便性

    擴充方法
    新增 HMACSHA256.cs 並針對 string 加入 HMACSHA256 擴充方法
    public static class ExtensionMethods
    {
        public static string HMACSHA256(this string message, string key)
        {
            var encoding = new System.Text.UTF8Encoding();
            byte[] keyByte = encoding.GetBytes(key);
            byte[] messageBytes = encoding.GetBytes(message);
            using (var hmacSHA256 = new HMACSHA256(keyByte))
            {
                byte[] hashMessage = hmacSHA256.ComputeHash(messageBytes);
                return BitConverter.ToString(hashMessage).Replace("-", "").ToLower();
            }
        }
    }
    使用方式如下
    static void Main(string[] args)
    {
        var result = "TEST".HMACSHA256("KEY");
        Console.WriteLine(result);
    }

    以上是透過簡單代碼達到實作 HMACSHA256 的代碼,如果想了解更細節的部分推薦參考 Stackoverflow 中 Calculating HMACSHA256 using c# to match payment provider example 的回答內容,可以讓你更加了解,Happy Coding :)

    參考

    Copyright © m@rcus 學習筆記 | Powered by Blogger

    Design by Anders Noren | Blogger Theme by NewBloggerThemes.com