zoukankan      html  css  js  c++  java
  • 云原生系统 之 弹性模式

    大纲:

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

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

    1. 弹性模式: 作用在下游请求消息上
    2. 短期中断的响应码
    3. Polly经典策略
    4. Golang 断路器模式

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

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

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

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

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

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

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

    当B、C服务不可用,下游客户端/backend srv能做什么?
    客观上请求不通,执行预定的弹性策略: 重试/断路?

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

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

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

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

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

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

    这里我想着重聊一聊断路器模式:Circuit Breaker
    断路器模式有三种状态:

    • Close: 请求无故障,断路器关闭
    • Open: 出现故障到达阈值,切换到[Open]状态, 快速失败。
    • Half-Open:进入Open状态后,开启一个Timer, 到达Timer, 会进入Half-Open状态,会尝试发送少量请求。
      成功进入 ----> Close
      失败----> Open,开启新的Timer

    3. 短期中断的响应码

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

    正确规范的响应码能给开发者足够的信息,尽快确认故障。

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

    4. Polly的经典策略

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

    install-package Microsoft.Extensions.Http.Polly

    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

    5. Golang的Circuit Breaker pkg

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

    总结

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

    注意,Circuit Breaker与Retry的意图不同,Retry模式的目的是让应用程序重试一个可能成功的操作;
    Circuit Breaker 防止程序应用程序执行可能失败的操作。

    一般情况下,应用程序会结合这两种模式:断路器包装 重试操作。


    本文来自博客园,作者:{有态度的马甲},转载请注明原文链接:https://www.cnblogs.com/JulianHuang/p/15002798.html

    欢迎关注我的原创高价值公众号

    上海鲜花港 - 郁金香
  • 相关阅读:
    内存分配小问题
    从MACHINE_START开始
    Linux驱动学习(二)
    9,斐波那契数列 6,旋转数组找最小 8青蛙跳台阶 JAVA实现
    数组练习题
    类的练习
    for循环练习题
    封装练习题
    Facebook成为Apache基金会的白金赞助商
    Visual Studio 2010
  • 原文地址:https://www.cnblogs.com/JulianHuang/p/15002798.html
Copyright © 2011-2022 走看看