【源码阅读】Sony的go breaker熔断器源码探究

文章目录

  • 背景
  • 源码分析
  • 总结

背景

在微服务时代,服务和服务之间调用、跨部门调用都是很常见的事,但这些调用都存在很多不确定因素,如核心服务A依赖的部门B服务挂掉了,那么A本身的功能将会受到直接的影响,而这些都会影响着我们本身为用户提供的产品功能表现,因此,做好服务调用的熔断降级措施是非常有必要的。在golang开发中,我们经常都会使用到一个组件gobreaker,用非常少量的代码实现了服务熔断功能,下面我们将对gobreaker的源代码进行分析。

源码分析

源码地址:https://github.com/sony/gobreaker
熔断器设计:gobreaker熔断器是否生效主要是根据状态来的,熔断器会在Closed、HalfOpen、Open三种状态中转换。

  • 初始状态是Closed,这个状态下熔断器会放行所有请求。
  • 当满足熔断条件(如达到一定数量的错误计数)时,熔断器进入Open 状态,不能再放行请求,对于所有的请求都直接返回熔断器本身定义的熔断错误。
  • 熔断器在Open状态经过一段Interval时间后,自动进入Half-Open状态,在此期间,会根据HalfOpen的策略放行请求,并记录请求的执行结果状态。如果放行的这些请求最终计数满足闭合状态,熔断器将进入Closed状态,继续放行请求,反之则会自动进入Open状态。
         Closed /    \Half-Open <--> Open

1、熔断器配置

type Settings struct {Name          string // 熔断器名称MaxRequests   uint32 // 最大请求数,熔断器半开状态放行的最大请求数Interval      time.Duration // 计数周期,类似于滑动窗口的窗口大小,用于定期清理countsTimeout       time.Duration // 熔断器进入Open状态后,经过timeout时间进入HalfOpen状态ReadyToTrip   func(counts Counts) bool // 熔断器的计数策略,计数是记在counts中的,如连续出错达到一定数量后,该方法将会返回true,此时熔断器将进入Open状态OnStateChange func(name string, from State, to State) // 熔断器状态发生变化时候的回调方法,参数表示熔断器从一个状态转变到另一个状态IsSuccessful  func(err error) bool // 熔断器的计数方法,调用发生错误时,通过该方法进行计数,累积到ReadyToTrip中的策略触发后,熔断器将进入Open状态
}

官方解释:
在这里插入图片描述
2、熔断器计数

type Counts struct {Requests             uint32 // 总的请求数量TotalSuccesses       uint32 // 总的成功数TotalFailures        uint32 // 总的失败数ConsecutiveSuccesses uint32 // 连续成功数ConsecutiveFailures  uint32 // 连续失败数
}

此外,需要注意的是,熔断器的计数是发生在范围: Generation周期内的。
3、熔断器

// CircuitBreaker is a state machine to prevent sending requests that are likely to fail.
type CircuitBreaker struct {name          string // 熔断器名称maxRequests   uint32 // 熔断器半开状态的最大请求数interval      time.Duration // 熔断器处于闭合状态时的计数周期,每个周期开始时会清理countstimeout       time.Duration // 熔断器从open状态到halfopen状态的时间readyToTrip   func(counts Counts) bool // 熔断器计数策略isSuccessful  func(err error) bool // 熔断器计数方法onStateChange func(name string, from State, to State) // 熔断器状态改变回调方法mutex      sync.Mutexstate      State // 熔断器当前状态:Open、Closed、HalfOpengeneration uint64 // 每一个时间周期(Interval)的计数(count)状态称为一个generation。counts     Counts // 当前generation的计数统计,切换generation时候会清空countsexpiry     time.Time // 过期时间
}

熔断器的核心方法Execute :

// Execute runs the given request if the CircuitBreaker accepts it.
// Execute returns an error instantly if the CircuitBreaker rejects the request.
// Otherwise, Execute returns the result of the request.
// If a panic occurs in the request, the CircuitBreaker handles it as an error
// and causes the same panic again.
func (cb *CircuitBreaker) Execute(req func() (interface{}, error)) (interface{}, error) {generation, err := cb.beforeRequest()if err != nil {return nil, err}defer func() {e := recover()if e != nil {cb.afterRequest(generation, false)panic(e)}}()result, err := req()cb.afterRequest(generation, cb.isSuccessful(err))return result, err
}

该方法主要是几个步骤,beforeRequest()、 执行请求req()和afterRequest(),其中,req是我们真正需要执行的业务方法,比如为A对B的一次http、rpc调用等。

  • beforeRequest()
