云原生系统之弹性模式

发布时间:2021-07-15 22:45 来源: 阅读:0 作者:小码甲 栏目: 云计算 欢迎投稿:712375056

本文转载自微信公众号「精益码农」,作者小码甲 。转载本文请联系精益码农公众号。

大纲

1.云原生系统的弹性模式resiliency pattern

  • 1.1 服务故障的雪崩效应
  • 1.2 回应之前云原生--弹性请求的疑问?

2. 弹性模式:作用在下游请求消息上

3. 短期中断的响应码

4. Polly经典策略

5. Golang 断路器模式

德国哲学家尼采说过:那些杀不死我的东西,只会让我更加强大。

01云原生系统的弹性模式

结合最近的工作经验,本次继续聊一聊云原生的弹性模式 (resilience not scale), 这也是回应《现代云原生设计理念》中

“在分布式体系结构中,当服务B不响应来自服务A的网络请求会发生什么?

当服务C暂时不可用,其他调用C的服务被阻塞时该怎么办?”

由于网络原因或自身原因,B、C服务不能及时响应,服务A发起的请求将被阻塞(直到B、C响应),此时若大量请求涌入,服务A的线程资源将被消耗殆尽,服务A的处理性能受到极大影响,进而影响下游依赖的external clients/backend srv。

故障会传播,造成连锁反应,对整个分布式结构造成灾难性后果,这就是服务故障的“雪崩效应”。

当B、C服务不可用,下游客户端/backend srv能做什么?

客观上请求不通,执行预定的弹性策略:重试/断路?

02弹性模式:作用在下游的请求消息上

弹性模式是系统面对故障仍然保持工作状态的能力,它不是为了避免故障,而是接受故障并尝试去面对它。

Polly是一个全面的.NET弹性和瞬时错误处理库,允许开发者以流畅和线程安全的方式表达弹性策略。

一般将弹性策略作用到各种请求消息上(外部客户端请求或后端服务请求)。

其目的是补偿暂时不可用的服务请求。

03短期中断的响应码

正确规范的响应码能帮助开发者尽快确认故障。

执行故障策略时,也能有的放矢,比如只重试那些由失败引起的操作,对于403UnAuthorized不可重试。

04Polly的经典策略

  • Retry:对网络抖动/瞬时错误可以执行retry策略(预期故障可以很快恢复),
  • Circuit Breaker:为避免无效重试导致的故障传播,在特定时间内如果失败次数到达阈值,断路器打开(在一定时间内快速失败);

同时启动一个timer,断路器进入半开模式(发出少量请求,请求成功则认为故障已经修复,进入关闭状态,重置失败计数器。)

  1. services.AddHttpClient("small"
  2.         //降级 
  3.         .AddPolicyHandler(Policy<HttpResponseMessage>.HandleInner<Exception>().FallbackAsync(new HttpResponseMessage(),async b => 
  4.         { 
  5.            // 1、降级打印异常 
  6.           Console.WriteLine($"服务开始降级,上游异常消息:{b.Exception.Message}"); 
  7.           // 2、降级后的数据 
  8.           b.Result.Content= new StringContent("请求太多,请稍后重试", Encoding.UTF8, "text/html"); 
  9.           b.Result.StatusCode = HttpStatusCode.TooManyRequests; 
  10.           await Task.CompletedTask; 
  11.         })) 
  12.         //熔断                                                       
  13.         .AddPolicyHandler(Policy<HttpResponseMessage>.Handle<Exception>()  
  14.            .CircuitBreakerAsync( 
  15.               3,    // 打开断路器之前失败的次数 
  16.               TimeSpan.FromSeconds(20), // 断路器的开启的时间间隔 
  17.               (ex, ts) =>  //熔断器开启 
  18.               { 
  19.                   Console.WriteLine($"服务断路器开启,异常消息:{ex.Exception.Message}"); 
  20.                   Console.WriteLine($"服务断路器开启的时间:{ts.TotalSeconds}s"); 
  21.               },  
  22.               () => { Console.WriteLine($"服务断路器重置"); },   //断路器重置事件 
  23.               () => { Console.WriteLine($"服务断路器半开启(一会开,一会关)"); }  //断路器半开启事件 
  24.             ) 
  25.         ) 
  26.         //重试 
  27.         .AddPolicyHandler(Policy<HttpResponseMessage>.Handle<Exception>().RetryAsync(3)) 
  28.        // 超时  
  29.        .AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(2)));  
  30.         

