2025上半年,最新 AI实践都在这!20+ 应用案例,任听一场议题就值回票价 了解详情
写点什么

分布式系统关注点:99% 人看得懂的“熔断”以及最佳实践

  • 2018-12-05
  • 本文字数:3190 字

    阅读完需:约 10 分钟

分布式系统关注点:99%人看得懂的“熔断”以及最佳实践

当我们工作所在的系统处于分布式系统初期的时候,往往这时候每个服务都只部署了一个节点。


那么在这样的背景下,如果某个服务 A 需要发布一个新版本,往往会对正在运行的其它依赖服务 A 的程序产生影响。甚至,一旦服务 A 的启动预热过程耗时过长,问题会更严重,大量请求会阻塞,产生级联影响,导致整个系统卡慢。



举个夸张的例子来形容:一幢楼的下水管是从最高楼直通到最低楼的,这个时候如果你家楼下的管道口堵住了,那么所有楼上的污水就会倒灌到你家。如果这导致你家的管道口也堵住了,之后又会倒灌到楼上一层,以此类推。


然而实际生活中一旦你发现了这个问题,必然会想办法先避免影响到自己家,然后跑到楼下让他们赶紧疏通管道。此时,避免影响自己家的办法就可被称之为「熔断」。


熔断本质上是一个过载保护机制。在互联网系统中的熔断机制是指:当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护自己以及系统整体的可用性,可以暂时切断对下游服务的调用。


做熔断的思路大体上就是:一个中心思想,分四步走。

一、熔断怎么做

首先,需秉持的一个中心思想是:量力而行。因为软件和人不同,没有奇迹会发生,什么样的性能撑多少流量是固定的。这是根本。


然后,这四步走分别是:


  1. 定义一个识别是否处于“不可用”状态的策略

  2. 切断联系

  3. 定义一个识别是否处于“可用”状态的策略,并尝试探测

  4. 重新恢复正常

定义一个识别是否处于“不正常”状态的策略

相信软件开发经验丰富的你也知道,识别一个系统是否正常,无非是两个点。


  • 是不是能调通

  • 如果能调通,耗时是不是超过预期的长


但是,由于分布式系统被建立在一个并不是 100%可靠的网络上,所以上述的情况总有发生,因此我们不能将偶发的瞬时异常等同于系统“不可用”(避免以偏概全)。由此我们需要引入一个「时间窗口」的概念,这个时间窗口用来“放宽”判定“不可用”的区间,也意味着多给了系统几次证明自己“可用”机会。但是,如果系统还是在这个时间窗口内达到了你定义“不可用”标准,那么我们就要“断臂求生”了。


这个标准可以有两种方式来指定。


• 阈值。比如,在 10 秒内出现 100 次“无法连接”或者出现 100 次大于 5 秒的请求。


• 百分比。比如,在 10 秒内有 30%请求“无法连接”或者 30%的请求大于 5 秒。


最终会形成这样这样的一段代码。


全局变量 errorcount = 0; //有个独立的线程每隔10秒(时间窗口)重置为0。全局变量 isOpenCircuitBreaker = false;
//do some thing...
if(success){ return success;}else{ errorcount++; if(errorcount == 不可用阈值){ isOpenCircuitBreaker = true; }}
复制代码
切断联系

切断联系要尽可能的“果断”,既然已经认定了对方“不可用”,那么索性就默认“失败”,避免做无用功,也顺带能缓解对方的压力。


分布式系统中的程序间调用,一般都会通过一些 RPC 框架进行。



那么,这个时候作为客户端一方,在自己进程内通过代理发起调用之前就可以直接返回失败,不走网络。



这就是常说的「fail fast」机制。就是在前面提到的代码段之前增加下面的这段代码。


if(isOpenCircuitBreaker == true){    return fail;}
//do some thing...
复制代码
定义一个识别是否处于“可用”状态的策略,并尝试探测

切断联系后,功能的完整性必然会受影响,所以还是需要尽快恢复回来,以提供完整的服务能力。这事肯定不能人为去干预,及时性必然会受到影响。那么如何能够自动的识别依赖系统是否“可用”呢?这也需要你来定义一个策略。


