服务容错-熔断策略之断路器hystrix-go
文章目录
- 概要
- 一、服务熔断
- 二、断路器模式
- 三、hystrix-go
- 3.1、使用
- 3.2、源码
- 四、参考
概要
微服务先行者Martin Fowler与James Lewis在文章microservices中指出了微服务的九大特征,其中一个便是容错性设计(Design for failure)。正如文章中提到的,微服务相对于单体服务而言,不同服务之间的通信是经过网络完成的,服务间调用时,上游服务可能随时处于不可用状态(比如崩溃,达到服务最大处理能力等等原因)。
由此会引发一个问题,一个服务点的错误经过层层传递,最终会波及到调用链上的所有服务,这便是雪崩效应,因此如何防止雪崩效应便是微服务架构容错性设计原则的具体实践,否则服务化程度越高,整个系统反而越不稳定。
在实践中有很多容错方案,诸如故障转移、快速失败(服务熔断)、安全失败、沉默失败、故障恢复、负载均衡、重试、限流、服务降级、舱壁隔离等等。这些方案分别从事前(负载均衡、限流、舱壁隔离、服务降级),事中(故障转移、快速失败、安全失败、沉默失败、重试),事后(故障恢复)三个节点提高整个系统的稳定性。
PS:服务降级归到事前,主要是因为服务降级大多数情况下不是在出现错误后才被执行的,在许多场景中,所说的服务降级更多的是指需要主动使服务进入降级逻辑的情况,比如电商预见双11流量高峰、游戏停服更新等。
一、服务熔断
服务熔断策略方案来源于生活中的电路保险丝,电路保险丝遵循一家一个的原则,当该家庭电流增大到一定数值时,其自身熔断而切断电路,保护电视机、冰箱等电器,并不会影响其他家庭的用电。

同理,可推理到微服务之间的网络调用。

如图,当服务C出现异常构,服务B很快会检测到服务C不可用(服务C接口超时或错误等指标满足不可用判定条件),此时服务B不在将请求转发到服务C,而是快速返回错误信息(快速失败)。在一段时间内的后续请求就一直返回失败,稍后当检测到服务C接口调用响应正常后,就会恢复到正常状态。
二、断路器模式
断路器模式是实现熔断策略的具体方案,其本质是接管微服务之间的远程调用请求,断路器会持续监控并统计被调用服务接口返回成功、失败、超时、拒绝等各种结果的指标,当某一个指标满足预设阈值时,断路器就会进入开启状态,后续相应的远程调用请求就会快速返回错误信息,而不会真的对被调用服务发起请求。若干时间后断路器会进入半打开状态,此时断路器会放行一次请求,如果请求正常,则断路器进入关闭状态,否则转入开启状态。
从上面描述来看,断路器是一种有限状态机:

- 关闭状态,此时断路器会放行请求到上游服务,该状态是断路器的初始状态;
- 开启状态,当断路器统计的某一项指标满足开启条件时就会进入该状态,此时不会放行请求到上游服务,而是快速返回错误信息;
- 半打开状态,这时一种中间状态,主要是因为断路器要具有故障恢复的能力,所以当进入该状态时,断路器会允许放行一次请求到上游服务。一般是在断路器开启后若干时间后自动进入该状态。
断路器进入半打开状态在实现时并不需要计时器,而是收到请求时检测下是否满足半打开状态(一般是将断路器开启时间与当前时间做比较),是的话就放行该次请求,否则快速返回错误信息。
断路器工作时序图如下:

