云原生系统之弹性模式
Posted CSDN
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了云原生系统之弹性模式相关的知识,希望对你有一定的参考价值。
出品 | CSDN(ID:CSDNnews)
大纲
1.云原生系统的弹性模式resiliency pattern 1.1 服务故障的雪崩效应 1.2 回应之前云原生--弹性请求的疑问? 2. 弹性模式:作用在下游请求消息上 3. 短期中断的响应码 4. Polly经典策略 5. Golang 断路器模式
德国哲学家尼采说过:那些杀不死我的东西,只会让我更加强大。
云原生系统的弹性模式
“在分布式体系结构中,当服务B不响应来自服务A的网络请求会发生什么?
当服务C暂时不可用,其他调用C的服务被阻塞时该怎么办?”
客观上请求不通,执行预定的弹性策略:重试/断路?
Retry:对网络抖动/瞬时错误可以执行retry策略(预期故障可以很快恢复);
Circuit Breaker:为避免无效重试导致的故障传播,在特定时间内如果失败次数到达阈值,断路器打开(在一定时间内快速失败);
同时启动一个timer,断路器进入半开模式(发出少量请求,请求成功则认为故障已经修复,进入关闭状态,重置失败计数器)。
services.AddHttpClient("small")
//降级
.AddPolicyHandler(Policy<HttpResponseMessage>.HandleInner<Exception>().FallbackAsync(new HttpResponseMessage(),async b =>
{
// 1、降级打印异常
Console.WriteLine($"服务开始降级,上游异常消息:{b.Exception.Message}");
// 2、降级后的数据
b.Result.Content= new StringContent("请求太多,请稍后重试", Encoding.UTF8, "text/html");
b.Result.StatusCode = HttpStatusCode.TooManyRequests;
await Task.CompletedTask;
}))
//熔断
.AddPolicyHandler(Policy<HttpResponseMessage>.Handle<Exception>()
.CircuitBreakerAsync(
3, // 打开断路器之前失败的次数
TimeSpan.FromSeconds(20), // 断路器的开启的时间间隔
(ex, ts) => //熔断器开启
{
Console.WriteLine($"服务断路器开启,异常消息:{ex.Exception.Message}");
Console.WriteLine($"服务断路器开启的时间:{ts.TotalSeconds}s");
},
() => { Console.WriteLine($"服务断路器重置"); }, //断路器重置事件
() => { Console.WriteLine($"服务断路器半开启(一会开,一会关)"); } //断路器半开启事件
)
)
//重试
.AddPolicyHandler(Policy<HttpResponseMessage>.Handle<Exception>().RetryAsync(3))
// 超时
.AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(2)));
☹️当一个应用存在多个Http调用,按照上面的经典写法,代码中会混杂大量重复、与业务无关的口水代码。
思考如何优雅的对批量HttpClient做弹性策略。
Golang的断路器
go get github.com/sony/gobreaker
type Settings struct { Name string MaxRequests uint32 #半开状态允许的最大请求数量,默认为0,允许1个请求 Interval time.Duration Timeout time.Duration # 断路器进入半开状态的间隔,默认60s ReadyToTrip func(counts Counts) bool # 切换状态的逻辑 OnStateChange func(name string, from State, to State)}
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"github.com/sony/gobreaker"
)
var cb *gobreaker.CircuitBreaker
func init() {
var st gobreaker.Settings
st.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))
}