服务容错-熔断策略之断路器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次是被快速失败了

  1. 第一次请求接口超时;
  2. 第四次请求时,10s内的请求4个了,满足RequestVolumeThreshold配置,此时错误接口个数是1,计算1/4*100等于25,不小于ErrorPercentThreshold配置,断路器进入开启状态;
  3. 第五、六、七次的请求都被快速失败了;
  4. 第八次请求时,满足断路器进入半开启状态的条件(time.Millisecond * 400*3>=SleepWindow),放行本次请求,并且请求响应正常,那么断路器进入关闭状态;
  5. 第九、十次正常。
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

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

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

相关文章

Python打印Python环境、PyTorch和CUDA版本、GPU数量名称等信息

代码&#xff1a; import torch import platformgpu_num torch.cuda.device_count() torch_version torch.__version__ python_version platform.python_version()print("Python Version: Python %s" % python_version) print("PyTorch Version: %s" %…

Spring之推断构造方法源码

文章目录 一、 简介1. 内容回顾2. Spring推断构造方法 二、 源码分析 一、 简介 1. 内容回顾 前面分析了Spring的Bean的生命周期的源码&#xff0c;然后分析了依赖注入源码&#xff0c;以及依赖注入的过程中循环依赖Spring的解决方案。在介绍Bean的生命周期中&#xff0c;我们…

三维模型的几何坐标纠正应用探讨

三维模型的几何坐标纠正应用探讨 倾斜摄影三维模型数据的几何坐标纠正应用分析 近年来&#xff0c;倾斜摄影技术在三维数据采集设备中得到广泛应用。倾斜摄影技术通过在飞行平台上搭载多台传感器&#xff0c;从不同角度采集影像&#xff0c;相比传统的摄影测量&#xff0c;倾斜…

每日算法打卡:数的三次方根 day 7

文章目录 原题链接题目描述输入格式输出格式数据范围输入样例&#xff1a;输出样例&#xff1a; 题目分析示例代码 原题链接 790. 数的三次方根 题目难度&#xff1a;简单 题目描述 给定一个浮点数 n&#xff0c;求它的三次方根。 输入格式 共一行&#xff0c;包含一个浮…

第17课 为rtsp流加入移动检测功能

在上节课&#xff0c;我们成功拿到了rtsp视频和音频流&#xff0c;在第13课&#xff0c;我们为普通的usb摄像头加上了移动检测功能&#xff0c;那能不能给rtsp摄像头也加上移动检测功能以实现一些好玩的应用呢&#xff1f;答案是肯定的&#xff0c;在usb摄像头检测中&#xff0…

vue结合Cesium加载gltf模型

Cesium支持什么格式&#xff1f; Cesium支持的格式包括&#xff1a;3D模型格式&#xff08;如COLLADA、gITF、OBJ&#xff09;、影像格式&#xff08;如JPEG、PNG、GeoTIFF&#xff09;、地形格式&#xff08;如STL、Heightmap&#xff09;、矢量数据格式&#xff08;如GeoJSON…

初识Linux shell

Linux初探 Linux系统可以划分为4个部分&#xff1a; Linux内核&#xff1a;Linux系统的核心&#xff0c;控制着系统的所有硬件和软件&#xff0c;在必要时分配硬件&#xff0c;并根据需要执行软件。 内核主要功能&#xff1a; 系统内存管理&#xff1a;内核通过硬件上称为交换…

labelImg的安装与使用

目录 1、查看本机是否安装labelImg 2、安装labelImg 3、创建自己的数据集 3.1 建立新文件夹 3.2 打开labelImg 注意&#xff1a;出现闪退的情况处理。 4、文件格式转换 4.1 修改文件夹路径 4.2 新建datasets文件夹 4.3 修改图片路径 4.4 执行 1、查看本机是否安装la…

【Origin绘图1】环形图

环形图绘制 Origin绘制环形图案例 MATLAB绘制环形饼状图案例 参考 环形图如下&#xff0c;可分析不同年份各组分变化情况&#xff1a; Origin绘制环形图 貌似对Origin版本有要求&#xff0c;下载的2019版并无环形图绘制工具。因此&#xff0c;重新下载了2022版本。 案例 第…

Python 利用PYQT5设计基于RSA算法盲签名的匿名化电子支付系统设计与实现

