云原生系統(tǒng)之彈性模式

大綱
1.云原生系統(tǒng)的彈性模式resiliency pattern 1.1 服務(wù)故障的雪崩效應(yīng) 1.2 回應(yīng)之前云原生--彈性請求的疑問?
2. 彈性模式:作用在下游請求消息上3. 短期中斷的響應(yīng)碼4. Polly經(jīng)典策略5. Golang 斷路器模式
德國哲學(xué)家尼采說過:那些殺不死我的東西,只會讓我更加強(qiáng)大。
hi,好久不見,馬甲哥之前意譯并連載了《Microsoft Cloud-native toc.pdf》部分內(nèi)容
?什么是云原生?現(xiàn)代云原生設(shè)計理念?.NET微服務(wù)?談到云原生,繞不開容器化?支撐性服務(wù) & 自動化能力
01
云原生系統(tǒng)的彈性模式
結(jié)合最近的工作經(jīng)驗,本次繼續(xù)聊一聊云原生的彈性模式 (resilience not scale), 這也是回應(yīng)《現(xiàn)代云原生設(shè)計理念》中
“在分布式體系結(jié)構(gòu)中,當(dāng)服務(wù)B不響應(yīng)來自服務(wù)A的網(wǎng)絡(luò)請求會發(fā)生什么?
當(dāng)服務(wù)C暫時不可用,其他調(diào)用C的服務(wù)被阻塞時該怎么辦?”

由于網(wǎng)絡(luò)原因或自身原因,B、C服務(wù)不能及時響應(yīng),服務(wù)A發(fā)起的請求將被阻塞(直到B、C響應(yīng)),此時若大量請求涌入,服務(wù)A的線程資源將被消耗殆盡,服務(wù)A的處理性能受到極大影響,進(jìn)而影響下游依賴的external clients/backend srv。
故障會傳播,造成連鎖反應(yīng),對整個分布式結(jié)構(gòu)造成災(zāi)難性后果,這就是服務(wù)故障的“雪崩效應(yīng)”。
當(dāng)B、C服務(wù)不可用,下游客戶端/backend srv能做什么?
客觀上請求不通,執(zhí)行預(yù)定的彈性策略:重試/斷路?
02
彈性模式:作用在下游的請求消息上
彈性模式是系統(tǒng)面對故障仍然保持工作狀態(tài)的能力,它不是為了避免故障,而是接受故障并嘗試去面對它。
Polly是一個全面的.NET彈性和瞬時錯誤處理庫,允許開發(fā)者以流暢和線程安全的方式表達(dá)彈性策略。
| 策略 | 場景 | 行為 |
| Retry | 抖動/瞬時錯誤,短時間內(nèi)自動恢復(fù) | 在特定操作上配置重試行為 |
| Circuit Breaker | 在短期內(nèi)不大可能恢復(fù) | 當(dāng)故障超過閾值,在一段時間內(nèi)快速失敗 |
| Timeout | 限制調(diào)用者等待響應(yīng)的時間 | |
| Bulkhead | 將操作限制在固定的資源池,防止故障傳播 | |
| Cache | 自動存儲響應(yīng) | |
| Bulkhead | 一旦失敗,定義結(jié)構(gòu)化的行為 |
一般將彈性策略作用到各種請求消息上(外部客戶端請求或后端服務(wù)請求)。
其目的是補償暫時不可用的服務(wù)請求。

