背景
从单体服务拆分到微服务过程当中,原来模块间交互逐渐抽离成远程调用,可能http,rpc,tcp,,,等等,那么这些模块在调用中必定存在某种依赖关系。这时一旦下游某个 服务超时或者down,请求量还很大的时候,那么最坏状况是上游服务也会所以超时或者down掉。它的上游也如此,如此“递归”同样的出错在微服务中叫作雪崩效应。那么做为微服务架构中的三剑客之一--熔断,就是为了解决这个问题,熔断器像是一个保险丝。当咱们依赖的服务出现问题时,能够及时容错。一方面能够减小依赖服务对自身访问的依赖,防止出现雪崩效应;另外一方面下降请求频率以方便上游尽快恢复服务。本文结合调研结果进行讲解,目的也是为了接下来的自研熔断器作准备。git
由来
毕竟你们都见识过了2020美股熔断时刻,想必这个词也并不陌生。熔断一词最先是对电路中对引出线过载,使保险丝断掉而进行保护机制,这一过程的描述。后来是指为控制股票、期货或其余金融衍生产品的交易风险,为其单日价格波动幅度规定区间限制,一旦成交价触及区间上下限,交易则自动中断一段时间。因此你们能够认为,所谓熔断便是一种保护机制,出了事,缓一缓,等一等,稍后看看能不能恢复。github
1、功能简介
熔断器要最基本完成一下功能list:golang
功能点 | 说明 |
---|---|
通路 | 当请求符合预期结果将和正常调用无区别 |
熔断 | 当请求符合不预期结果必定时间内必定数量下将断开方法执行再也不请求下游方法 |
半通路 | 在熔断状况下,当请求符合预期结果开始符合预期结果必定时间内必定数量下将放行部分请求到下游方法 |
熔断休眠 | 在熔断后一段时间后转换成为半通路 |
画图来说的话: ·初始为close状态,一旦遇到请求失败时,会触发熔断检测,熔断检测来决定是否将状态从closed转为open。 ·当熔断器为open状态时,会熔断全部当前方法的执行,直到冷却时间结束,会从open转变为half-open状态。 ·当熔断器为half-open状态时,以检测时间为周期去发送请求。请求成功则计数器加1,当计数器达到必定阈值时则转为close状态;请求失败则转为open状态。json
2、调研方向:
调研中主要调研了目前比较主流的两个熔断器,在设计上有所不一样,Hystrix更注重异步请求,统计收集更全面,虽然使用数占上风可是总体过重,gobreaker在使用上更方便,总体及其轻量知足同步调用的各类需求,介绍完使用稍后咱们再来作对比。
一、hystrix
Hystrix的golang版本项目地址是:https://github.com/afex/hystrix-go Hystrix是Netflix开源的一个限流熔断的项目、主要有如下功能: 1)隔离(线程池隔离和信号量隔离):限制调用分布式服务的资源使用,某一个调用的服务出现问题不会影响其余服务调用。 2)优雅的降级机制:超时降级、资源不足时(线程或信号量)降级,降级后能够配合降级接口返回托底数据。 3)融断:当失败率达到阀值自动触发降级(如因网络故障/超时形成的失败率高),熔断器触发的快速失败会进行快速恢复。 4)缓存:提供了请求缓存、请求合并实现。支持实时监控、报警、控制(修改配置)
一、添加配置
使用时建议首先添加配置,其中可配置项有:
Timeout int `json:"timeout"` MaxConcurrentRequests int `json:"max_concurrent_requests"` RequestVolumeThreshold int `json:"request_volume_threshold"` SleepWindow int `json:"sleep_window"` ErrorPercentThreshold int `json:"error_percent_threshold"`
其中:缓存
字段 | 说明 |
---|---|
Timeout | 执行command的超时时间。默认时间是1000毫秒 |
MaxConcurrentRequests | command的最大并发量 默认值是10 |
SleepWindow | 当熔断器被打开后,SleepWindow的时间就是控制过多久后去尝试服务是否可用了。默认值是5000毫秒 |
RequestVolumeThreshold | 一个统计窗口10秒内请求数量。达到这个请求数量后才去判断是否要开启熔断。默认值是20 |
ErrorPercentThreshold | 错误百分比,请求数量大于等于RequestVolumeThreshold而且错误率到达这个百分比后就会启动熔断 默认值是50 |
添加配置eg:网络
hystrix.ConfigureCommand(strategyName, hystrix.CommandConfig{ Timeout: 1000, MaxConcurrentRequests: 100, RequestVolumeThreshold: 10, SleepWindow: 2, ErrorPercentThreshold: 50, })
二、方法调用
hystrix的使用是很是简单的,同步执行,直接调用Do方法。架构
datatest := []byte{} err := hystrix.Do("my_command", func() error { res, err := http.Get("www.baidu.com") if err != nil { return err } defer res.Body.Close() data, err := ioutil.ReadAll(res.Body) if err != nil { return err } datatest = data return nil }, func(err error) error { fmt.Println("任务失败") return nil })
异步执行Go方法,内部实现是启动了一个gorouting,若是想获得自定义方法的数据,须要你传channel来处理数据,或者输出。返回的error也是一个channel并发
output := make(chan []byte, 1) errors := hystrix.Go("my_command", func() error { res, err := http.Get("www.baidu.com") if err != nil { return err } defer res.Body.Close() data, err := ioutil.ReadAll(res.Body) if err != nil { return err } output <- data return nil }, nil) select { case out := <-output: fmt.Println("任务成功", string(out)) case err := <-errors: fmt.Println("任务失败", err.Errors()) } /* 这里能用得上得就是配置里得并发数(不能听任开太多协程) 同步调用底层用得也是异步调用,不过会本身处理等待errors chan */
这里介绍一下hystrix的实现,首先总体流程如上图,hystrix在初始化时就已经初始化了一个基于channel实现的令牌桶,当请求到达的时候,首先第一件事先判断是否熔断,此处降会调用熔断对象(circuit.go)的AllowRequest() 函数,该函数从指标类(metrics.go)中统计出请求数和错误指数,前者判断是否执行熔断的必要条件,后者是充分条件(很好理解qps=1基本就告别限流了)。而后逻辑到了限流模块,成功拿到令牌的继续执行,没有的走回调函数或者直接返回。执行中会记录各类事件,好比请求数,成功数,失败数,超时等。。。在这个请求终态时此时触发golang的条件锁(sync.Cond)唤醒协程返回令牌上报事件,事件模块异步统计。 这里值得提的是hystrix的统计模块,采用滑动窗口计数(下图),
hystrix采用滑动窗口计数很好地解决了时间轴上的时间间隔问题同时还支持指标采集,可是在滑动窗口的实现上采用golang的map类型,过时元素将delete掉,这种方法只是标记此块内存不可用并无真正释放内存,须要设置gc指数进行回收。异步
二、gobreaker
项目地址为:https://github.com/sony/gobreaker gobreaker是索尼的开源的一个限流熔断的项目、主要有如下功能: 1)简单的代码结构,一共300多行,极容易阅读。 2)一样提供降级机制:可是只会根据当前连续错误数在熔断时进行降级。 3)融断:当失败数达到阀值自动触发降级(如因网络故障/超时形成的失败率高),熔断器触发的快速失败会进行快速恢复。
一、添加配置
type Settings struct { Name string MaxRequests uint32 Interval time.Duration Timeout time.Duration ReadyToTrip func(counts Counts) bool OnStateChange func(name string, from State, to State) }
字段 | 说明 |
---|---|
MaxRequests | 最大请求数。当在最大请求数下,均请求正常的状况下,会关闭熔断器 |
interval | 一个正常的统计周期。若是为0,那每次都会将计数清零 |
timeout | 进入熔断后,能够再次请求的时间 |
readyToTrip | 判断熔断生效的钩子函数(经过Counts 判断是否开启熔断。须要自定义也能够走默认配置) |
onStateChagne | 状态变动的钩子函数(看是否须要) |
var cb *breaker.CircuitBreaker cb = breaker.NewCircuitBreaker(breaker.Settings{ Name: "Get", MaxRequests: 100, Interval: time.Second*2, Timeout: time.Second*2 })
二、调用过程
熔断器的执行操做,主要包括三个阶段;①请求以前的断定;②服务的请求执行;③请求后的状态和计数的更新tcp
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 }
gobreaker不一样采用原子计数,加锁统计,过时清零的操做,属于简单粗暴不支持指标采集,是对熔断的这个操做(以下图)的短小精悍的实现。 其中原子计数带来的问题会比较大,以下图,当59秒的时候尚未到达数量阈值,可是1:01时又来大量请求,此时由于进入下一时刻计数早就清空,这样对于故障判断的准确性带来了挑战
以上是对golang熔断器的调研结果,相信不一样场景会有不一样需求,不一样需求能够对熔断器来回选择,下一篇会介绍自研的熔断器。