在微服务中服务间依赖非常常见,比如评论服务依赖审核服务而审核服务又依赖反垃圾服务,当评论服务调用审核服务时,审核服务又调用反垃圾服务,而这时反垃圾服务超时了,由于审核服务依赖反垃圾服务,反垃圾服务超时导致审核服务逻辑一直等待,而这个时候评论服务又在一直调用审核服务,审核服务就有可能因为堆积了大量请求而导致服务宕机

由此可见,在整个调用链中,中间的某一个环节出现异常就会引起上游调用服务出现一些列的问题,甚至导致整个调用链的服务都宕机,这是非常可怕的。因此一个服务作为调用方调用另一个服务时,为了防止被调用服务出现问题进而导致调用服务出现问题,所以调用服务需要进行自我保护,而保护的常用手段就是熔断

熔断器原理

熔断机制其实是参考了我们日常生活中的保险丝的保护机制,当电路超负荷运行时,保险丝会自动的断开,从而保证电路中的电器不受损害。而服务治理中的熔断机制,指的是在发起服务调用的时候,如果被调用方返回的错误率超过一定的阈值,那么后续的请求将不会真正发起请求,而是在调用方直接返回错误

在这种模式下,服务调用方为每一个调用服务(调用路径)维护一个状态机,在这个状态机中有三个状态:

  • 关闭(Closed):在这种状态下,我们需要一个计数器来记录调用失败的次数和总的请求次数,如果在某个时间窗口内,失败的失败率达到预设的阈值,则切换到断开状态,此时开启一个超时时间,当到达该时间则切换到半关闭状态,该超时时间是给了系统一次机会来修正导致调用失败的错误,以回到正常的工作状态。在关闭状态下,调用错误是基于时间的,在特定的时间间隔内会重置,这能够防止偶然错误导致熔断器进去断开状态
  • 打开(Open):在该状态下,发起请求时会立即返回错误,一般会启动一个超时计时器,当计时器超时后,状态切换到半打开状态,也可以设置一个定时器,定期的探测服务是否恢复
  • 半打开(Half-Open):在该状态下,允许应用程序一定数量的请求发往被调用服务,如果这些调用正常,那么可以认为被调用服务已经恢复正常,此时熔断器切换到关闭状态,同时需要重置计数。如果这部分仍有调用失败的情况,则认为被调用方仍然没有恢复,熔断器会切换到关闭状态,然后重置计数器,半打开状态能够有效防止正在恢复中的服务被突然大量请求再次打垮

服务治理中引入熔断机制,使得系统更加稳定和有弹性,在系统从错误中恢复的时候提供稳定性,并且减少了错误对系统性能的影响,可以快速拒绝可能导致错误的服务调用,而不需要等待真正的错误返回

熔断器引入

上面介绍了熔断器的原理,在了解完原理后,你是否有思考我们如何引入熔断器呢?一种方案是在业务逻辑中可以加入熔断器,但显然是不够优雅也不够通用的,因此我们需要把熔断器集成在框架内,在zRPC框架内就内置了熔断器

我们知道,熔断器主要是用来保护调用端,调用端在发起请求的时候需要先经过熔断器,而客户端拦截器正好兼具了这个这个功能,所以在zRPC框架内熔断器是实现在客户端拦截器内,拦截器的原理如下图:

对应的代码为:

func BreakerInterceptor(ctx context.Context, method string, req, reply interface{},
cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
// 基于请求方法进行熔断
breakerName := path.Join(cc.Target(), method)
return breaker.DoWithAcceptable(breakerName, func() error {
// 真正发起调用
return invoker(ctx, method, req, reply, cc, opts...)
// codes.Acceptable判断哪种错误需要加入熔断错误计数
}, codes.Acceptable)
}

熔断器实现

zRPC中熔断器的实现参考了Google Sre过载保护算法,该算法的原理如下:

  • 请求数量(requests):调用方发起请求的数量总和
  • 请求接受数量(accepts):被调用方正常处理的请求数量

