云原生系统之弹性模式

全栈码农画像

共 5307字,需浏览 11分钟

 · 2021-07-16

大纲


1.云原生系统的弹性模式resiliency pattern    1.1 服务故障的雪崩效应    1.2 回应之前云原生--弹性请求的疑问?

2. 弹性模式:作用在下游请求消息上3. 短期中断的响应码4. Polly经典策略5.   Golang 断路器模式
德国哲学家尼采说过:那些杀不死我的东西,只会让我更加强大


hi,好久不见,马甲哥之前意译并连载了《Microsoft Cloud-native toc.pdf》部分内容

什么是云原生现代云原生设计理念.NET微服务谈到云原生,绕不开容器化支撑性服务 & 自动化能力

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原因
404not found
408request timeout
429two many requests
502bad gateway
503service unavailable
504gateway timeout

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

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

Kubernetes探针踩坑记

04

 Polly的经典策略


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做弹性策略。

这里提供两个实践:

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

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

05

 Golang的断路器

go get github.com/sony/gobreaker

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

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

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

package mainimport (    "fmt"    "io/ioutil"    "log"    "net/http"    "github.com/sony/gobreaker")var cb *gobreaker.CircuitBreakerfunc 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))}

总结



   本文记录了云原生系统的弹性模式:通过预设策略直面失败,补偿暂时不可用的请求、避免故障传播, 这对于实现微服务高可用、弹性容错相当重要。
https://blog.csdn.net/weixin_44588495/article/details/106361934https://blog.csdn.net/qq_26900081/article/details/108071374https://www.cnblogs.com/edisonchou/p/9159644.htmlhttps://docs.microsoft.com/en-us/dotnet/architecture/cloud-native/application-resiliency-patternshttps://docs.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker





更多干货及最佳实践
关注并星标我们

后台回复cloud-native,获取微软云原生技术白皮书

今天因为你的点赞,让我元气满满!

 ✍️本文永久链接在这里!

浏览 13
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报