一般来说这个策略与识别“不可用”的策略类似,只是这里是一个反向指标。


• 阈值。比如,在 10 秒内出现 100 次“调用成功”并且耗时都小于 1 秒。


• 百分比。比如,在 10 秒内有 95%请求“调用成功”并且 98%的请求小于 1 秒。


同样包含「时间窗口」、「阈值」以及「百分比」。


稍微不同的地方在于,大多数情况下,一个系统“不可用”的状态往往会持续一段时间,不会那么快就恢复过来。所以我们不需要像第一步中识别“不可用”那样,无时无刻的记录请求状况,而只需要在每隔一段时间之后去进行探测即可。所以,这里多了一个「间隔时间」的概念。这个间隔幅度可以是固定的,比如 30 秒。也可以是动态增加的,通过线性增长或者指数增长等方式。


这个用代码表述大致是这样。


全局变量 successCount = 0; //有个独立的线程每隔10秒(时间窗口)重置为0。//并且将下面的isHalfOpen设为false。
全局变量 isHalfOpen = true;//有个独立的线程每隔30秒(间隔时间)重置为true。
//do some thing...if(success){ if(isHalfOpen){ successCount ++; if(successCount = 可用阈值){ isOpenCircuitBreaker = false; } } return success;}else{ errorcount++; if(errorcount == 不可用阈值){ isOpenCircuitBreaker = true; }}
复制代码


另外,尝试探测本质上是一个“试错”,要控制下“试错成本”。所以我们不可能拿 100%的流量去验证,一般会有以下两种方式:


  1. 放行一定比例的流量去验证。

  2. 如果在整个通信框架都是统一的情况下,还可以统一给每个系统增加一个专门用于验证程序健康状态检测的独立接口。这个接口额外可以多返回一些系统负载信息用于判断健康状态,如 CPU、I/O 的情况等。

重新恢复正常

一旦通过了衡量是否“可用”的验证,整个系统就恢复到了“正常”状态,此时需要重新开启识别“不可用”的策略。就这样,系统会形成一个循环。



这就是一个完整的熔断机制的面貌。了解了这些核心思想,用什么框架去实施就变得不是那么重要了,因为大部分都是换汤不换药。


上面聊到的这些可以说是主干部分,还有一些最佳实践可以让你在实施熔断的时候拿捏的更到位。

二、做熔断的最佳实践

什么场景最适合做熔断

一个事物在不同的场景里会发挥出不同的效果。以下是我能想到最适合熔断发挥更大优势的几个场景:


• 所依赖的系统本身是一个共享系统,当前客户端只是其中的一个客户端。这是因为,如果其它客户端进行胡乱调用也会影响到你的调用。


• 所以依赖的系统被部署在一个共享环境中(资源未做隔离),并不独占使用。比如,和某个高负荷的数据库在同一台服务器上。


• 所依赖的系统是一个经常会迭代更新的服务。这点也意味着,越“敏捷”的系统越需要“熔断”。


• 当前所在的系统流量大小是不确定的。比如,一个电商网站的流量波动会很大,你能抗住突增的流量不代表所依赖的后端系统也能抗住。这点也反映出了我们在软件设计中带着“面向怀疑”的心态的重要性。

做熔断时还要注意的一些地方

与所有事物一样,熔断也不是一个完美的事物,我们特别需要注意 2 个问题。


首先,如果所依赖的系统是多副本或者做了分区的,那么要注意其中个别节点的异常并不等于所有节点都存在异常,所以需要区别对待。


其次,熔断往往应作为最后的选择,我们应优先使用一些「降级」或者「限流」方案。因为“部分胜于无”,虽然无法提供完整的服务,但尽可能的降低影响是要持续去努力的。比如,抛弃非核心业务、给出友好提示等等,这部分内容我们会在后续的文章中展开。

三、总结

本文主要聊了熔断的作用以及做法,并且总结了一些我自己的最佳实践。


上面的这些代码示例中也可以看到,熔断代码所在的位置要么在实际方法之前,要么在实际方法之后。它非常适合 AOP 编程思想的发挥,所以我们平常用到的熔断框架都会基于 AOP 去做。