在正常情况下,这两个值是相等的,随着被调用方服务出现异常开始拒绝请求,请求接受数量(accepts)的值开始逐渐小于请求数量(requests),这个时候调用方可以继续发送请求,直到requests = K * accepts,一旦超过这个限制,熔断器就回打开,新的请求会在本地以一定的概率被抛弃直接返回错误,概率的计算公式如下:

通过修改算法中的K(倍值),可以调节熔断器的敏感度,当降低该倍值会使自适应熔断算法更敏感,当增加该倍值会使得自适应熔断算法降低敏感度,举例来说,假设将调用方的请求上限从 requests = 2 * acceptst 调整为 requests = 1.1 * accepts 那么就意味着调用方每十个请求之中就有一个请求会触发熔断

代码路径为go-zero/core/breaker

type googleBreaker struct {
k float64 // 倍值 默认1.5
stat *collection.RollingWindow // 滑动时间窗口,用来对请求失败和成功计数
proba *mathx.Proba // 动态概率
}

自适应熔断算法实现

func (b *googleBreaker) accept() error {
accepts, total := b.history() // 请求接受数量和请求总量
weightedAccepts := b.k * float64(accepts)
// 计算丢弃请求概率
dropRatio := math.Max(0, (float64(total-protection)-weightedAccepts)/float64(total+1))
if dropRatio <= 0 {
return nil
}
// 动态判断是否触发熔断
if b.proba.TrueOnProba(dropRatio) {
return ErrServiceUnavailable
} return nil
}

每次发起请求会调用doReq方法,在这个方法中首先通过accept效验是否触发熔断,acceptable用来判断哪些error会计入失败计数,定义如下:

func Acceptable(err error) bool {
switch status.Code(err) {
case codes.DeadlineExceeded, codes.Internal, codes.Unavailable, codes.DataLoss: // 异常请求错误
return false
default:
return true
}
}

如果请求正常则通过markSuccess把请求数量和请求接受数量都加一,如果请求不正常则只有请求数量会加一

func (b *googleBreaker) doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error {
// 判断是否触发熔断
if err := b.accept(); err != nil {
if fallback != nil {
return fallback(err)
} else {
return err
}
} defer func() {
if e := recover(); e != nil {
b.markFailure()
panic(e)
}
}() // 执行真正的调用
err := req()
// 正常请求计数
if acceptable(err) {
b.markSuccess()
} else {
// 异常请求计数
b.markFailure()
} return err
}

总结

调用端可以通过熔断机制进行自我保护,防止调用下游服务出现异常,或者耗时过长影响调用端的业务逻辑,很多功能完整的微服务框架都会内置熔断器。其实,不仅微服务调用之间需要熔断器,在调用依赖资源的时候,比如mysql、redis等也可以引入熔断器的机制。

项目地址:

https://github.com/tal-tech/go-zero

