云原生系统之弹性模式

大纲

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的服务被阻塞时该怎么办?”

云原生系统之弹性模式_第1张图片

由于网络原因或自身原因,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
一旦失败,定义结构化的行为

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

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

云原生系统之弹性模式_第2张图片

03

 短期中断的响应码

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

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

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

Kubernetes探针踩坑记

04

 Polly的经典策略

•Retry:对网络抖动/瞬时错误可以执行retry策略(预期故障可以很快恢复),•Circuit Breaker:为避免无效重试导致的故障传播,在特定时间内如果失败次数到达阈值,断路器打开(在一定时间内快速失败);           同时启动一个timer,断路器进入半开模式(发出少量请求,请求成功则认为故障已经修复,进入关闭状态,重置失败计数器。)


services.AddHttpClient("small")
        //降级
        .AddPolicyHandler(Policy.HandleInner().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.Handle() 
           .CircuitBreakerAsync(
              3,    // 打开断路器之前失败的次数
              TimeSpan.FromSeconds(20), // 断路器的开启的时间间隔
              (ex, ts) =>  //熔断器开启
              {
                  Console.WriteLine($"服务断路器开启,异常消息:{ex.Exception.Message}");
                  Console.WriteLine($"服务断路器开启的时间:{ts.TotalSeconds}s");
              }, 
              () => { Console.WriteLine($"服务断路器重置"); },   //断路器重置事件
              () => { Console.WriteLine($"服务断路器半开启(一会开,一会关)"); }  //断路器半开启事件
            )
        )
        //重试
        .AddPolicyHandler(Policy.Handle().RetryAsync(3))
       // 超时 
       .AddPolicyHandler(Policy.TimeoutAsync(TimeSpan.FromSeconds(2))); 
       

☹️当一个应用存在多个Http调用,按照上面的经典写法,代码中会混杂大量重复、与业务无关的口水代码,
思考如何优雅的对批量HttpClient做弹性策略。

这里提供两个实践:

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

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

云原生系统之弹性模式_第3张图片

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)
}
云原生系统之弹性模式_第4张图片

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

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

总结

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

云原生系统之弹性模式_第5张图片

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

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

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

云原生系统之弹性模式_第6张图片

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

你可能感兴趣的:(分布式,java,nginx,zookeeper,http)