基于RSA算法的盲签名算法 David Chaum 于1982年提出盲签名的概念&#xff0c;并利用RSA算法设计了第一个盲签名方案. 该方案的安全性基于大整数分解问题 盲签名的步骤 1.密钥生成 签名者执行以下步骤生成密钥对: ①签名者选择两个大素数p,q&#xff0c; 计算npq&#xff0…

【OpenVINO 】在 MacOS 上编译 OpenVINO C++ 项目

前言 英特尔公司发行的模型部署工具OpenVINO™模型部署套件&#xff0c;可以实现在不同系统环境下运行&#xff0c;且发布的OpenVINO™ 2023最新版目前已经支持MacOS系统并同时支持在苹果M系列芯片上部署模型。在该项目中&#xff0c;我们将向大家展示如何在MacOS系统、M2芯片的…

鸿鹄电子招投标系统源码实现与立项流程:基于Spring Boot、Mybatis、Redis和Layui的企业电子招采平台

随着企业的快速发展&#xff0c;招采管理逐渐成为企业运营中的重要环节。为了满足公司对内部招采管理提升的要求&#xff0c;建立一个公平、公开、公正的采购环境至关重要。在这个背景下&#xff0c;我们开发了一款电子招标采购软件&#xff0c;以最大限度地控制采购成本&#…

NFS 共享存储实验

一、服务器部署 第一步、安装nfs和rpcbind包 [rootserver ~]# yum install -y nfs-utils rpcbind截图&#xff1a; 第二步、这里选择一个 lvm 挂载点做 NFS 共享目录 [rootserver ~]# df -HT截图&#xff1a; 第三步、修改配置文件 [rootserver ~]# vi /etc/exports /home …

神经网络的核心:简单易懂理解 PyTorch 非线性激活函数

目录 torch.nn子函数非线性激活详解 nn.Softmin Softmin 函数简介 函数工作原理 参数详解 使用技巧与注意事项 示例代码 nn.Softmax Softmax 函数简介 函数工作原理 参数详解 使用技巧与注意事项 示例代码 nn.Softmax2d Softmax2d 函数简介 函数工作原理 输入…

2024最新前端源码分享(附效果图及在线演示)

分享10款非常有趣的前端特效源码 其中包含css动画特效、js原生特效、svg特效以及小游戏等 下面我会给出特效样式图或演示效果图 但你也可以点击在线预览查看源码的最终展示效果及下载源码资源 粒子文字动画特效 基于canvas实现的粒子文字动画特效 会来回切换设定的文字特效 图…

在版权付费方面,OpenAI 比人想象中的还要「小气」

随着新闻出版商与AI公司达成“使用新闻训练AI模型”的协议&#xff0c;像 OpenAI 等科技企业愿意为受版权保护的信息支付的价格逐渐浮出水面。 据 The Information 报道&#xff0c;OpenAI 每年愿意向出版商提供 100万到500万美元来支付受版权保护的新闻文章训练其AI模型。 但…

【leetcode】力扣热门之合并两个有序列表【简单难度】

题目描述 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 用例 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4] 输入&#xff1a;l1 [], l2 [] 输出&#xff1a;[] 输入&#xff1a;l1 []…

王中阳Go赠书活动第一期:《TVM编译器原理与实践》

文章目录 前言TVM编译器的实现过程关于《TVM编译器原理与实践》编辑推荐内容简介作者简介图书目录书中前言/序言《TVM编译器原理与实践》全书速览入手《TVM编译器原理与实践》传送门&#xff1a;结束语参加抽奖 前言 随着人工智能的发展&#xff0c;计算机视觉、自然语言处理和…

MySQL复习汇总(图书管理系统)

MySQL图书管理系统&#xff08;49-94&#xff09;源码_71.备份book数据库到e盘的mybook.sql文件(备份文件中要求包含建库命令)-CSDN博客 CROSS JOIN&#xff1a;交叉连接&#xff08;笛卡尔积&#xff09; -- 1、 创建一个名称为book的数据库。 -- 2、 打开book数据库…

Vue2-组件的基本应用

个人练习&#xff0c;仅供参考。 1.先在components中创建公用的内容&#xff08;public.vue&#xff09;。components文件夹下放组件供其他页面调用。 2.在用到组件的页面导入该公用组件&#xff08;import navTitle from "/components/public.vue";&#xff09;。 …