当前位置: 首页 > 科技观察

GolangFuse

时间:2023-03-15 14:47:43 科技观察

Go项目的实现使用熔断技术来提高系统的容错能力。本文介绍GoCircuitBreaker及其使用。保险丝就像保险丝。当我们依赖的服务出现问题时,可以及时进行容错。一方面可以降低依赖服务对自身访问的依赖,防止雪崩效应;另一方面可以减少请求的频率,使上游服务尽快恢复。保险丝也被广泛使用。除了在我们的应用程序中请求服务时使用熔断器外,在Web网关和微服务中也有非常广泛的应用。本文将从源码的角度学习实现sony开源的fusegithub/sony/gobreaker。(代码注释可查看github/lpflpf/gobreaker)断路器模式gobreaker是基于本书《微软云设计模式》中断路器模式的Golang实现。有索尼公司开源,目前star数1.2K。有很多用户。下面是mode定义的状态机:熔断器有三种状态,有四种状态转换情况:三种状态:熔断器关闭,服务正常访问,熔断器打开,服务异常,熔断器半开,部分请求受限流式访问有四种状态转换:fuse-closed状态,当出现故障,满足一定条件时,会直接转为fuse-open状态。在熔断器打开状态下,如果超过指定时间,会进入半开状态,验证当前服务是否可用。在熔断器的半开状态下,如果出现故障,会再次进入闭合状态。熔断器半开后,所有请求(限额)成功,熔断器关闭。所有的请求都会被正常访问。gobreaker的实现Gobreaker是在上述状态机的基础上实现的熔断器。定义熔断器类型CircuitBreakerstruct{namestringmaxRequestsuint32//最大请求次数(半开状态会限流)intervaltime.Duration//统计周期timeouttime.Duration//进入熔断器后超时时间readyToTripfunc(countsCounts)bool//判断是否打开由计数保险丝。需要自定义onStateChangefunc(namestring,fromState,toState)//state修改时的钩子函数mutexsync.Mutex//Mutex,后面数据的更新需要锁定stateState//记录当前状态生成uint64//哪个标记属于CyclecountsCounts//计数器,计数成功、失败、连续成功、连续失败等,用于决定是否进入熔断器expirytime.Time//进入下一个循环的时间}其中,以下参数为我们可定制:MaxRequests:最大请求数。当所有请求在最大请求数下都正常时,熔断器将关闭间隔:一个正常的统计周期。如果为0,则每次清零计数timeout:进入断路器后,再次请求的时间readyToTrip:判断断路器生效1阶段的钩子函数;①请求前判断;②服务请求的执行;③请求后状态和计数的更新//fuse调用func(cb*CircuitBreaker)Execute(reqfunc()(interface{},error))(interface{},error){//①请求前的判断生成,err:=cb.beforeRequest()iferr!=nil{returnnil,err}deferfunc(){e:=recover()ife!=nil{//③捕获paniccb.afterRequest(generation,false)panic(e)}}()//②请求并执行结果,err:=req()//③更新计数cb.afterRequest(generation,err==nil)returnresult,err}在请求前的判断操作request之前,会判断fuse的当前状态.如果断路器打开,请求将不会继续。如果断路器半开,并且已经达到最大请求阈值,请求将不会继续。func(cb*CircuitBreaker)beforeRequest()(uint64,error){cb.mutex.Lock()defercb.mutex.Unlock()now:=time.Now()state,generation:=cb.currentState(now)ifstate==StateOpen{//熔断器打开,直接返回returgengeneration,ErrOpenState}elseifstate==StateHalfOpen&&cb.counts.Requests>=cb.maxRequests{//如果是半开状态,请求次数太多很多,直接返回returngeneration,ErrTooManyRequests}cb.counts.onRequest()returngeneration,nil}根据当前状态计算当前状态。如果当前状态为开启,则判断是否超时,超时可将状态改为半开;如果当前状态为关闭,则通过循环判断是否进入下一个循环。func(cb*CircuitBreaker)currentState(nowtime.Time)(State,uint64){switchcb.state{caseStateClosed:if!cb.expiry.IsZero()&&cb.expiry.Before(now){//是否需要输入nextcountCyclecb.toNewGeneration(now)}caseStateOpen:ifcb.expiry.Before(now){//熔断器由开变为半开cb.setState(StateHalfOpen,now)}}returncb.state,cb.generation}cyclelength设置也是基于当前状态。如果当前正常(熔断器闭合),则设置为一个间隔周期;如果当前熔断器是导通的,则设置为超时时间(超时后可改为半开状态)。请求后的处理操作每次请求后,会根据请求结果是否成功进行断路器计数。func(cb*CircuitBreaker)afterRequest(beforeuint64,successbool){cb.mutex.Lock()defercb.mutex.Unlock()now:=time.Now()//如果不在一个循环中,不再统计状态,生成:=cb.currentState(now)ifgeneration!=before{return}ifsuccess{cb.onSuccess(state,now)}else{cb.onFailure(state,now)}}if处于半开状态:如果请求成功,然后会判断当前连续成功请求数大于等于maxRequests,则状态可以从半开状态转为关闭状态。如果在半开状态下请求失败,则直接将半开状态转为打开状态。如果它处于关闭状态:如果请求成功,则更新计数。如果请求失败,调用readyToTrip判断状态是否需要从关闭状态转为打开状态。总结对于一些远程或者第三方不可靠服务的频繁请求,失败的概率还是很高的。使用熔断器的好处是我们自己的服务不会被这些不可靠的服务拖累,造成雪崩。因为在熔断器中,不仅会维护很多统计数据,还要用互斥锁进行资源隔离,成本也会很多。在半开状态下,可能请求过多。这是因为在半开状态下,连续请求成功的次数没有达到最大请求值。所以对于请求时间过长(但相对频繁)的服务,断路器可能会导致大量的toomanyrequests错误