云原生系统之弹性模式

云计算 云原生
在分布式体系结构中,当服务B不响应来自服务A的网络请求会发生什么?当服务C暂时不可用,其他调用C的服务被阻塞时该怎么办?”

[[410918]]

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

大纲

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弹性和瞬时错误处理库,允许开发者以流畅和线程安全的方式表达弹性策略。

策略 场景 行为
Retry 抖动/瞬时错误,短时间内自动恢复 在特定操作上配置重试行为
Circuit Breaker 在短期内不大可能恢复 当故障超过阈值,在一段时间内快速失败
Timeout   限制调用者等待响应的时间
Bulkhead   将操作限制在固定的资源池,防止故障传播
Cache   自动存储响应
Bulkhead   一旦失败,定义结构化的行为

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

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

03短期中断的响应码

 

Http Status code 原因
404 not found
408 request timeout
429 two many requests
502 bad gateway
503 service unavailable
504 gateway timeout

 

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

执行故障策略时,也能有的放矢,比如只重试那些由失败引起的操作,对于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

[[410921]]

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)) 

总结

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

 

责任编辑:武晓燕 来源: 精益码农
相关推荐

2023-08-30 16:22:03

云原生云计算

2023-09-13 11:58:17

云原生反模式

2023-07-26 16:20:36

云原生云计算

2023-07-13 15:24:13

云计算云原生架构

2023-08-22 15:40:12

云原生云计算

2022-08-18 17:58:38

华为云华为云TechWav云原生数据库

2020-03-04 09:56:56

网络安全云原生容器

2023-02-08 07:55:33

K8sHPA服务器

2022-09-07 21:43:34

云原生存储技术消息队列

2021-11-15 08:00:00

云原生端点漏洞

2022-05-26 15:02:35

Docker容器云原生

2023-10-12 09:48:00

微服务工具

2023-01-05 14:41:24

2010-05-06 14:38:09

云计算

2014-11-17 15:16:34

2022-12-26 08:14:57

K8sCronhpa定时弹性

2023-07-10 15:47:05

2023-02-17 13:08:31

2022-04-25 12:39:57

云原生云计算

2015-01-19 09:50:12

阿里云12306云计算
点赞
收藏

51CTO技术栈公众号