当我们工作的系统处于分布式系统的早期阶段时,往往此时每个服务只部署一个节点。在这样的背景下,如果一个服务A需要发布新版本,往往会影响其他依赖服务A运行的程序,甚至,一旦服务A的启动和预热过程时间过长,就会出现问题变得更加严重,大量的请求会被阻塞,造成级联效应,导致整个系统变慢。我举个夸张的例子来说明:一栋楼里的下水道管道是从最高层到最低层的。这时候,如果你家楼下的水管堵了,楼上的污水就会全部流回你家。;如果这导致你家的管道口被堵塞,它就会流回楼上,依此类推。但是,一旦在现实生活中发现了这个问题,难免会先想办法避免影响到家里,然后跑到楼下让他们赶紧把水管修好。这时候,避免影响自己家的方式,就可以称之为“熔断”。什么是保险丝?保险丝本质上是一种过载保护机制。这个概念来自于电子工程中的断路器,也许你已经被这个东西的“跳闸”保护了。互联网系统中的断路器机制是指当下游服务响应缓慢或因访问压力过大而失效时,上游服务可以暂时切断对下游服务的调用,以保护自身和系统的整体可用性。制作断路器的思路一般是:一个中心思想,分为四个步骤。如何进行融合如何进行融合?首先,你需要坚持的一个中心思想是:量力而行。因为软件不同于人,不会有奇迹发生。什么样的性能支撑多少流量是固定的,这是根本。那么,这四个步骤分别是:定义一个识别是否“不可用”的策略切断联系定义一个识别是否“可用”的策略并尝试再次检测定义一个识别是否“不健康”相信有丰富软件开发经验的人都知道。判断一个系统是否正常无外乎两点:是否可以调优。如果可以调整,是否需要比预期更长的时间?但是,由于分布式系统是建立在一个并非100%可靠的网络上,所以上述情况总是会发生,所以我们不能将偶尔出现的瞬时异常等同于系统“不可用”(避免偏泛化)。因此,我们需要引入“时间窗”的概念。这个时间窗用来“放宽”判断“不可用”的区间,也意味着给系统多了几次证明自己“可用”的机会。但是,如果系统在这个时间窗口内还是达到了你定义的“不可用”,那我们就“断臂求生”了。可以通过两种方式指定此标准:阈值。例如,10秒内100个“无法连接”或100个超过5秒的请求。百分比。例如,30%的请求在10秒内“无法连接”或30%的请求超过5秒。最终会形成这样一段代码:globalvariableerrorcount=0;//有一个独立的线程,每隔10秒(时间窗口)重置为0。全局变量isOpenCircuitBreaker=false;//dosomething...if(success){returnssuccess;}else{errorcount++;if(errorcount==unavailablethreshold){isOpenCircuitBreaker=true;}}切断联系应该尽可能果断”,既然已经确定对方“不可用”,那么就干脆默认为“失败”,避免做无用功,顺便减轻对方的压力分布式系统中的程序间调用一般都是通过一些RPC框架进行的。那么此时,作为client端,在自己进程中通过proxy发起调用之前,可以不经过网络直接返回failure,这就是所谓的“failfast”机制,只需添加如下内容上述代码段之前的一段代码:if(isOpenCircuitBreaker==true){returnfail;}//dosomething...定义一个策略来识别是否处于“可用”状态,并尝试检测断开连接最后,完整性该功能将在难免受到影响,所以还是需要尽快恢复,提供完整的服务能力。这件事绝对不能人为干预,时效性难免会受到影响。那么如何自动识别依赖系统是否“可用”呢?这也需要你定义一个策略。一般来说,这个策略类似于识别“不可用”的策略,不同的是这里有一个反向指标:阈值。例如,10秒内出现100次“调用成功”,并且所有这些都不到1秒。百分比。例如,95%的请求在10秒内“成功”,98%的请求不到1秒。还包括“时间窗口”、“阈值”和“百分比”。稍有不同的是,在大多数情况下,一个系统的“不可用”状态往往会持续一段时间,不会那么快恢复。所以我们不需要像第一步识别“不可用”一样,一直记录请求状态,只需要在一段时间后检测即可。因此,多了一个“间隔时间”的概念。这个间隔可以是固定的,比如30秒。它也可以通过线性增长或指数增长来动态增加。这在代码中大致表示如下:globalvariablesuccessCount=0;//有一个独立的线程,每10秒(时间窗口)重置为0。//并将下面的isHalfOpen设置为false。全局变量isHalfOpen=true;//有一个独立线程,每30秒(间隔时间)重置为true。//dosomething...if(success){if(isHalfOpen){successCount++;if(successCount=availablethreshold){isOpenCircuitBreaker=false;}}returnsuccess;}else{errorcount++;if(errorcount==unavailablethreshold){isOpenCircuitBreaker=true;}}另外,尝试检测本质上是一种“试错”,必须控制“试错成本”。所以我们不可能100%的验证流量。一般有两种方法:允许一定比例的流量进行验证。如果整个通信框架是统一的,也可以统一给各个系统增加一个独立的接口,专门用来验证程序的健康状态。该接口可以额外返回一些系统负载信息,用于判断健康状态,如CPU、I/O状态等。恢复正常一旦“可用性”的验证通过,整个系统就恢复到“正常”状态,需要重新开启识别“不可用”的策略。这样,系统就会形成一个循环,如下图所示:这就是一个完整的熔断机制的外观。理解了这些核心思想之后,用什么框架去实现就变得不那么重要了,因为大部分都是换汤不换药。以上可以说是主要的部分,还有一些最佳实践可以让你在实现断路器的时候更加精准。融合的最佳实践以及什么场景最适合融合一个东西在不同的场景下会有不同的效果。以下是我能想到的几个最适合融合的场景,以发挥更大的优势:依赖系统本身就是一个共享系统,当前客户端只是其中之一。这是因为,如果其他客户端随意拨打电话,也会影响您的通话。所有依赖的系统都部署在共享环境中(资源不隔离),不独占使用。例如,在与负载很重的数据库相同的服务器上。依赖系统是一种经常迭代更新的服务。这也意味着越“敏捷”的系统需要“融合”。当前系统中的流量是不确定的。比如电商网站的流量会出现很大的波动,你能承受流量的突然增加并不代表你所依赖的后台系统也能抵挡。这也反映了在我们的软件设计中拥有“怀疑”心态的重要性。做熔断器有一些需要注意的地方。与所有事物一样,保险丝也不是完美的东西。我们需要特别注意两个问题。首先,如果你所依赖的系统是多副本或分区的,你要注意,个别节点出现异常并不代表所有节点都出现异常,需要区别对待。其次,断路器应该始终作为最后的选择,我们应该优先使用一些“降级”或“限流”的解决方案。因为“有总比没有好”,虽然不可能提供完整的服务,但仍需继续努力,尽可能减少影响。比如放弃非核心业务,友情提醒等,我们会在后续的文章中展开这部分内容。总结这篇文章主要讲了断路器的作用和实践,总结了一些自己的最佳实践。从上面的代码示例也可以看出,熔断代码的位置要么在实际方法之前,要么在实际方法之后。非常适合AOP编程思想的开发,所以我们平时使用的fuse框架都会基于AOP。保险丝只是一个保护壳,当周围出现异常时保护自己。但从长远来看,通常会定期进行压力测试,以更好地防患于未然,减少触发熔断的次数。如果清楚每个系统的权重是多少,然后在此基础上做好“限流”和“降级”,这样基本上就可以将“高压”下触发熔断的概率降到最低。