03
短期中斷的響應(yīng)碼
| Http Status code | 原因 |
| 404 | not found |
| 408 | request timeout |
| 429 | two many requests |
| 502 | bad gateway |
| 503 | service unavailable |
| 504 | gateway timeout |
正確規(guī)范的響應(yīng)碼能幫助開發(fā)者盡快確認(rèn)故障。
執(zhí)行故障策略時,也能有的放矢,比如只重試那些由失敗引起的操作,對于403UnAuthorized不可重試。
04
Polly的經(jīng)典策略
?Retry:對網(wǎng)絡(luò)抖動/瞬時錯誤可以執(zhí)行retry策略(預(yù)期故障可以很快恢復(fù)),?Circuit Breaker:為避免無效重試導(dǎo)致的故障傳播,在特定時間內(nèi)如果失敗次數(shù)到達(dá)閾值,斷路器打開(在一定時間內(nèi)快速失敗); 同時啟動一個timer,斷路器進(jìn)入半開模式(發(fā)出少量請求,請求成功則認(rèn)為故障已經(jīng)修復(fù),進(jìn)入關(guān)閉狀態(tài),重置失敗計數(shù)器。)
services.AddHttpClient("small")//降級.AddPolicyHandler(Policy<HttpResponseMessage>.HandleInner<Exception>().FallbackAsync(new HttpResponseMessage(),async b =>{// 1、降級打印異常Console.WriteLine($"服務(wù)開始降級,上游異常消息:{b.Exception.Message}");// 2、降級后的數(shù)據(jù)b.Result.Content= new StringContent("請求太多,請稍后重試", Encoding.UTF8, "text/html");b.Result.StatusCode = HttpStatusCode.TooManyRequests;await Task.CompletedTask;}))//熔斷.AddPolicyHandler(Policy<HttpResponseMessage>.Handle<Exception>().CircuitBreakerAsync(3, // 打開斷路器之前失敗的次數(shù)TimeSpan.FromSeconds(20), // 斷路器的開啟的時間間隔(ex, ts) => //熔斷器開啟{Console.WriteLine($"服務(wù)斷路器開啟,異常消息:{ex.Exception.Message}");Console.WriteLine($"服務(wù)斷路器開啟的時間:{ts.TotalSeconds}s");},() => { Console.WriteLine($"服務(wù)斷路器重置"); }, //斷路器重置事件() => { Console.WriteLine($"服務(wù)斷路器半開啟(一會開,一會關(guān))"); } //斷路器半開啟事件))//重試.AddPolicyHandler(Policy<HttpResponseMessage>.Handle<Exception>().RetryAsync(3))// 超時.AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(2)));
??當(dāng)一個應(yīng)用存在多個Http調(diào)用,按照上面的經(jīng)典寫法,代碼中會混雜大量重復(fù)、與業(yè)務(wù)無關(guān)的口水代碼,
思考如何優(yōu)雅的對批量HttpClient做彈性策略。
這里提供兩個實踐:
① 博客園馳名博主edisonchou: 使用AOP框架,動態(tài)織入Polly
② CSDN某佚名大牛,使用反射加配置實現(xiàn)的PollyHttpClientServiceCollectionExtension擴(kuò)展類, 支持在配置文件指定HttpClientName

05
Golang的斷路器
go get github.com/sony/gobreaker
func NewCircuitBreaker(st Settings) *CircuitBreaker 實例化斷路器對象, 參數(shù)如下:
type Settings struct {Name stringMaxRequests uint32 #半開狀態(tài)允許的最大請求數(shù)量,默認(rèn)為0,允許1個請求Interval time.DurationTimeout time.Duration # 斷路器進(jìn)入半開狀態(tài)的間隔,默認(rèn)60sReadyToTrip func(counts Counts) bool # 切換狀態(tài)的邏輯OnStateChange func(name string, from State, to State)}

下面這個示例演示了:請求谷歌網(wǎng)站,失敗比例達(dá)到60%,就切換到"打開"狀態(tài),同時開啟60sTimer,到60s進(jìn)入“半開”狀態(tài)(允許發(fā)起一個請求),如果成功, 斷路器進(jìn)入"關(guān)閉"狀態(tài);失敗則重新進(jìn)入“打開”狀態(tài),并重置60sTimer
package mainimport ("fmt""io/ioutil""log""net/http""github.com/sony/gobreaker")var cb *gobreaker.CircuitBreakerfunc init() {var st gobreaker.Settingsst.Name = "HTTP GET"st.ReadyToTrip = func(counts gobreaker.Counts) bool {failureRatio := float64(counts.TotalFailures) / float64(counts.Requests)return counts.Requests >= 3 && failureRatio >= 0.6}cb = gobreaker.NewCircuitBreaker(st)}// Get wraps http.Get in CircuitBreaker.func Get(url string) ([]byte, error) {body, err := cb.Execute(func() (interface{}, error) {resp, err := http.Get(url)if err != nil {return nil, err}defer resp.Body.Close()body, err := ioutil.ReadAll(resp.Body)if err != nil {return nil, err}return body, nil})if err != nil {return nil, err}return body.([]byte), nil}func main() {body, err := Get("http://www.google.com/robots.txt")if err != nil {log.Fatal(err)}fmt.Println(string(body))}
總結(jié)
本文記錄了云原生系統(tǒng)的彈性模式:通過預(yù)設(shè)策略直面失敗,補償暫時不可用的請求、避免故障傳播, 這對于實現(xiàn)微服務(wù)高可用、彈性容錯相當(dāng)重要。
?https://blog.csdn.net/weixin_44588495/article/details/106361934?https://blog.csdn.net/qq_26900081/article/details/108071374?https://www.cnblogs.com/edisonchou/p/9159644.html?https://docs.microsoft.com/en-us/dotnet/architecture/cloud-native/application-resiliency-patterns?https://docs.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker


更多干貨及最佳實踐
關(guān)注并星標(biāo)我們
后臺回復(fù)cloud-native,獲取微軟云原生技術(shù)白皮書

??本文永久鏈接在這里!