func (cb *CircuitBreaker) beforeRequest() (uint64, error) {cb.mutex.Lock()defer cb.mutex.Unlock()now := time.Now()state, generation := cb.currentState(now) // 获取当前熔断器的状态state和计数周期generationif state == StateOpen { // 如果熔断器处于Open状态,那么将会直接返回熔断错误,并将generation返回return generation, ErrOpenState//如果熔断器处于半开状态,且请求数目已经超过了最大请求数,那么也将会返回错误} else if state == StateHalfOpen && cb.counts.Requests >= cb.maxRequests {return generation, ErrTooManyRequests}// 熔断器处于闭合状态,正常放行请求,计数cb.counts.onRequest() // counts计数return generation, nil
}
  • 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 { // 如果此时的计数周期和before阶段返回的不一致,那么将直接返回return}// 否则,根据调用设置的响应,对counts的成功或者失败请求进行计数if success {cb.onSuccess(state, now)} else {cb.onFailure(state, now)}
}
// 根据熔断器状态计数成功的请求:
// 1、熔断器处于闭合状态,则直接计数success
// 2、熔断器处于半开状态,则计数成功,且如果连续成功的数量超过了最大请求数,那么熔断器将进入闭合状态,计数进入下一个周期
func (cb *CircuitBreaker) onSuccess(state State, now time.Time) {switch state {case StateClosed: cb.counts.onSuccess()case StateHalfOpen:cb.counts.onSuccess()if cb.counts.ConsecutiveSuccesses >= cb.maxRequests {cb.setState(StateClosed, now)}}
}
// 根据熔断器状态计数成功的请求:
// 1、熔断器处于闭合状态,计数失败,且如果当前计数周期的统计结果达到了熔断的条件,那么熔断器将被设置为打开状态。
// 2、如果熔断器处于半开状态,此时又发生了错误,那么熔断器直接进入打开状态
func (cb *CircuitBreaker) onFailure(state State, now time.Time) {switch state {case StateClosed:cb.counts.onFailure()if cb.readyToTrip(cb.counts) {cb.setState(StateOpen, now)}case StateHalfOpen:cb.setState(StateOpen, now)}
}
  • currentState()
    该方法作用主要是根据熔断器的状态以及计数过期时间expiry等,来判断是否需要进入到下一个generation(计数周期)中,currentState作用当然就是返回当前的generation和熔断器状态了。