熔断原理与实现Golang版的更多相关文章

  1. Golang版protobuf编译

    官方网址: https://developers.google.com/protocol-buffers/ (需要FQ) 代码仓库: https://github.com/google/protobu ...

  2. [Golang] kafka集群搭建和golang版生产者和消费者

    一.kafka集群搭建 至于kafka是什么我都不多做介绍了,网上写的已经非常详尽了. 1. 下载zookeeper  https://zookeeper.apache.org/releases.ht ...

  3. APP自动化框架-ATX原理解析及JAVA版客户端

    作为网易开源的ATX APP自动化测试框架,对比现有的macaca自动化框架/Appium自动化框架,最大的特别就是在于可远程进行自动化测试 先给大家看一张我自己梳理的框架架构图 框架巧妙点: 1. ...

  4. 《Let's Build A Simple Interpreter》之 Golang 版

    一直以来对编译器/解释器等都较有兴趣.我非科班出身,当初还在大学时,只是马马虎虎看完了<编译原理>之类教材,上机非常少,对龙书之类圣经也只是浅尝辄止而已.工作至今,基本已将编译原理相关知识 ...

  5. 研究微信红包分配算法之Golang版

    今天来看一下红包的分配,参考几年前流传的微信红包分配算法,今天用Golang实现一版,并测试验证结果. 微信红包的随机算法是怎样实现的?https://www.zhihu.com/question/2 ...

  6. 开源项目go2o - golang版的o2o项目

    发一个github上唯一用golang实现的o2o项目 What's Go2o Golang combine simple o2o DDD domain-driven design realizati ...

  7. adb常用命令(golang版)及输入中文

    package main import ( "crypto/md5" "fmt" "image/png" "io/ioutil&q ...

  8. Spring MVC工作原理(好用版)

    Spring MVC工作原理 参考: SpringMVC工作原理 - 平凡希 - 博客园https://www.cnblogs.com/xiaoxi/p/6164383.html SpringMVC的 ...

  9. 重新梳理一下adb操作app(golang版)

    主要参考我之前整理的内容https://www.cnblogs.com/pu369/p/10490668.html,梳理简化一下思路,以便于用最简单的代码来应对无聊人士的要求. 需求主要是:打开手机. ...

随机推荐

  1. Shiro框架--将Shrio的session改成HTTPSession数据

    重写 FormAuthenticationFilter类 的 onLoginSuccess()方法即可 import javax.servlet.ServletRequest; import java ...

  2. 关于sqlmap当中tamper脚本编码绕过原理的一些总结(学习python没多久有些地方肯定理解有些小问题)

    sqlmap中tamper脚本分析编写 置十对一些编码实现的脚本,很多sqlmap里面需要引用的无法实现,所以有一部分例如keywords就只写写了几个引用了一下,其实这里很多脚本运用是可以绕过安全狗 ...

  3. pwnable.kr-cmd1-witeup

    执行分析程序,知过滤掉了flag.sh.tmp字段,但在linux下可通过通配符匹配文件哦. 哦对,参数输入的命令中,没有环境变量的支持,所有文件和命令必须用绝对路径哦.

  4. MySQL表关系总结

    一对多关系  : 一对多关系是关系数据库中两个表之间的一种关系,该关系中第一个表中的单个行可以与第二个表中的一个或多个行相关,但第二个表中的一个行只可以与第一个表中的一个行相关. 一对多关系,一般是一 ...

  5. Salesforce LWC学习(二十七) File Upload

    本篇参考: https://developer.salesforce.com/docs/component-library/bundle/lightning-file-upload/documenta ...

  6. 微服务通信之feign的注册、发现过程

    前言 feign 是目前微服务间通信的主流方式,是springCloud中一个非常重要的组件.他涉及到了负载均衡.限流等组件.真正意义上掌握了feign可以说就掌握了微服务. 一.feign的使用 f ...

  7. 0923 lca练习

    P1967 货车运输 题目描述 A 国有 nnn 座城市,编号从 11 1 到 n nn,城市之间有 mmm 条双向道路.每一条道路对车辆都有重量限制,简称限重. 现在有 qqq 辆货车在运输货物, ...

  8. 引用类型之Array(二)

    操作方法 concat( ) concat() 方法用于连接两个或多个数组. 该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本. 语法 arrayObject.concat(arrayX,a ...

  9. ubuntu19.10如何添加开机启动项

    $sudo vi /lib/systemd/system/rc-local.service内容如下[Unit]Description=/etc/rc.local CompatibilityDocume ...

  10. mysql任意文件读取漏洞复现

    前言 第一次得知该漏洞后找了一些文章去看. 一开始不明白这个漏洞是怎么来的,只知道通过在服务端运行poc脚本就可以读取客户端的任意文件,直接找到网上准备好的靶机进行测试,发现可行,然后就拿别人的poc ...