服务容错-熔断策略之断路器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;包含一个浮…

C++_虚析构

介绍 1.只有虚析构 (PS:没有虚构造) 2.加与不加 virtual关键词 的区别 2.1如果父类的析构函数 不是虚析构&#xff0c;则父类指针指向子类时&#xff0c;delete掉父类的指针 (PS:则效果为 只调用父类的析构的函数&#xff0c;不调用子类的析构函数) 2.2 如果父类的析构函数 是虚…

第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…

css计时器 animation实现计时器延时器

css计时器 animation实现计时器延时器 缺点当切页面导航会休眠不执行 最初需求是一个列表每个项目都有各自的失效时间 然后就想到 计时器延时器轮询等方案 这些方案每一个都要有自己的计时器 感觉不是很好 轮询也占资源 然后突发奇想 css能不能实现 开始想到的是transition测…

初识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芯片的…

编写一个简单的服务和客户端(C++)

背景 当节点使用服务进行通信时&#xff0c;发送数据请求的节点称为客户端节点&#xff0c;响应请求的节点称为服务节点。请求和响应的结构由.srv文件确定。 这里使用的例子是一个简单的整数加法系统&#xff1b;一个节点请求两个整数之和&#xff0c;另一个节点响应结果。 …

基于云平台技术的车辆远程诊断浅谈

基于云平台技术的车辆远程诊断浅谈 一、引言 随着汽车工业的不断发展&#xff0c;车辆故障诊断技术的需求日益增长。传统的车辆故障诊断方式已经无法满足现代汽车对高效、智能的追求。基于云平台技术的车辆远程诊断创新&#xff0c;为汽车故障诊断带来了新的解决方案。 二、…

【linux】docker基本概念和基础指令操作(镜像、容器)

docker docker镜像 images xxxx(名称):xxxx(版本号) 每一个image可以生成若干个容器&#xff08;container&#xff09;&#xff0c;所有用相同镜像生成的容器环境完全一样 每一个容器都是一个完全独立的云端服务器 镜像指令 docker pull ubuntu:20.04 #拉取一个镜像 doc…

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

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

HarmonyOS应用开发者基础(初级)认证题库

开发者能力认证 一、判断 首选项preferences是以Key-Value形式存储数据&#xff0c;其中Key是可以重复。&#xff08;错&#xff09;使用http模块发起网络请求时&#xff0c;必须要使用on(‘headersReceive’&#xff09;订阅请求头&#xff0c;请求才会成功。&#xff08;错…

15.脚本备份、恢复达梦DM数据库

1.备份脚本 #!/bin/bash # 通过docker命令查询达梦数据库容器id CONTAINER_IDdocker ps -a | grep "dm8_single" | awk {print $1} | head -n 1#备份目录 DUMP_DIR"/data/dm8/data/backup"DATE_FORMATdate %Y%m%d echo "备份my_db1数据库开始&#…

从零开始构建一个属于您的 Web3 项目(Web3项目三实战之一)

午后,沏上一壶茶,走到那冬日暖阳照耀到的阳台,落座于桌旁,很是舒心地敲打键盘上的每个按键,这样的午后,或许才是我们所向往的吧! 它舍弃了城市中的喧嚣;也没有大都市的那种快节奏;更加没有了“尔虞我诈、精于算计”的职场“战火硝烟”。 它有的只是寂静、随心所欲、闲…