??当一个应用存在多个Http调用,按照上面的经典写法,代码中会混杂大量重复、与业务无关的口水代码,

思考如何优雅的对批量HttpClient做弹性策略。

这里提供两个实践:

① 博客园驰名博主edisonchou: 使用AOP框架,动态织入Polly

② 某佚名大牛,使用反射加配置实现的PollyHttpClientServiceCollectionExtension扩展类, 支持在配置文件指定HttpClientName

05Golang的断路器

  1. go get github.com/sony/gobreaker 

func NewCircuitBreaker(st Settings) *CircuitBreaker 实例化断路器对象, 参数如下:

  1. type Settings struct { 
  2.     Name          string 
  3.     MaxRequests   uint32       #半开状态允许的最大请求数量,默认为0,允许1个请求 
  4.     Interval      time.Duration 
  5.     Timeout       time.Duration  # 断路器进入半开状态的间隔,默认60s 
  6.     ReadyToTrip   func(counts Counts) bool   # 切换状态的逻辑 
  7.     OnStateChange func(name string, from State, to State) 

下面这个示例演示了:请求谷歌网站,失败比例达到60%,就切换到"打开"状态,同时开启60sTimer,到60s进入“半开”状态(允许发起一个请求),如果成功, 断路器进入"关闭"状态;失败则重新进入“打开”状态,并重置60sTimer

  1. package main 
  2. import ( 
  3.     "fmt" 
  4.     "io/ioutil" 
  5.     "log" 
  6.     "net/http" 
  7.     "github.com/sony/gobreaker" 
  8. var cb *gobreaker.CircuitBreaker 
  9. func init() { 
  10.     var st gobreaker.Settings 
  11.     st.Name = "HTTP GET" 
  12.     st.ReadyToTrip = func(counts gobreaker.Counts) bool { 
  13.         failureRatio := float64(counts.TotalFailures) / float64(counts.Requests) 
  14.         return counts.Requests >= 3 && failureRatio >= 0.6 
  15.     } 
  16.     cb = gobreaker.NewCircuitBreaker(st) 
  17. // Get wraps http.Get in CircuitBreaker. 
  18. func Get(url string) ([]byte, error) { 
  19.     body, err := cb.Execute(func() (interface{}, error) { 
  20.         resp, err := http.Get(url) 
  21.         if err != nil { 
  22.             return nil, err 
  23.         } 
  24.         defer resp.Body.Close() 
  25.         body, err := ioutil.ReadAll(resp.Body) 
  26.         if err != nil { 
  27.             return nil, err 
  28.         } 
  29.         return body, nil 
  30.     }) 
  31.     if err != nil { 
  32.         return nil, err 
  33.     } 
  34.     return body.([]byte), nil 
  35. func main() { 
  36.     body, err := Get("http://www.google.com/robots.txt"
  37.     if err != nil { 
  38.         log.Fatal(err) 
  39.     } 
  40.     fmt.Println(string(body)) 

总结

本文记录了云原生系统的弹性模式:通过预设策略直面失败,补偿暂时不可用的请求、避免故障传播, 这对于实现微服务高可用、弹性容错相当重要。

【编辑推荐】

免责声明:本站发布的内容(图片、视频和文字)以原创、来自本网站内容采集于网络互联网转载等其它媒体和分享为主,内容观点不代表本网站立场,如侵犯了原作者的版权,请告知一经查实,将立刻删除涉嫌侵权内容,联系我们QQ:712375056,同时欢迎投稿传递力量。