分布式系统关注点(8)——99%的人都能看懂的「熔断」以及最佳实践
当我们工作所在的系统处于分布式系统初期的时候往往这时候每个服务都只部署了一个节点。那么在这样的背景下如果某个服务A需要发布一个新版本往往会对正在运行的其它依赖服务A的程序产生影响。甚至一旦服务A的启动预热过程耗时过长问题会更严重大量请求会阻塞产生级联影响导致整个系统卡慢。▲点击图片可查看大图举个夸张的例子来形容一幢楼的下水管是从最高楼直通到最低楼的这个时候如果你家楼下的管道口堵住了那么所有楼上的污水就会倒灌到你家。如果这导致你家的管道口也堵住了之后又会倒灌到楼上一层以此类推。然而实际生活中一旦你发现了这个问题必然会想办法先避免影响到自己家然后跑到楼下让他们赶紧疏通管道。此时避免影响自己家的办法就可被称之为「熔断」。一、熔断是什么熔断本质上是一个过载保护机制。这一概念来源于电子工程中的断路器可能你曾经被这个东西的“跳闸”保护过。▲图片来源于网络版权归原作者所有在互联网系统中的熔断机制是指当下游服务因访问压力过大而响应变慢或失败上游服务为了保护自己以及系统整体的可用性可以暂时切断对下游服务的调用。做熔断的思路大体上就是一个中心思想分四步走。二、熔断怎么做首先需秉持的一个中心思想是量力而行。因为软件和人不同没有奇迹会发生什么样的性能撑多少流量是固定的。这是根本。然后这四步走分别是定义一个识别是否处于“不可用”状态的策略切断联系定义一个识别是否处于“可用”状态的策略并尝试探测重新恢复正常定义一个识别是否处于“不正常”状态的策略相信软件开发经验丰富的你也知道识别一个系统是否正常无非是两个点。是不是能调通如果能调通耗时是不是超过预期的长但是由于分布式系统被建立在一个并不是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%的流量去验证一般会有以下两种方式放行一定比例的流量去验证。如果在整个通信框架都是统一的情况下还可以统一给每个系统增加一个专门用于验证程序健康状态检测的独立接口。这个接口额外可以多返回一些系统负载信息用于判断健康状态如CPU、I/O的情况等。重新恢复正常一旦通过了衡量是否“可用”的验证整个系统就恢复到了“正常”状态此时需要重新开启识别“不可用”的策略。就这样系统会形成一个循环。▲点击图片可查看大图这就是一个完整的熔断机制的面貌。了解了这些核心思想用什么框架去实施就变得不是那么重要了因为大部分都是换汤不换药。上面聊到的这些可以说是主干部分还有一些最佳实践可以让你在实施熔断的时候拿捏的更到位。