func (cb *CircuitBreaker) currentState(now time.Time) (State, uint64) {switch cb.state {case StateClosed: // 当熔断器处于闭合状态时,如果过期时间到,则进入到下一个计数周期中,产生一个新的generationif !cb.expiry.IsZero() && cb.expiry.Before(now) {cb.toNewGeneration(now) // 产生新的generation}case StateOpen: // 如果熔断器处于打开状态,且过期时间expiry到,那么熔断器将进入半开状态if cb.expiry.Before(now) {cb.setState(StateHalfOpen, now) }}return cb.state, cb.generation
}func (cb *CircuitBreaker) setState(state State, now time.Time) {if cb.state == state {return}prev := cb.statecb.state = statecb.toNewGeneration(now)if cb.onStateChange != nil {cb.onStateChange(cb.name, prev, state)}
}
// 生成新的generation。 主要是清空counts和设置expiry(过期时间)。
// 当状态为Closed时expiry为Closed的过期时间(当前时间 + interval),
// 当状态为Open时expiry为Open的过期时间(当前时间 + timeout)
func (cb *CircuitBreaker) toNewGeneration(now time.Time) {cb.generation++ // 计数周期++cb.counts.clear() // 清空counts统计// 根据熔断器状态state、闭合状态的计数周期interval和// 熔断器从Open恢复到HalfOpen的超时时间timeout来重置过期时间var zero time.Timeswitch 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 }
}

总结

Sony的gobreaker通过短短几百行代码就实现了一个功能强大的熔断器,其中的原理解释来源微软Circuit Breaker Pattern,整体上,gobreaker的设计思想主要体现在几个函数中:

  • beforeRequest()
    该函数主要作用是根据熔断器的计数状态,判断是否放行请求,计数或达到切换新条件刚切换。
    1、判断熔断器是否Closed,如是,放行所有请求。并且会在调用toNewGeneration()判断时间是否达到Interval周期,从而清空计数,进入新的计数周期。
    2、如果是Open状态,返回ErrOpenState,不放行所有请求。同样判断周期时间,到达则 同样调用 toNewGeneration()
    3、如果是HalfOpen状态,则判断是否已放行MaxRequests个请求,如未达到则放行请求;否则返回:ErrTooManyRequests。

beforeRequest方法中,一旦放行请求,就会对当前的周期的请求计数加1。

  • afterRequest()
    该函数核心内容很简单,主要就是对before阶段放行的请求进行统计,放行请求执行成功/失败都会调用该方法进行计数,达到条件则切换状态。
    1、与beforeRequest一样,会调用公共函数 currentState方法;在currentState中会根据熔断器状态和来判断如何产生一个新的计数周期;如果熔断器处于闭合状态,则会根据expiry过期时间来判断熔断器是否进入先前的一个计数周期,如果是则调用toNewGeneration来产生一个新的计数周期,并且清空计数统计。如果熔断器处于断开状态,并且达到超时时间,那么将会改变熔断器的状态为半开状态,并且调用toNewGeneration进入下一个计数周期。
    2、注意:在after中进入新的计数周期并是好事,因为这往往意味着执行业务请求req花费了更多的时间,导致before阶段和after阶段不在一个计数周期内,因此,这种情况熔断器将不会计数。也就是说,如果req耗时大于Interval,熔断器每次after时都会进入新的计数周期,上一个周期的统计就清空了,熔断器也就没有太大价值了。

gobreaker的核心代码中使用了一个generation的概念,每一个时间周期(Interval)的计数(count)状态称为一个generation。这个概念保证了熔断器after阶段的计数和before的计数是在同一个计数周期内。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/48460.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GaussianPro使用笔记

1. 介绍 GaussianPro: 3D Gaussian Splatting with Progressive Propagation 3D高斯分布(3DGS)最近以其高保真度和效率彻底改变了神经渲染领域。然而&#xff0c;3DGS在很大程度上依赖于运动结构&#xff08;SfM&#xff09;技术生成的初始化点云。当处理不可避免地包含无纹理…

<数据集>手势识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;2400张 标注数量(xml文件个数)&#xff1a;2400 标注数量(txt文件个数)&#xff1a;2400 标注类别数&#xff1a;5 标注类别名称&#xff1a;[fist, no_gesture, like, ok, palm] 序号类别名称图片数框数1fist597…

Pycharm 和虚拟环境的那些事?

背景: 我既有 python 又有Anaconda Pycharm新建虚拟环境: 只说两种方式 通过Virualenv Environment新建: 这里我们勾选上 Make available to all projects ,之后点击&#x1f197; 然后可以发现只有非常少的包,因为没有勾选继承 编译器的包 创建的虚拟环境一般目录如下&…

Sparse4D-v3:稀疏感知的性能优化及端到端拓展

极致的感知性能与极简的感知pipeline一直是牵引我们持续向前的目标。为了实现该目标&#xff0c;打造一个性能优异的端到端感知模型是重中之重&#xff0c;充分发挥深度神经网络数据闭环的作用&#xff0c;才能打破当前感知系统的性能上限&#xff0c;解决更多的corner case&am…

下载最新版Anaconda、安装、更换源、配置虚拟环境并在vscode中使用

文章目录 进入官网进入下载页安装更换源配置虚拟环境env安装包requests在vscode中使用虚拟环境 进入官网 https://repo.anaconda.com/ 或进入清华大学下载 https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/ 进入下载页 安装 更换源 查看已经存在的镜像源 bash cond…

物联网在养殖业领域的应用——案例分析

作者主页: 知孤云出岫 目录 作者主页:物联网在养殖业领域的应用——案例分析背景技术架构硬件设置连接多种传感器到微控制器 代码实现1. Arduino代码&#xff1a;采集多种传感器数据并上传到Thingspeak2. Python代码&#xff1a;从Thingspeak获取数据并进行综合分析和可视化 …

大模型学习笔记十二:AI产品部署

文章目录 一、如何选择GPU和云服务器厂商&#xff0c;追求最高性价比1&#xff09;根据场景选择GPU2&#xff09;训练或微调所需显卡&#xff08;以Falcon为例子&#xff09;3&#xff09;服务器价格计算器 二、全球大模型了解1&#xff09;llm所有模型2&#xff09;模型综合排…

WSL2 Centos7 Docker服务启动失败怎么办?

wsl 安装的CentOS7镜像,安装了Docker之后,发现用systemctl start docker 无法将docker启动起来。 解决办法 1、编辑文件 vim /usr/lib/systemd/system/docker.service将13行注释掉,然后在下面新增14行的内容。然后保存退出。 2、再次验证 可以发现,我们已经可以正常通过s…

offer题目51:数组中的逆序对

题目描述&#xff1a;在数组中的两个数字&#xff0c;如果前面一个数字大于后面的数字&#xff0c;则这两个数字组成一个逆序对。输入一个数组&#xff0c;求出这个数组中的逆序对的总数。例如&#xff0c;在数组{7,5,6,4}中&#xff0c;一共存在5个逆序对&#xff0c;分别是(7…

给Wordpress添加评分功能到评论表单

今天要 给你的 Wordpress 添加评分功能到评论表单 吗&#xff1f; 评分功能效果图 什么类型的网站需要评分&#xff1f; 资源站教程站其他&#xff0c;我也没想到。。。 但我这个网站&#xff0c;因为是电影类的网站&#xff0c;好像还是有点需要的&#xff0c;所以&#xf…

针对汽车应用而设计的SCT4026D、SCT4062K、SCT3105K、SCT3080A、SCT3060A全新系列碳化硅 (SiC) MOSFET

全新系列碳化硅 (SiC) MOSFET SCT4026DWAHRTL SCT4062KWAHRTL SCT3105KRC15 SCT3080ALHRC11 SCT3080ARC15 SCT3060ARC15 ——明佳达 AEC-Q101 SiC功率MOSFETs是汽车和开关电源的理想选择。SiC功率MOSFETs可以提高开关频率&#xff0c;减少所需的电容、电抗器和其他元件的体积…

【RAG探索第4讲】KG+RAG丨基于知识图谱优化大型语言模型方法

原文链接&#xff1a;【RAG探索第4讲】KGRAG丨基于生物医学知识图谱优化的大型语言模型提示生成方法 一、现有问题&#xff1a; LLMs在处理特定领域或高度专业化查询时缺乏专业知识&#xff0c;导致回答不够准确和可靠。 LLMs可能会产生事实错误&#xff08;即幻觉&#xff0…

【计算机视觉】siamfc论文复现

什么是目标跟踪 使用视频序列第一帧的图像(包括bounding box的位置)&#xff0c;来找出目标出现在后序帧位置的一种方法。 什么是孪生网络结构 孪生网络结构其思想是将一个训练样本(已知类别)和一个测试样本(未知类别)输入到两个CNN(这两个CNN往往是权值共享的)中&#xff0…

深入理解PHP基础【代码审计实战指南】

文章目录 基础语法单双引号的区别前后端分离数据类型PHP常量函数var_dump函数count函数print_r函数**readfile&#xff08;&#xff09;函数****file_get_contents()函数****file_put_contents()函数**header函数fopen函数fread 函数rename函数copy&#xff08;&#xff09;函数…

OCR识别采购单小程序管理助手

千呼新零售2.0系统是零售行业连锁店一体化收银系统&#xff0c;包括线下收银线上商城连锁店管理ERP管理商品管理供应商管理会员营销等功能为一体&#xff0c;线上线下数据全部打通。 适用于商超、便利店、水果、生鲜、母婴、服装、零食、百货、宠物等连锁店使用。 详细介绍请…

Qt开发网络嗅探器01

引言 随着互联网的快速发展和普及&#xff0c;人们对网络性能、安全和管理的需求日益增 长。在复杂的网络环境中&#xff0c;了解和监控网络中的数据流量、安全事件和性能 问题变得至关重要。为了满足这些需求&#xff0c;网络嗅探器作为一种重要的工具被 广泛应用。 网络嗅探…

【Godot4.2】SVGParser - SVG解析器函数库

概述 这是一个基于GDScript内置XMLParser编写的简易SVG文件解析函数库。 目的就是可以将SVG文件解析为GDSCript可以处理的字典或DOM形式&#xff0c;方便SVG渲染和编辑。 目前还只是一个简易实现版本。还需要一些改进。 函数库源码 # # 名称&#xff1a;SVGParser # 类型…

AI算法23-决策树ID3算法Iterative Dichotomiser 3 | ID3

目录 决策树ID3算法概述 决策树ID3算法简介 决策树ID3算法的原理 决策树ID3算法的核心 决策树ID3算法的本质 决策树ID3算法的基本流程 决策树ID3算法计算过程 步骤1 步骤2 步骤3 决策树ID3算法的代码实现 决策树ID3算法的优缺点 优点 缺点 决策树ID3算法的应用场…

ue5笔记

1 点光源 聚光源 矩形光源 参数比较好理解 &#xff08;窗口里面&#xff09;环境光混合器&#xff1a;快速创造关于环境光的组件 大气光源&#xff1a;太阳光&#xff0c;定向光源 天空大气&#xff1a;蓝色的天空和大气 高度雾&#xff1a;大气下面的高度感的雾气 体积…

【HarmonyOS】HarmonyOS NEXT学习日记:五、交互与状态管理

【HarmonyOS】HarmonyOS NEXT学习日记&#xff1a;五、交互与状态管理 在之前我们已经学习了页面布局相关的知识&#xff0c;绘制静态页面已经问题不大。那么今天来学习一下如何让页面动起来、并且结合所学完成一个代码实例。 交互 如果是为移动端开发应用&#xff0c;那么交…