19

基于 Golang 语言的微服务熔断器

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzA4Mjc1NTMyOQ%3D%3D&%3Bmid=2247484106&%3Bidx=1&%3Bsn=7a2c2c931794fc5aa80656755930e8fc
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

背景

从单体服务拆分到微服务过程中,原来模块间交互逐渐抽离成远程调用,可能http,rpc,tcp,,,等等,那么这些模块在调用中一定存在某种依赖关系。

这时一旦下游某个服务超时或者down,请求量还很大的时候,那么最坏情况是上游服务也会因此超时或者down掉。它的上游也如此,如此“递归”一样的出错在微服务中叫做雪崩效应。

那么作为微服务架构中的三剑客之一--熔断,就是为了解决这个问题,熔断器像是一个保险丝。当我们依赖的服务出现问题时,可以及时容错。一方面可以减少依赖服务对自身访问的依赖,防止出现雪崩效应;另一方面降低请求频率以方便上游尽快恢复服务。本文结合调研结果和自研熔断器进行讲解

由来

毕竟大家都见识过了2020美股熔断时刻,想必这个词也并不陌生。熔断一词最早是对电路中对引出线过载,使保险丝断掉而进行保护机制,这一过程的描述。

后来是指为控制股票、期货或其他金融衍生产品的交易风险,为其单日价格波动幅度规定区间限制,一旦成交价触及区间上下限,交易则自动中断一段时间。所以大家可以认为,所谓熔断即是一种保护机制,出了事,缓一缓,等一等,稍后看看能不能恢复。

一、功能简介

熔断器要最基本完成一下功能list:

功能点 说明 通路 当请求符合预期结果将和正常调用无区别 熔断 当请求符合不预期结果一定时间内一定数量下将断开方法执行不再请求下游方法 半通路 在熔断情况下,当请求符合预期结果开始符合预期结果一定时间内一定数量下将放行部分请求到下游方法 熔断休眠 在熔断后一段时间后转换成为半通路

画图来讲的话:

YVnuayR.jpg!mobile

图1

  • 初始为close状态,一旦遇到请求失败时,会触发熔断检测,熔断检测来决定是否将状态从closed转为open。

  • 当熔断器为open状态时,会熔断所有当前方法的执行,直到冷却时间结束,会从open转变为half-open状态。

  • 当熔断器为half-open状态时,以检测时间为周期去发送请求。请求成功则计数器加1,当计数器达到一定阈值时则转为close状态;请求失败则转为open状态。

二、调研方向:

调研中主要调研了目前比较主流的两个熔断器,在设计上有所不同,Hystrix更注重异步请求,统计收集更全面,虽然使用数占上风但是整体太重,gobreaker在使用上更方便,整体及其轻量满足同步调用的各种需求

1、hystrix

Hystrix是Netflix开源的一个限流熔断的项目、主要有以下功能:
 1)隔离(线程池隔离和信号量隔离):限制调用分布式服务的资源使用,某一个调用的服务出现问题不会影响其他服务调用。
 2)优雅的降级机制:超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。
 3)融断:当失败率达到阀值自动触发降级(如因网络故障/超时造成的失败率高),熔断器触发的快速失败会进行快速恢复。

4)缓存:提供了请求缓存、请求合并实现。支持实时监控、报警、控制(修改配置)
缺点:需要指定并发数 
其中Hystrix的golang版本是:https://github.com/afex/hystrix-go

1、添加配置

使用时建议首先添加配置,其中可配置项有:
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,
})

2、方法调用

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
*/

2、gobreaker

项目地址为:

https://github.com/sony/gobreaker

1、添加配置

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

2、调用过程

熔断器的执行操作,主要包括三个阶段;

①请求之前的判定;

②服务的请求执行;

③请求后的状态和计数的更新

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
}

hystrix天生具有限流特性,统计模块采用异步事件上报,同步计数的方式,每次请求都会计算错误率,其中统计模块采用滑动窗口的计数方式,解决了时间轴上的时间间隔问题同时还支持指标采集,但是在滑动窗口的实现上采用map,过期元素将delete掉,这种方法只是标记此块内存不可用并没有真正释放内存,需要设置gc指数进行回收

BVVRryq.png!mobile

图2

gobreaker不同采用原子计数,加锁统计,过期清零的操作,属于简单粗暴不支持指标采集

MZbMVj3.png!mobile

图3

三、自研breaker

项目地址:

https://github.com/EAHITechnology/breaker.git

结合hystrix滑动窗口的设计和gobreaker的结构清晰的代码逻辑,加上对并发的优化权衡的点,目前设计自研了breaker熔断器

  • 支持滑动窗口计数

  • 滑动窗口实现为环形结构,不会有新内存创建和老内存回收,每次查找计数次数可控

  • 支持并发请求,异步统计结果

by2AJj3.png!mobile

图4

其中ringslidingwindow的计数设计:

UNNBfe3.png!mobile

图5

缺点

目前市面上除了串行化请求的gobreaker基本上都没有应对流量尖刺,但是串行化又带来了无法做到真正并发的问题,所以这也是目前设计的缺憾的地方好在目前这种情况还是少数,类似在1S中前几毫秒的大量并发在业务上嗯就允许,未来会将这里考虑进去

微信群

一个人可以走得很快,一群人可以走得很远。

B3yUfqm.jpg!mobile

本文由「微信群」成员投稿。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK