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

news2024/11/15 21:19:19

文章目录

  • 背景
  • 源码分析
  • 总结

背景

在微服务时代,服务和服务之间调用、跨部门调用都是很常见的事,但这些调用都存在很多不确定因素,如核心服务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 // 计数周期,类似于滑动窗口的窗口大小,用于定期清理counts
	Timeout       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 // 熔断器处于闭合状态时的计数周期,每个周期开始时会清理counts
	timeout       time.Duration // 熔断器从open状态到halfopen状态的时间
	readyToTrip   func(counts Counts) bool // 熔断器计数策略
	isSuccessful  func(err error) bool // 熔断器计数方法
	onStateChange func(name string, from State, to State) // 熔断器状态改变回调方法

	mutex      sync.Mutex
	state      State // 熔断器当前状态:Open、Closed、HalfOpen
	generation uint64 // 每一个时间周期(Interval)的计数(count)状态称为一个generation。
	counts     Counts // 当前generation的计数统计,切换generation时候会清空counts
	expiry     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和计数周期generation

	if 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: // 当熔断器处于闭合状态时,如果过期时间到,则进入到下一个计数周期中,产生一个新的generation
		if !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.state
	cb.state = state

	cb.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.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 
	}
}

总结

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.coloradmin.cn/o/1943036.html

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

相关文章

vue this.$refs 动态拼接

业务需要&#xff0c;refs是不固定的 <vxe-grid refgridWarehouse v-bind"gridWarehouseOptions" v-if"tableHeight" :height"tableHeight":expand-config"{iconOpen: vxe-icon-square-minus, iconClose: vxe-icon-square-plus}"c…

(Qt) 文件读写基础

文章目录 &#x1f5c2;️前言&#x1f4c4;ref&#x1f4c4;访问标记&#x1f5c3;️enum 标记 &#x1f5c2;️Code&#x1f4c4;demo&#x1f4c4;分点讲解&#x1f5c3;️继承体系&#x1f5c3;️打开/关闭&#x1f5c3;️写&#x1f5c3;️读 &#x1f5c2;️END&#x1f…

2024年,人工智能行业哪些证书权威?

人工智能领域颁发的证书有很多&#xff0c;但哪些更权威呢&#xff1f;看颁发证书的单位&#xff01; 工信部电子标准院的人工智能从业人员认证证书是由工业和信息化部电子工业标准化研究院&#xff08;以下简称“电子标准院”&#xff09;颁发&#xff0c;旨在评价和认证人工…

【嵌入式开发之标准I/O】文件I/O的基本概念,打开、关闭、定位函数及实例

文件I/O和标准I/O 什么是文件I/O?什么是标准I/O? 文件I/O&#xff1a;文件I/O又称系统IO&#xff0c;系统调用&#xff0c;称之为不带缓存的IO&#xff08;unbuffered I/O)。是操作系统提供的API接口函数。不带缓存指的是每个read&#xff0c;write都调用内核中的一个系统调…

【HarmonyOS4学习笔记】《HarmonyOS4+NEXT星河版入门到企业级实战教程》课程学习笔记(二十三)

课程地址&#xff1a; 黑马程序员HarmonyOS4NEXT星河版入门到企业级实战教程&#xff0c;一套精通鸿蒙应用开发 &#xff08;本篇笔记对应课程第 33 节&#xff09; P33《32.通知-进度条通知》 下载按钮对应的逻辑&#xff1a; 取消按钮对应的逻辑&#xff1a; 暂停按钮对应的…

Java | Leetcode Java题解之第274题H指数

题目&#xff1a; 题解&#xff1a; class Solution {public int hIndex(int[] citations) {int left0,rightcitations.length;int mid0,cnt0;while(left<right){// 1 防止死循环mid(leftright1)>>1;cnt0;for(int i0;i<citations.length;i){if(citations[i]>mi…

【Django】在anaconda虚拟环境中创建Django项目

文章目录 进入工作目录创建django项目及进入vscode(打开项目目录)选择解析器 进入工作目录 cd C:\WF\developer\djangodemo创建django项目及进入vscode(打开项目目录) django-admin startproject antproject选择解析器 ctrlshiftP打开命令面板

【杰理蓝牙开发】AC695x VM接口原理与使用(1)

本文主要记录 杰理蓝牙VM接口的使用,包括原理的介绍和API接口的使用。 【杰理蓝牙开发】AC695x VM接口原理与使用(1) 0. 个人简介 && 授权须知1. 系统 flash 区域划分1.1 系统区域划分1.2 在配置文件中配置flash区域2. 系统配置项读写接口2.1 读接口:2.2 写接口:2…

MATLAB实验五:MATLAB数据分析

1. 某线路上不同时间对应的电压如下表所示&#xff1a; 1&#xff09;用 3 次多项式拟合(polyfit)该实验曲线&#xff0c;要求绘制 2 原始采样 点&#xff0c;并在 1~8 范围内&#xff0c;使用时间间隔为 0.2 的数据绘制拟合曲线。 建立一个脚本文件&#xff1a;text5_1.m 如下…

【Hot100】LeetCode—279. 完全平方数

目录 题目1- 思路2- 实现⭐完全平方数——题解思路 3- ACM 实现 题目 原题连接&#xff1a;279. 完全平方数 1- 思路 思路 动规五部曲 2- 实现 ⭐完全平方数——题解思路 class Solution {public int numSquares(int n) {// 1. 定义 dpint[] dp new int[n1];//2. 递推公式…

1Panel面板配置java运行环境及网站的详细操作教程

本篇文章主要讲解&#xff0c;通过1Panel面板实现java运行环境&#xff0c;部署网站并加载的详细教程。 日期&#xff1a;2024年7月21日 作者&#xff1a;任聪聪 独立博客&#xff1a;https://rccblogs.com/501.html 一、实际效果 二、详细操作 步骤一、给我的项目进行打包&am…

报考安全工程师,这些人千万别考!

近几年&#xff0c;注安证书的缺口很大&#xff0c;而这两年题目的难度也在逐步增大&#xff0c;考生能越早考越好。但在大环境不好时&#xff0c;很多人都想着增加点副业&#xff0c;因此很多人都会有想学习考证&#xff0c;基本上为了注安挂靠才去考的&#xff0c;有这种念头…

一次搞定!中级软件设计师备考通关秘籍

大家好&#xff0c;我是小欧&#xff01; 今天我们来聊聊软考这个话题。要是你准备参加计算机技术与软件专业技术资格&#xff08;软考&#xff09;&#xff0c;那么这篇文章就是为你量身定做的。话不多说&#xff0c;咱们直接进入正题。 什么是软考&#xff1f; 软考&#xf…

CST热仿真案例——电动车直流快充Cable热仿真

作者 | Zhou Ming 随着大功率直流充电桩的不断普及&#xff0c;我们在享受直流快充带来的高效优势外&#xff0c;更要关注直流快充的安全问题。工程师在设计中必须考虑过压、过流、短路、过温等多重保护机制&#xff0c;才能确保充电过程中的设备和人身安全。本期我们利用CST对…

在浏览器中测试JavaScript代码方法简要介绍

在浏览器中测试JavaScript代码方法简要介绍 在浏览器中测试JavaScript代码是前端开发中的一个重要技能。方法如下&#xff1a; 1. 浏览器控制台 最简单和直接的方法是使用浏览器的开发者工具中的控制台&#xff08;Console&#xff09;。 步骤&#xff1a; 在大多数浏览器…

IT common sense常识

how to input formative json data in console console.log({"message": [{"cat_id": 1,"cat_name": "大家电","cat_pid": 0,"cat_level": 0,"cat_deleted": false,,,,})2) how to clear unecessary c…

软件安全测试知识分享,第三方软件测评中心推荐

在信息技术飞速发展的今天&#xff0c;软件安全测试已经成为确保软件产品质量及用户数据安全的关键环节。 安全测试的核心是识别、评估和降低软件系统中潜在的安全风险。通过深度剖析软件架构、代码及其使用环境&#xff0c;测试人员能够找出可能被黑客攻击的薄弱环节。所谓“…

QSqlRelationalTableModel 增删改查

QSqlRelationalTableModel 可以作为关系数据表的模型类&#xff0c;适用于三范式设计的表&#xff0c;主表中自动加载外键表中的名称。本文实现QSqlRelationalTableModel 为模型类&#xff0c;实现增删改查。 目录 0.表准备 1. 构建表格数据 声明变量 表格、数据模型、选择…

5G赋能新能源,工业5G路由器实现充电桩远程高效管理

随着5G技术的广泛应用&#xff0c;新能源充电桩的5G应用正逐步构建起全新的生态系统。在数字化转型的浪潮中&#xff0c;新能源充电桩行业正迎来数字化改革。工业5G路由器的引入&#xff0c;为充电桩的远程管理提供了强有力的技术支持&#xff0c;新能源充电桩5G路由器网络部署…

Linux系统编程__进程学习1

文章目录 进程相关概念进程创建实战fork函数创建进程vfork创建进程子进程退出等待子进程孤儿进程 进程相关概念 一、什么是程序&#xff0c;什么是进程&#xff0c;有什么区别&#xff1f; 程序是静态的概念&#xff0c;gcc xxx.c –o pro 磁盘中生成pro文件&#xff0c;叫做程…