微服务的断路器实现图解Golang通用版
断路器背景
微服务连锁故障场景
在分布式环境中,各个微服务相互调用,当某些情况下,比如后端中间件服务故障、第三方服务中断导致某个服务无限期不可用,短时间无法恢复,则可能会导致连锁故障,最终影响压垮整个业务集群
断路器与重试
断路器模式不同于重试模式,重试模式是使应用程序可以重试操作以期望它会成功,而断路器模式是防止应用程序执行一个可能失败的操作,减少执行可能失败操作的CPU、内存、线程等资源的浪费,从而保证服务的整体可用
断路器设计解析
基于代理模式的断路器
断路器相当于一个请求操作执行的代理,托管请求操作的执行
实现原理流程:
- 拦截服务执行的请求,通过当前状态决定是否直接返回,如果否则执行后续操作
- 尝试执行操作,并获取返回结果
- 根据返回结果和当前统计信息,决定当前断路器的状态,修改状态
- 返回执行结果
断路器状态机
断路器状态机实现上有三种状态:Closed(断路器关闭)、Open(开放)、HalfOpen(半开放)
状态 | 说明 | 备注 |
---|---|---|
Closed | 关闭 | 断路器关闭正常执行操作 |
Open | 打开 | 断路器开放,所有请求直接返回错误,不执行任何请求 |
HalfOpen | 半开放 | 允许有限数量的请求通过,如果执行成功,恢复到关闭状态,如果仍然失败,则恢复到开放,然后重新启动超时定时器 |
#断路器实现
实现原理图解
断路器实现实现主要分为三部分:状态统计、状态转移、请求执行
状态统计:统计已经执行的请求的成功失败的数量,以确定是否需要进行状态转移 状态转移:根据当前统计信息和当前状态来进行目标状态的确定及转移操作 请求执行:代理前端任务的执行,如果当前状态不需要进行尝试执行,就直接返回错误,避免资源浪费
Golang里面已经有开源的实现,https://github.com/sony/gobreaker/blob/, 接下来救市剖析它的实现
状态统计-计数器Counts
Counts就是一个计数器,记录当前请求成功和失败的数量
type Counts struct {
Requests uint32 // 请求数
TotalSuccesses uint32 // 成功
TotalFailures uint32 // 失败
ConsecutiveSuccesses uint32 // 连续成功
ConsecutiveFailures uint32 // 连续失败
}
计数器完成对应请求状态的次数,为后续状态转移提供数据, Counts提供了onRequest、onSuccess、onFailure、clear几个辅助接口用于实现对应请求状态的操作,感兴趣可以看下
状态机- CircuitBreaker
type CircuitBreaker struct {
name string
// maxRequests限制half-open状态下最大的请求数,避免海量请求将在恢复过程中的服务再次失败
maxRequests uint32
// interval用于在closed状态下,断路器多久清除一次Counts信息,如果设置为0则在closed状态下不会清除Counts
interval time.Duration
// timeout进入open状态下,多长时间切换到half-open状态,默认60s
timeout time.Duration
// readyToTrip熔断条件,当执行失败后,会根据readyToTrip决定是否进入Open状态
readyToTrip func(counts Counts) bool
// onStateChange断路器状态变更回调函数
onStateChange func(name string, from State, to State)
mutex sync.Mutex
//. state 断路器状态
state State
// generation 是一个递增值,相当于当前断路器状态切换的次数, 为了避免状态切换后,未完成请求对新状态的统计的影响,如果发现一个请求的generation同当前的generation不同,则不会进行统计计数
generation uint64
// Counts 统计
counts Counts
// expiry 超时过期用于open状态到half-open状态的切换,当超时后,会从open状态切换到half-open状态
expiry time.Time
}
核心流程
CircuitBreaker.Execute
请求执行,对外开放的请求执行接口
func (cb *CircuitBreaker) Execute(req func() (interface{}, error)) (interface{}, error) {
// 执行请求钩子,会根据当前状态,来返回当前的generation和err(如果位于open和half-open则不为nil), 通过err来进行判断是否直接返回
generation, err := cb.beforeRequest()
if err != nil {
return nil, err
}
// 捕获panic,避免应用函数错误造成断路器panic
defer func() {
e := recover()
if e != nil {
cb.afterRequest(generation, false)
panic(e)
}
}()
// 执行请求
result, err := req()
// 根据结果来进行对应状态的统计, 同时传递generation
cb.afterRequest(generation, err == nil)
return result, err
}
CircuitBreaker.beforeRequest
func (cb *CircuitBreaker) beforeRequest() (uint64, error) {
cb.mutex.Lock()
defer cb.mutex.Unlock()
// 获取当前的状态
now := time.Now()
state, generation := cb.currentState(now)
// open和half-open状态则直接返回
if state == StateOpen {
return generation, ErrOpenState
} else if state == StateHalfOpen && cb.counts.Requests >= cb.maxRequests {
// 避免海量请求对处于恢复服务的影响,这里有一个限流的操作,避免请求数超过最大请求数
return generation, ErrTooManyRequests
}
// 统计状态
cb.counts.onRequest()
return generation, nil
}
CircuitBreaker.afterRequest
func (cb *CircuitBreaker) afterRequest(before uint64, success bool) {
cb.mutex.Lock()
defer cb.mutex.Unlock()
// 重新获取状态
now := time.Now()
state, generation := cb.currentState(now)
// 如果前后状态不一致,则不计数
if generation != before {
return
}
// 根据状态计数
if success {
cb.onSuccess(state, now)
} else {
cb.onFailure(state, now)
}
}
CircuitBreaker.currentState
func (cb *CircuitBreaker) currentState(now time.Time) (State, uint64) {
switch cb.state {
case StateClosed:
// 如果当前当前是closed状态,并且有设置expiry,则递增Generation到新一轮统计计数
if !cb.expiry.IsZero() && cb.expiry.Before(now) {
cb.toNewGeneration(now)
}
case StateOpen:
// 如果是Open状态,并且超时,则尝试到半打开状态
if cb.expiry.Before(now) {
cb.setState(StateHalfOpen, now)
}
}
return cb.state, cb.generation
}
CircuitBreaker.toNewgeneration
func (cb *CircuitBreaker) toNewGeneration(now time.Time) {
// 递增generation, 清除状态
cb.generation++
cb.counts.clear()
// 设置超时时间
var zero time.Time
switch cb.state {
case StateClosed:
if cb.interval == 0 {
cb.expiry = zero
} else {
cb.expiry = now.Add(cb.interval)
}
case StateOpen:
cb.expiry = now.Add(cb.timeout)
default: // StateHalfOpen
cb.expiry = zero
}
}
总结
断路器黄金链路
- beforeRequest :完成当前请求是否可以执行请求,状态超时切换,同时返回当前的genenration
- req: 执行请求
- afterRequest: 完成请求状态统计,决定状态切换
断路器的优缺点
断路器比较适合针对远程服务或者第三方服务的调用,如果该操作极有可能会失败,则断路器可以尽可能的减小失败对应用的影响,避免资源浪费
但缺点也显而易见,断路器本身相当于一层代理,在应用程序执行进行统计和控制,本身就有一定的资源消耗,同时内部基于synx.Mutex锁来实现,高并发下肯定会有锁争用问题,可能需要根据业务来使用多个断路器,来分散这种锁争用,同时应该避免在断路器req函数内,去执行重试和过长时间的超时等待,因为断路器核心是快速失败
更多文章可以访问http://www.sreguide.com/

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
千万并发不是梦:TCPBurn并发测试
王斌 TCPCopy和Cetus开源主要作者 知识星球ID: 47406575,提供TCP经典案例分析课程 微信公众号:得一技术 “黑云压城城欲摧,甲光向日金鳞开”,唐朝诗人李贺字面上描绘了黑云压城的自然景象,但实际描述的是敌军攻占城池的人马众多,来势汹汹,但是守城士兵依旧严阵以待,斗志昂扬。这种攻守的战争场面,和服务器的高并发压测十分相似。待压测的服务器应用类似于待攻占的城池,而测试软件需要做的就是构造出成千上百万的士兵,来攻占服务器的应用。 如何构造出成千上百万的攻城士兵,是高并发测试的关键。而传统压力测试工具设计的时候并不是针对高并发测试设计的。针对高并发场景,传统压力测试工具往往自身是性能瓶颈。为适应高并发趋势,我们设计了TCPBurn,用于无状态协议的高并发压力测试,瞬间可以创造出任意多的攻城的精兵猛将。 我们以消息推送服务为例,来模拟海量用户并发场景。千万并发连接测试相关的公开资料很少,据说要达到C10M(千万连接)并发,需要从根本上解决内核自身的问题。我们的实验希望验证linux服务器环境下Nginx能否承受千万连接的考验。 1、软硬件配置 为进行千万并发...
-
下一篇
大话文本检测经典模型:SegLink
在自然场景中,例如灯箱广告牌、产品包装盒、商标等,要检测出其中的文字会面临着各种复杂的情况,例如角度倾斜、变形等情况,这时就需要使用基于深度学习的方法进行文字检测。在之前的文章中,介绍了基于卷积神经网络和循环神经网络的CTPN文本检测方法(见文章:大话文本检测经典模型 CTPN),该方法能在自然场景下较好地实现对文字的检测,但在CTPN中给出的文本检测效果是基于水平方向的,对于非水平的文本检测效果并不好,而在自然场景中,很多的文本信息都是带有一定的旋转角度的,例如用手机拍街道上的指示牌,如下图。如果文本检测的结果只有水平方向的,没有带角度信息,那么下图指示牌检测出来的就是红色框结果,而其实绿色框才是理想的检测目标,可见检测的结果误差太大。 那要怎样才能实现对各种角度的灵活检测呢?一个最直接的思路就是让模型不仅能学习和输出边框的位置(x, y, w, h),还要能输出一个文本框的旋转角度参数θ。本文要介绍的文本检测模型SegLink,便是采用了这个思路,也即SegLink检测模型能检测有旋转角度的文本,如下图: 一、SegLink模型的主要思想 SegLink模型的检测过程主要如下: ...
相关文章
文章评论
共有0条评论来说两句吧...