十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
本篇文章给大家分享的是有关golang中怎么实现一个熔断器,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。
阎良网站建设公司创新互联,阎良网站设计制作,有大型网站制作公司丰富经验。已为阎良超过千家提供企业网站建设服务。企业网站搭建\成都外贸网站建设公司要多少钱,请找那个售后服务好的阎良做网站的公司定做!
熔断器名称 | hystrix | go-breaker |
---|---|---|
滑动窗口计数 | 支持 | 不支持 |
限流 | 支持 | 不支持 |
阻塞读 | 是 | 是 |
对半开启的处理 | 滑动计数器+阈值 | 连续成功则转移到close |
对监控的支持 | 支持metric采集 | 不支持 |
降级处理 | 支持hook | 支持hook |
解决并发尖刺 | 不支持 | 支持 |
代码结构易读性 | 稍差 | 较好 |
我们一一展开来讲
计数模块是熔断器的核心,网上有针对计数器的大篇幅的分析针对这里引用知乎上一位大佬的比较类型的文章,根据最后的比较我们选择滑动窗口的算法来完成计数需求。 在hystrix的设计中,滑动窗口的比较重要的是写入时刻和读取时刻,因为我们很容易想到在这两个环节涉及到对一块内存并发读写的问题,首先我们不建议采用go-breaker的全加锁(读写都加锁)的设计,因为锁在发生竞争时会挂起线程,从而降低了CPU的使用率和共享内存总线上的同步通信量,那么我们参考hystrix,采用异步提交的方法,也就是将结果放入一个队列中,不断消费这个队列,这么做有几点好处
消息串行化,减少写入读取数据不必要竞争 在数据生产层--->数据存储层中间构造出中间层,方便进行监控统计收集等操作 方便控制消息的消费情况
在实现上采用channel的数据结构,消费有高效保证。但是事物都有两面性,这种设计带来的问题有
滑动窗口统计需要访问当前窗口內所有数据 串行化没有将统计性能发挥最大(虽然在计数丰方面表现很快) 业务要允许流量尖刺的出现(假设没有加限流)
其中2,3点经过调研都在业务允许范围內,且针对第三点我们可以增加限流策略来完善这一点。
hystrix天生限流,所有请求先过令牌桶然后进入熔断统计,go-breaker还没有这方面支持,在限流这里我们怀疑要不要在一起做(毕竟熔断是熔断,限流是限流),所以做了另一个方案,在半开启时进行限流放行请求,这样比较符合半开启时的请求通过策略,同时进行统计,限流策略采用退化版本令牌桶,方法如下:
type limitPoolManager struct { max int tickets chan *struct{} lock *sync.RWMutex } /* 方法返回一个限流器 */ func NewLimitPoolManager(max int) *limitPoolManager { lpm := new(limitPoolManager) tickets := make(chan *struct{}, max) for i := 0; i < max; i++ { tickets <- &struct{}{} } lpm.max = max lpm.tickets = tickets lpm.lock = &sync.RWMutex{} return lpm } /* 方法填充限流器所有令牌 */ func (this *limitPoolManager) ReturnAll() { this.lock.Lock() defer this.lock.Unlock() if len(this.tickets) == 0 { for i := 0; i < this.max; i++ { this.tickets <- &struct{}{} } } } /* 方法返回一个令牌,得到令牌返回true,令牌用完后返回false */ func (this *limitPoolManager) GetTicket() bool { this.lock.RLock() defer this.lock.RUnlock() select { case <-this.tickets: return true default: return false } } /* 方法返回剩余令牌数 */ func (this *limitPoolManager) GetRemaind() int { this.lock.RLock() defer this.lock.RUnlock() return len(this.tickets) }
因为串行化设计所以在每次收失败请求时可以对窗口內数据进行错误率转化。避免hystrix与go-breaker的锁争抢
以上+本节主流程基本完结,现梳理整个流程明确half-open时处理:
当熔断器为close时。只有当出现错误请求时,才进行错误率统计,统计过阈值则状态转移到open,正确请求则正常计数。 当熔断器为half-open时,仅当令牌桶中还有令牌时接收请求否则熔断。令牌桶中还有令牌时,出现错误请求则更新熔断休眠时间并返回所有令牌等待下次半开启,正常请求则进入半开启时统计达到阈值则状态转移到close。 当熔断器为open时,仅当熔断休眠时间小于当前时间时,当熔断器状态转移到half-open,可以进行第二条,否则执行熔断
首先判断是否是半开启状态
switch this.counter.GetStatus() { case STATE_OPEN: if this.cycleTime < time.Now().Local().Unix() { return OPEN_TO_HALF_ERROR } return BREAKER_OPEN_ERROR }
其次如果是半开启状态则取令牌,取到令牌则执行请求,进入熔断时计数,否则直接熔断
/*取令牌*/ if !this.lpm.GetTicket() { this.safelCalllback(fallback, BREAKER_OPEN_ERROR) return nil } /*执行方法*/ runErr := run() if runErr != nil { this.fail() this.safelCalllback(fallback, runErr) return runErr } this.success() return nil
流量尖刺的削峰伴随着限流的逻辑,所以可以在请求到达时优先进入令牌桶
提供hook函数,在限流或者执行失败时可以提供降级或者回掉
/* 执行函数 */ type runFunc func() error /* 回调函数 */ type fallbackFunc func(error) /* Do方法结合熔断策略执行run函数 其中参数包括:上下文ctx,策略名name,将要执行方法run,以及回调函数fallback.其中ctx,name,run必传 run函数的错误会直接同步返回,回调函数fallback接收除了run错误以外还会接收熔断时错误,调用方如果需要降级可在fallback中自己判断 */ func Do(ctx context.Context, name string, run runFunc, fallback fallbackFunc) error { ........ }
以上就是golang中怎么实现一个熔断器,小编相信有部分知识点可能是我们日常工作会见到或用到的。希望你能通过这篇文章学到更多知识。更多详情敬请关注创新互联行业资讯频道。