熔断只是一个保护壳,在周围出现异常的时候保全自身。但是从长远来看平时定期做好压力测试才能更好的防范于未然,降低触发熔断的次数。如果清楚的知道每个系统有几斤几两,在这个基础上再把「限流」和「降级」做好,这基本就将“高压”下触发熔断的概率降到最低了。


作者介绍:张帆(Zachary),7 年电商行业经验,5 年开发团队管理经验,4 年互联网架构经验,目前任职于某垂直电商技术总监。专注大型系统架构、分布式系统。坚持用心打磨每一篇原创。本文首发于公众号:跨界架构师(ID:Zachary_ZF)。


2018-12-05 14:406216

评论 2 条评论

发布
用户头像
学习了,谢谢
2019-01-10 09:23
回复
用户头像
受教了
2018-12-06 17:01
回复
没有更多了
发现更多内容

深入浅出!全面剖析Java反射-Reflection,java项目开发实战入门电子书百度云

Java 程序员 后端

牛P牛P!Github上堪称2021最全、最新Java面试题库到底有多香

Java 程序员 后端

区块链让奢侈品的分销、溯源不再是难题

CECBC

牛皮了!一篇文章直接解决关于TCP的23种疑难问题!,springboot源码深度解析视频

Java 程序员 后端

独家!Java开发专家P7岗必备的MySQL高级笔记及面试宝典,面试横竖绕不开MySQL

Java 程序员 后端

测试用例的设计方法及案例,java技术框架

Java 程序员 后端

消息队列面试题及答案,大V推荐

Java 程序员 后端

淘系,60W年薪大牛!新肝出一份,细说JVM内存模型

Java 程序员 后端

牛批!阿里的Springboot笔记,果然值得我每天熬夜啃,全栈系统化的学习路线

Java 程序员 后端

牛掰plus!裸辞后集中Java面试,凭借一个技术套路了多个面试官

Java 程序员 后端

涨姿势,Java中New一个对象是个怎么样的过程?,linux操作系统实用教程教师用书

Java 程序员 后端

深入解析java虚拟机:垃圾回收,最大并发标记清除垃圾回收器

Java 程序员 后端

清华大牛纯手写2021年最新JVM调优实战手册,看完让你精通JVM调优

Java 程序员 后端

炸裂!这份阿里P8大佬手写“Java核心技能精选,java笔试面试宝典

Java 程序员 后端

一文了解「区块链桥」:区块链桥的工作方式及四种类型

CECBC

元宇宙将如何影响我们的投资、就业和生活方式?

CECBC

浅析Mysql索引数据结构演变,让你一看就懂,java业务场景面试题

Java 程序员 后端

深入P8级别JAVA底层知识:你知道阿里P8需要掌握哪些技术吗?

Java 程序员 后端

深入理解Java内存模型,小白也能看得懂!,限时发布

Java 程序员 后端

深入理解Java虚拟机之类加载机制篇,秋招java后端面试

Java 程序员 后端

源码解析BeanUtils,Java开发还不会这些

Java 程序员 后端

教学改革拆除“骨鲠”,产教融合的“一鱼多吃”创新之路

脑极体

消息疯狂堆积!RocketMQ出Bug了?,rabbitmq分布式事务原理

Java 程序员 后端

模块二作业-微信朋友圈的高性能复杂度

无名

架构实战营 「架构实战营」

牺牲速度来节省内存,Redis是觉得自己太快了吗?,mysql破解版百度网盘

Java 程序员 后端

深入浅出!带你重学Java—ArrayList,mongodb的存储原理

Java 程序员 后端

源码解析 HashMap 的线程安全问题,mysql索引左前缀原理

Java 程序员 后端

爽,字节架构师DDD(领域驱动设计,Spring事务扩展机制

Java 程序员 后端

深入理解静态代理与JDK动态代理,java编程技术基础周绍斌

Java 程序员 后端

爆赞!腾讯T4大牛发布Java基础核心宝典,简直就是及时雨

Java 程序员 后端

032云原生之AIOps运维

穿过生命散发芬芳

云原生 10月月更

分布式系统关注点:99%人看得懂的“熔断”以及最佳实践_架构_张帆_InfoQ精选文章