三、hystrix-go
hystrix-go是作者从JAVA Netflix的子项目Hystrix翻译过来的,很经典的断路器项目。
3.1、使用
hystrix-go 调用接口有两个:
- Do:同步调用
func Do(name string, run runFunc, fallback fallbackFunc)
- Go:异步调用
func Go(name string, run runFunc, fallback fallbackFunc)
hystrix-go配置项:
// CommandConfig is used to tune circuit settings at runtime
type CommandConfig struct {Timeout int `json:"timeout"`MaxConcurrentRequests int `json:"max_concurrent_requests"`RequestVolumeThreshold int `json:"request_volume_threshold"`SleepWindow int `json:"sleep_window"`ErrorPercentThreshold int `json:"error_percent_threshold"`
}
- MaxConcurrentRequests:请求的并发量,接口并发超过该值也会被归为接口错误(ErrMaxConcurrency);
- Timeout:请求超时时间,接口响应时间超过该值也会归为接口错误(ErrTimeout);
- RequestVolumeThreshold:一个窗口(代码里写死的10秒)内的请求数阙值,达到这个阙值才会进入接口错误百分比计算逻辑;
- ErrorPercentThreshold :设置接口错误(除了ErrMaxConcurrency,ErrTimeout两种错误,接口自身错误也会被计入)的百分比,大于该值断路器就会进入开启状态;
- SleepWindow:断路器开启后,多久后进入半开启状态。
直接上代码。
import ("errors""fmt""github.com/afex/hystrix-go/hystrix""time"
)
var (global errortimes int
)
//模拟远程请求
func mockHttp() error {times++fmt.Println(times)if global != nil {return nil}time.Sleep(2 * time.Second)return errors.New("业务出错")
}
const breakFlag = "testBreaker"
func main() {hystrix.ConfigureCommand(breakFlag, hystrix.CommandConfig{Timeout: 1000, MaxConcurrentRequests: 50, ErrorPercentThreshold: 25, RequestVolumeThreshold: 4, SleepWindow: 1000, })//hystrix.SetLogger() //打印断流器内部日志for i := 0; i < 10; i++ {time.Sleep(time.Millisecond * 400) //给熔断器重试服务时机_ = hystrix.Do(breakFlag, func() error {return mockHttp()}, func(err error) error { //不发生错误不会进入该逻辑的if err != nil {fmt.Printf("times:%d,断路器检测到错误:%s\n", times, err.Error())} else {fmt.Printf("times:%d,断路器恢复正常", times)}global = errreturn nil})}fmt.Println("times:", times)
}
输出如下:
1
times:1,断路器检测到错误:hystrix: timeout
2
3
4
times:4,断路器检测到错误:hystrix: circuit open
times:4,断路器检测到错误:hystrix: circuit open
times:4,断路器检测到错误:hystrix: circuit open
5
6
7
times: 7
分析:
可以看到真正发出的请求是7次,3次是被快速失败了
- 第一次请求接口超时;
- 第四次请求时,10s内的请求4个了,满足RequestVolumeThreshold配置,此时错误接口个数是1,计算1/4*100等于25,不小于ErrorPercentThreshold配置,断路器进入开启状态;
- 第五、六、七次的请求都被快速失败了;
- 第八次请求时,满足断路器进入半开启状态的条件(time.Millisecond * 400*3>=SleepWindow),放行本次请求,并且请求响应正常,那么断路器进入关闭状态;
- 第九、十次正常。
3.2、源码
Do和Go两个API最终都会进入GoC函数
func GoC(ctx context.Context, name string, run runFuncC, fallback fallbackFuncC) chan error {cmd := &command{run: run,fallback: fallback,start: time.Now(),errChan: make(chan error, 1),finished: make(chan bool, 1),}circuit, _, err := GetCircuit(name)//获取指标统计器if err != nil {cmd.errChan <- errreturn cmd.errChan}cmd.circuit = circuitticketCond := sync.NewCond(cmd)ticketChecked := falsereturnTicket := func() {cmd.Lock()// Avoid releasing before a ticket is acquired.for !ticketChecked {ticketCond.Wait()}cmd.circuit.executorPool.Return(cmd.ticket)//执行完之后归还请求令牌cmd.Unlock()}// Shared by the following two goroutines. It ensures only the faster// goroutine runs errWithFallback() and reportAllEvent().returnOnce := &sync.Once{}reportAllEvent := func() {err := cmd.circuit.ReportEvent(cmd.events, cmd.start, cmd.runDuration)//上报此次请求时正常还是异常,便于后续进行指标统计if err != nil {log.Printf(err.Error())}}go func() {defer func() { cmd.finished <- true }()if !cmd.circuit.AllowRequest() {//统计指标,决定开启、半开启、关闭三个状态的流转cmd.Lock()// It's safe for another goroutine to go ahead releasing a nil ticket.ticketChecked = trueticketCond.Signal()cmd.Unlock()returnOnce.Do(func() {returnTicket()cmd.errorWithFallback(ctx, ErrCircuitOpen)//上报断路器处于开启状态的错误,不过该错误不会被纳入接口错误指标reportAllEvent()})return}cmd.Lock()select {case cmd.ticket = <-circuit.executorPool.Tickets://获取一个请求令牌ticketChecked = trueticketCond.Signal()cmd.Unlock()default: //没有令牌,就表示请求达到并发限制MaxConcurrentRequests配置的值,上报ErrMaxConcurrency错误ticketChecked = trueticketCond.Signal()cmd.Unlock()returnOnce.Do(func() {returnTicket()cmd.errorWithFallback(ctx, ErrMaxConcurrency)reportAllEvent()})return}runStart := time.Now()runErr := run(ctx) //没有达到限流就发起请求returnOnce.Do(func() {defer reportAllEvent()cmd.runDuration = time.Since(runStart)returnTicket()if runErr != nil {cmd.errorWithFallback(ctx, runErr) //出错就上报业务接口的错误return}cmd.reportEvent("success")//表示请求成功})}()go func() {timer := time.NewTimer(getSettings(name).Timeout)//根据Timeout配置起一个定时器defer timer.Stop()select {case <-cmd.finished: //请求执行完毕// returnOnce has been executed in another goroutinecase <-ctx.Done(): //收集context上下文错误returnOnce.Do(func() {returnTicket()cmd.errorWithFallback(ctx, ctx.Err())reportAllEvent()})returncase <-timer.C: //标识服务接口超时,上报ErrTimeout错误returnOnce.Do(func() {returnTicket()cmd.errorWithFallback(ctx, ErrTimeout)reportAllEvent()})return}}()return cmd.errChan
}
进入开启状态
func (circuit *CircuitBreaker) AllowRequest() bool {return !circuit.IsOpen() || circuit.allowSingleTest()
}
//判断断路器处于关闭状态还是开启状态
func (circuit *CircuitBreaker) IsOpen() bool {circuit.mutex.RLock()o := circuit.forceOpen || circuit.opencircuit.mutex.RUnlock()if o {return true}if uint64(circuit.metrics.Requests().Sum(time.Now())) < getSettings(circuit.Name).RequestVolumeThreshold {return false}if !circuit.metrics.IsHealthy(time.Now()) {//计算10s内错误请求百分比// too many failures, open the circuitcircuit.setOpen() //断路器状态为开启状态return true}return false
}//circuit.metrics.Requests().Sum方法,这里可以看到统计指标的窗口是10s
func (r *Number) Sum(now time.Time) float64 {sum := float64(0)r.Mutex.RLock()defer r.Mutex.RUnlock()for timestamp, bucket := range r.Buckets {// TODO: configurable rolling windowif timestamp >= now.Unix()-10 {sum += bucket.Value}}return sum
}
断路器半开启状态判断
func (circuit *CircuitBreaker) allowSingleTest() bool {circuit.mutex.RLock()defer circuit.mutex.RUnlock()now := time.Now().UnixNano()openedOrLastTestedTime := atomic.LoadInt64(&circuit.openedOrLastTestedTime)//如果断路器处于开启状态,且当前时间>断路器开启时间+SleepWindow配置,精确到纳秒,则进入半开启状态if circuit.open && now > openedOrLastTestedTime+getSettings(circuit.Name).SleepWindow.Nanoseconds() {swapped := atomic.CompareAndSwapInt64(&circuit.openedOrLastTestedTime, openedOrLastTestedTime, now)if swapped {log.Printf("hystrix-go: allowing single test to possibly close circuit %v", circuit.Name)}return swapped}return false
}
恢复为关闭状态
func (circuit *CircuitBreaker) ReportEvent(eventTypes []string, start time.Time, runDuration time.Duration) error {if len(eventTypes) == 0 {return fmt.Errorf("no event types sent for metrics")}circuit.mutex.RLock()o := circuit.opencircuit.mutex.RUnlock()if eventTypes[0] == "success" && o {//此次请求成功,且断路器处于开启状态,则将断路器转为关闭状态circuit.setClose()}//省略代码...return nil
}
四、参考
1]:服务治理:熔断器介绍以及hystrix-go的使用
2]:Microservices
相关文章:
服务容错-熔断策略之断路器hystrix-go
文章目录 概要一、服务熔断二、断路器模式三、hystrix-go3.1、使用3.2、源码 四、参考 概要 微服务先行者Martin Fowler与James Lewis在文章microservices中指出了微服务的九大特征,其中一个便是容错性设计(Design for failure)。正如文章中提到的,微服…...
C++进阶(三)多态
📘北尘_:个人主页 🌎个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上,不忘来时的初心 文章目录 一、多态的概念1、概念 二、多态的定义及实现1、多态的构成条件2、虚函数3、虚函数的重写4、C…...
大众汽车宣布将ChatGPT,批量集成在多种汽车中!
1月9日,大众汽车在官网宣布,将ChatGPT批量集成到电动、内燃机汽车中。 大众表示,将ChatGPT与其IDA语音助手相结合,用户通过自然语言就能与ChatGPT进行互动,例如,帮我看看最近的三星米其林饭店在哪里&#…...
React----函数组件和类组件
函数组件与类组件:React 中的两种组件风格 React 是一个用于构建用户界面的流行 JavaScript 库,其中组件是构建块的基本单元。在 React 中,有两种主要的组件风格:函数组件和类组件。本文将使用TypeScript介绍它们的用法、区别以及…...
Kafka集群部署 (KRaft模式集群)
KRaft 模式是 Kafka 在 3.0 版本中引入的新模式。KRaft 模式使用了 Raft 共识算法来管理 Kafka 集群元数据。Raft 算法是一种分布式共识算法,具有高可用性、可扩展性和安全性等优势。 在 KRaft 模式下,Kafka 集群中的每个 Broker 都具有和 Zookeeper 类…...
Vue 自定义仿word表单录入之日期输入组件
因项目需要,要实现仿word方式录入数据,要实现鼠标经过时才显示编辑组件,预览及离开后则显示具体的文字。 鼠标经过时显示 正常显示及离开时显示 组件代码 <template ><div class"paper-input flex flex-col border-box "…...
Oracle与Java JDBC数据类型对照
Oracle Database JDBC开发人员指南和参考 SQL Data TypesJDBC Type CodesStandard Java TypesOracle Extension Java Types CHAR java.sql.Types.CHAR java.lang.String oracle.sql.CHAR VARCHAR2 java.sql.Types.VARCHAR java.lang.String oracle.sql.CHAR LONG jav…...
C++力扣题目226--翻转二叉树
给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。 示例 1: 输入:root [4,2,7,1,3,6,9] 输出:[4,7,2,9,6,3,1]示例 2: 输入:root [2,1,3] 输出:[2,3,1]示例 3&#x…...
Gorm 数据库表迁移与表模型定义
文章目录 一、Docker快速创建MySQL实例1.1 创建1.3 创建数据库 二、AutoMigrate介绍与使用2.1 AutoMigrate介绍2.2 AutoMigrate 基本使用 三、模型定义3.1 模型定义3.2 快速增删改查3.3 约定3.4 gorm.Model 四、表模型主键、表名、列名的约定4.1 主键(Primary Key&a…...
系列三、Spring Security中自定义用户名/密码
一、Spring Security中自定义用户名/密码 1.1、自定义用户名/密码 1.1.1、配置文件中配置 spring.security.user.nameroot spring.security.user.password123456 1.1.2、定义基于内存的用户 /*** Author : 一叶浮萍归大海* Date: 2024/1/11 21:50* Description:*/ Configu…...
如何顺滑使用华为云编译构建平台?
这两年平台构建服务需求越来越大,却一直苦于找不到一些指南, 这里特意写了一篇, 对在学习代码阶段和新手程序员朋友也蛮友好, 配置真的也不难, 也特别适合想尝试从0到1做个APP的朋友了。 以华为云的CodeArts Build为例…...
查看Linux磁盘空间
(1)、该命令会列出当前系统所有挂载的文件系统以及它们的使用情况,包括总容量、已用空间、可用空间、使用百分比等信息 df -h如果查看某一个文件夹的,可以 df -h folderName (2)、计算指定目录下所有文件和子目录所占用的磁盘空间大小,并以人类可读的格…...
2023年全国职业院校技能大赛(高职组)“云计算应用”赛项赛卷⑩
2023年全国职业院校技能大赛(高职组) “云计算应用”赛项赛卷10 目录 需要竞赛软件包环境以及备赛资源可私信博主!!! 2023年全国职业院校技能大赛(高职组) “云计算应用”赛项赛卷10 模块…...
vim基本操作命令
一、vi简介 vi是“Visual interface”的简称,它在Linux上的地位就仿佛Edit程序在DOS上一样。它可以执行输出、删除、查找、替换、块操作等众多文本操作,而且用户可以根据自己的需要对其进行定制。Vi不是一个排版程序,它不象Word或WPS那样可以…...
mybatis-plus实现真正的批量插入
1、安装依赖 <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-extension</artifactId><version>3.5.3.2</version></dependency>版本与mybatis-plus一致 2、编写sql注入器 package com.example.answe…...
pytorch12:GPU加速模型训练
目录 1、CPU与GPU2、数据迁移至GPU2.1 to函数使用方法 3、torch.cuda常用方法4、多GPU并行运算4.1 torch.nn.DataParallel4.2 torch.distributed加速并行训练 5、gpu总结 1、CPU与GPU CPU(Central Processing Unit, 中央处理器):主要包括控制…...
P1603 斯诺登的密码题解
题目 (1)找出句子中所有用英文表示的数字(≤20),列举在下: 正规:one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen twenty 非正规…...
YOLOv8 + openVINO 多线程数据读写顺序处理
多线程数据读写顺序处理 一个典型的生产者-消费者模型,在这个模型中,多个工作线程并行处理从共享队列中获取的数据,并将处理结果以保持原始顺序的方式放入另一个队列。 多线程处理模型,具体细节如下: 1.数据:数据里必…...
端到端自动驾驶
自动驾驶主要流程:感知->预测->规划 预测是预测周围目标(车、行人、动物等)的轨迹,规划是规划自车的运动轨迹。 UniAD[CVPR 2023]: 使用transformer架构,统一自动驾驶流程,完成所有检测,…...
Developer Tools for Game Creator 1
插件包含: 持久世界时间管理系统 单击以生成对象或预设 游戏内调试控制台 游戏内事件控制台 控制台管理控制 命令模板脚本 游戏内屏幕截图 低分辨率和高分辨率图像 缩略图生成 移动支持 使用Game Creator Action或拖放来激活和控制组件,无需编码。 通过此资产,您可以获得: …...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...
土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等
🔍 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术,可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势,还能有效评价重大生态工程…...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...
SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...
什么是VR全景技术
VR全景技术,全称为虚拟现实全景技术,是通过计算机图像模拟生成三维空间中的虚拟世界,使用户能够在该虚拟世界中进行全方位、无死角的观察和交互的技术。VR全景技术模拟人在真实空间中的视觉体验,结合图文、3D、音视频等多媒体元素…...
Android写一个捕获全局异常的工具类
项目开发和实际运行过程中难免会遇到异常发生,系统提供了一个可以捕获全局异常的工具Uncaughtexceptionhandler,它是Thread的子类(就是package java.lang;里线程的Thread)。本文将利用它将设备信息、报错信息以及错误的发生时间都…...
WEB3全栈开发——面试专业技能点P7前端与链上集成
一、Next.js技术栈 ✅ 概念介绍 Next.js 是一个基于 React 的 服务端渲染(SSR)与静态网站生成(SSG) 框架,由 Vercel 开发。它简化了构建生产级 React 应用的过程,并内置了很多特性: ✅ 文件系…...
