架构演化带来的问题当我们使用传统的CS架构时,服务端会因为故障等原因阻塞请求,这可能导致客户端的请求失去响应,进而导致一段时间后出现问题。批量用户无法获得服务。不过,这种情况可能造成的影响是有限的,也是可以预见的。但是,在微服务系统中,你的服务器可能依赖于其他几个微服务,而这些微服务又依赖于其他更多的微服务。内部)因为级联的资源消耗对整个链路造成了灾难性的后果,我们称之为“服务崩溃”。解决问题的几种方法Fuse模式:顾名思义,就像家用电路一样,如果线路电压过高,保险丝就会熔断,以防止火灾。在使用断路器模式的系统中,如果发现上游服务调用缓慢,或者大量超时,则直接终止对服务的调用,直接返回信息,快速释放资源。恢复呼叫,直到上游服务改善。隔离模式:将不同的资源或服务调用分成几个不同的请求池。一个池中的资源耗尽不会影响对其他资源的请求,防止单点故障耗尽所有资源。这是一种非常传统的容灾设计。限流方式:熔断和隔离都是事后处理的方式,限流方式可以防患于未然,降低问题发生的概率。限流模式可以为某些业务请求设置最高QPS阈值,超过阈值的请求直接返回,不占用资源进行处理。但是,限流方式并不能解决服务崩溃的问题,因为崩溃往往不是因为请求量太大,而是因为多个级联层的放大。断路器的机制和实现断路器的存在相当于给我们多了一层保护。当调用稳定性不好时,或者当服务和资源有可能发生故障时,断路器可以监测到这些错误并达到一定的级别。超过阈值后请求失败,以防止过度消耗资源。而且,断路器还具有自动识别服务状态并恢复的功能。当上游服务恢复正常时,断路器可以自动判断并恢复正常请求。我们来看一个没有断路器的请求流程:用户依赖ServiceA提供服务,ServiceA依赖ServiceB提供的服务。假设此时ServiceB发生故障,在一段时间内,每个请求都会延迟10秒。那么假设我们有N个用户请求ServiceA的服务,在几秒内,ServiceA的资源就会因为向ServiceB发起的请求被挂起而耗尽,从而拒绝User后续的任何请求。对于用户来说,这意味着ServiceA和ServiceB同时失效,导致整个服务链路崩溃。当我们在ServiceA上安装断路器时会发生什么?断路器会在失败次数达到一定阈值后发现对ServiceB的请求无效。此时ServiceA不需要继续请求ServiceB,而是直接返回失败,或者使用其他Fallback备份数据。此时,断路器处于分闸状态。一段时间后,断路器会开始定时查询ServiceB是否已经恢复。此时断路器处于半开状态。如果ServiceB已经恢复,断路器会被置于关闭状态,ServiceA会正常调用ServiceB并返回结果。断路器的状态图如下:可以看出断路器的几个核心点如下:超时时间:请求达到多长时间,导致失败失败阈值:需要失败的次数在断路器触发开路之前要到达Retrytimeout:当断路器处于打开状态时,需要多长时间重试请求,即进入半开状态有了这个知识,我们可以尝试创建断路器:classCircuitBreaker{constructor(timeout,failureThreshold,retryTimePeriod){//我们从关闭状态开始,希望一切正常this.state='CLOSED';//在我们将状态更改为“OPEN”之前,我们从依赖服务接收到的失败次数this.failureThreshold=failureThreshold;//API请求超时.this.timeout=timeout;//向相关//服务发出新请求以检查服务是否启动的时间段。this.retryTimePeriod=retryTimePeriod;this.lastFailureTime=null;这个.failureCount=0;}}构造断路器的状态机:asynccall(urlToCall){//确定电路的当前状态。这个.setState();switch(this.state){case'OPEN'://如果没有电路处于OPEN状态,则返回缓存的响应return{data:'这是陈旧的回应'};//如果电路未打开,则发出API请求);//耶!!API响应良好。让我们重置一切。这个。重置();返回响应;}catch(err){//哦!呼叫仍然失败。让我们在记录中更新它。这个.recordFailure();抛出新错误(错误);}default:console.log('Thisstateshouldneverbereached');返回“状态机中的意外状态”;}}补充余功能://电路初始化时将所有参数重置为初始状态reset(){this.failureCount=0;this.lastFailureTime=null;this.state='关闭';}//设置断路器的当前状态。setState(){如果(这个.失败ureCount>this.failureThreshold){if((Date.now()-this.lastFailureTime)>this.retryTimePeriod){this.state='HALF-OPEN';}else{this.state='OPEN';}}else{this.state='关闭';}}recordFailure(){this.failureCount+=1;this.lastFailureTime=Date.now();使用断路器时,只需要将请求包裹在断路器实例的Call中,在方法中调用即可:...constcircuitBreaker=newCircuitBreaker(3000,5,2000);constresponse=awaitcircuitBreaker.call('http://0.0.0.0:8000/flakycall');成熟的Node.js断路器库RedHat很早就创建了一个成熟的Node.js断路器实现,叫做Opossum,链接在这里:Opossum对于分布式系统,使用这个库可以大大提高你服务的容错能力,从根本上解决服务崩溃的问题。作者:ES2049文章,可随意转载,但请保留原文链接。如果你有热情,非常欢迎你加入ES2049Studio。请将简历发送至caijun.hcj@alibaba-inc.com
