版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、 系统熔断器架构设计前言:架构之家 微信号 itfly8功能介绍 ITFLY8架构之家,专注于架构知识分享交流,涵盖项目管理和产品设计。包括大型分布式网站架构(高性能,高可用,缓存,消息队列.),设计模式,架构模式,大数据,项目管理(SCRUM,PMP,Prince2),产品设计等如果大家有印象的话,尤其是夏天,如果家里用电负载过大,比如开了很多家用电器,就会”自动跳闸”,此时电路就会断开。在以前更古老的一种方式是”保险丝”,当负载过大,或者电路发生故障或异常时,电流会不断升高,为防止升高的电流有可能损坏电路中的某些重要器件或贵重器件,烧毁电路甚至造成火灾。保险丝会在电流异常升高到一定的高度和
2、热度的时候,自身熔断切断电流,从而起到保护电路安全运行的作用。同样,在大型的软件系统中,如果调用的远程服务或者资源由于某种原因无法使用时,如果没有这种过载保护,就会导致请求的资源阻塞在服务器上等待从而耗尽系统或者服务器资源。很多时候刚开始可能只是系统出现了局部的、小规模的故障,然而由于种种原因,故障影响的范围越来越大,最终导致了全局性的后果。软件系统中的这种过载保护就是本文将要谈到的熔断器模式(Circuit Breaker)一 问题的产生在大型的分布式系统中,通常需要调用或操作远程的服务或者资源,这些远程的服务或者资源由于调用者不可以控的原因比如网络连接缓慢,资源被占用或者暂时不可用等原因,
3、导致对这些远程资源的调用失败。这些错误通常在稍后的一段时间内可以恢复正常。但是,在某些情况下,由于一些无法预知的原因导致结果很难预料,远程的方法或者资源可能需要很长的一段时间才能修复。这种错误严重到系统的部分失去响应甚至导致整个服务的完全不可用。在这种情况下,采用不断地重试可能解决不了问题,相反,应用程序在这个时候应该立即返回并且报告错误。通常,如果一个服务器非常繁忙,那么系统中的部分失败可能会导致 “连锁失效”(cascading failure)。比如,某个操作可能会调用一个远程的WebService,这个service会设置一个超时的时间,如果响应时间超过了该时间就会抛出一个异常。但是这
4、种策略会导致并发的请求调用同样的操作会阻塞,一直等到超时时间的到期。这种对请求的阻塞可能会占用宝贵的系统资源,如内存,线程,数据库连接等等,最后这些资源就会消耗殆尽,使得其他系统不相关的部分所使用的资源也耗尽从而拖累整个系统。在这种情况下,操作立即返回错误而不是等待超时的发生可能是一种更好的选择。只有当调用服务有可能成功时我们再去尝试。二 解决方法熔断器模式可以防止应用程序不断地尝试执行可能会失败的操作,使得应用程序继续执行而不用等待修正错误,或者浪费CPU时间去等到长时间的超时产生。熔断器模式也可以使应用程序能够诊断错误是否已经修正,如果已经修正,应用程序会再次尝试调用操作。熔断器模式就像是
5、那些容易导致错误的操作的一种代理。这种代理能够记录最近调用发生错误的次数,然后决定使用允许操作继续,或者立即返回错误。熔断器可以使用状态机来实现,内部模拟以下几种状态。闭合(closed)状态: 对应用程序的请求能够直接引起方法的调用。代理类维护了最近调用失败的次数,如果某次调用失败,则使失败次数加1。如果最近失败次数超过了在给定时间内允许失败的阈值,则代理类切换到断开(Open)状态。此时代理开启了一个超时时钟,当该时钟超过了该时间,则切换到半断开(Half-Open)状态。该超时时间的设定是给了系统一次机会来修正导致调用失败的错误。断开(Open)状态:在该状态下,对应用程序的请求会立即返
6、回错误响应。半断开(Half-Open)状态:允许对应用程序的一定数量的请求可以去调用服务。如果这些请求对服务的调用成功,那么可以认为之前导致调用失败的错误已经修正,此时熔断器切换到闭合状态(并且将错误计数器重置);如果这一定数量的请求有调用失败的情况,则认为导致之前调用失败的问题仍然存在,熔断器切回到断开方式,然后开始重置计时器来给系统一定的时间来修正错误。半断开状态能够有效防止正在恢复中的服务被突然而来的大量请求再次拖垮。各个状态之间的转换如下图:在Close状态下,错误计数器是基于时间的。在特定的时间间隔内会自动重置。这能够防止由于某次的偶然错误导致熔断器进入断开状态。触发熔断器进入断开
7、状态的失败阈值只有在特定的时间间隔内,错误次数达到指定错误次数的阈值才会产生。在Half-Open状态中使用的连续成功次数计数器记录调用的成功次数。当连续调用成功次数达到某个指定值时,切换到闭合状态,如果某次调用失败,立即切换到断开状态,连续成功调用次数计时器在下次进入半断开状态时归零。实现熔断器模式使得系统更加稳定和有弹性,在系统从错误中恢复的时候提供稳定性,并且减少了错误对系统性能的影响。它通过快速的拒绝那些试图有可能调用会导致错误的服务,而不会去等待操作超时或者永远不会不返回结果来提高系统的响应事件。如果熔断器设计模式在每次状态切换的时候会发出一个事件,这种信息可以用来监控服务的运行状态
8、,能够通知管理员在熔断器切换到断开状态时进行处理。可以对熔断器模式进行定制以适应一些可能会导致远程服务失败的特定场景。比如,可以在熔断器中对超时时间使用不断增长的策略。在熔断器开始进入断开状态的时候,可以设置超时时间为几秒钟,然后如果错误没有被解决,然后将该超时时间设置为几分钟,依次类推。在一些情况下,在断开状态下我们可以返回一些错误的默认值,而不是抛出异常。三 要考虑的因素在实现熔断器模式的时候,以下这些因素需可能需要考虑:异常处理:调用受熔断器保护的服务的时候,我们必须要处理当服务不可用时的异常情况。这些异常处理通常需要视具体的业务情况而定。比如,如果应用程序只是暂时的功能降级,可能需要切
9、换到其它的可替换的服务上来执行相同的任务或者获取相同的数据,或者给用户报告错误然后提示他们稍后重试。异常的类型:请求失败的原因可能有很多种。一些原因可能会比其它原因更严重。比如,请求会失败可能是由于远程的服务崩溃,这可能需要花费数分钟来恢复;也可能是由于服务器暂时负载过重导致超时。熔断器应该能够检查错误的类型,从而根据具体的错误情况来调整策略。比如,可能需要很多次超时异常才可以断定需要切换到断开状态,而只需要几次错误提示就可以判断服务不可用而快速切换到断开状态。日志:熔断器应该能够记录所有失败的请求,以及一些可能会尝试成功的请求,使得的管理员能够监控使用熔断器保护的服务的执行情况。测试服务是否
10、可用:在断开状态下,熔断器可以采用定期的ping远程的服务或者资源,来判断是否服务是否恢复,而不是使用计时器来自动切换到半断开状态。这种ping操作可以模拟之前那些失败的请求,或者可以使用通过调用远程服务提供的检查服务是否可用的方法来判断。手动重置:在系统中对于失败操作的恢复时间是很难确定的,提供一个手动重置功能能够使得管理员可以手动的强制将熔断器切换到闭合状态。同样的,如果受熔断器保护的服务暂时不可用的话,管理员能够强制的将熔断器设置为断开状态。并发问题:相同的熔断器有可能被大量并发请求同时访问。熔断器的实现不应该阻塞并发的请求或者增加每次请求调用的负担。资源的差异性:使用单个熔断器时,一个
11、资源如果有分布在多个地方就需要小心。比如,一个数据可能存储在多个磁盘分区上(shard),某个分区可以正常访问,而另一个可能存在暂时性的问题。在这种情况下,不同的错误响应如果混为一谈,那么应用程序访问的这些存在问题的分区的失败的可能性就会高,而那些被认为是正常的分区,就有可能被阻塞。加快熔断器的熔断操作:有时候,服务返回的错误信息足够让熔断器立即执行熔断操作并且保持一段时间。比如,如果从一个分布式资源返回的响应提示负载超重,那么可以断定出不建议立即重试,而是应该等待几分钟后再重试。(HTTP协议定义了”HTTP 503 Service Unavailable”来表示请求的服务当前不可用,他可以
12、包含其他信息比如,超时等)重复失败请求:当熔断器在断开状态的时候,熔断器可以记录每一次请求的细节,而不是仅仅返回失败信息,这样当远程服务恢复的时候,可以将这些失败的请求再重新请求一次。四 使用场景应该使用该模式来:防止应用程序直接调用那些很可能会调用失败的远程服务或共享资源。不适合的场景对于应用程序中的直接访问本地私有资源,比如内存中的数据结构,如果使用熔断器模式只会增加系统额外开销。不适合作为应用程序中业务逻辑的异常处理替代品五 实现根据上面的状态切换图,我们很容易实现一个基本的熔断器,只需要在内部维护一个状态机,并定义好状态转移的规则,可以使用State模式来实现。首先,我们定义一个表示状
13、态转移操作的抽象类CircuitBreakerState:publicabstractclassCircuitBreakerStateprotectedCircuitBreakerState(CircuitBreakercircuitBreaker)this.circuitBreaker=circuitBreaker;/调用受保护方法之前处理的操作/publicvirtualvoidProtectedCodeIsAboutToBeCalled()/如果是断开状态,直接返回/然后坐等超时转换到半断开状态if(circuitBreaker.IsOpen)thrownewOpenCircuitExc
14、eption();/受熔断器保护的方法调用成功之后的操作/publicvirtualvoidProtectedCodeHasBeenCalled()circuitBreaker.IncreaseSuccessCount();/受熔断器保护的方法调用发生异常操作后的操作/publicvirtualvoidActUponException(Exceptione)/增加失败次数计数器,并且保存错误信息circuitBreaker.IncreaseFailureCount(e);/重置连续成功次数circuitBreaker.ResetConsecutiveSuccessCount();protect
15、edreadonlyCircuitBreakercircuitBreaker;抽象类中,状态机CircuitBreaker通过构造函数注入;当发生错误时,我们增加错误计数器,并且重置连续成功计数器,在增加错误计数器操作中,同时也记录了出错的异常信息。然后在分别实现表示熔断器三个状态的类。首先实现闭合状态CloseState:publicclassClosedState:CircuitBreakerStatepublicClosedState(CircuitBreakercircuitBreaker):base(circuitBreaker)/重置失败计数器circuitBreaker.Rese
16、tFailureCount();publicoverridevoidActUponException(Exceptione)base.ActUponException(e);/如果失败次数达到阈值,则切换到断开状态if(circuitBreaker.FailureThresholdReached()circuitBreaker.MoveToOpenState();在闭合状态下,如果发生错误,并且错误次数达到阈值,则状态机切换到断开状态。断开状态OpenState的实现如下:publicclassOpenState:CircuitBreakerStateprivatereadonlyTimert
17、imer;publicOpenState(CircuitBreakercircuitBreaker):base(circuitBreaker)timer=newTimer(circuitBreaker.Timeout.TotalMilliseconds);timer.Elapsed+=TimeoutHasBeenReached;timer.AutoReset=false;timer.Start();/断开超过设定的阈值,自动切换到半断开状态privatevoidTimeoutHasBeenReached(objectsender,ElapsedEventArgse)circuitBreaker
18、.MoveToHalfOpenState();publicoverridevoidProtectedCodeIsAboutToBeCalled()base.ProtectedCodeIsAboutToBeCalled();thrownewOpenCircuitException();断开状态内部维护一个计数器,如果断开达到一定的时间,则自动切换到版断开状态,并且,在断开状态下,如果需要执行操作,则直接抛出异常。最后半断开Half-Open状态实现如下:publicclassHalfOpenState:CircuitBreakerStatepublicHalfOpenState(CircuitB
19、reakercircuitBreaker):base(circuitBreaker)/重置连续成功计数circuitBreaker.ResetConsecutiveSuccessCount();publicoverridevoidActUponException(Exceptione)base.ActUponException(e);/只要有失败,立即切换到断开模式circuitBreaker.MoveToOpenState();publicoverridevoidProtectedCodeHasBeenCalled()base.ProtectedCodeHasBeenCalled();/如果
20、连续成功次数达到阈值,切换到闭合状态if(circuitBreaker.ConsecutiveSuccessThresholdReached()circuitBreaker.MoveToClosedState();切换到半断开状态时,将连续成功调用计数重置为0,当执行成功的时候,自增改字段,当达到连读调用成功次数的阈值时,切换到闭合状态。如果调用失败,立即切换到断开模式。有了以上三种状态切换之后,我们要实现CircuitBreaker类了:publicclassCircuitBreakerprivatereadonlyobjectmonitor=newobject();privateCircu
21、itBreakerStatestate;publicintFailureCountget;privateset;publicintConsecutiveSuccessCountget;privateset;publicintFailureThresholdget;privateset;publicintConsecutiveSuccessThresholdget;privateset;publicTimeSpanTimeoutget;privateset;publicExceptionLastExceptionget;privateset;publicboolIsClosedgetreturn
22、stateisClosedState;publicboolIsOpengetreturnstateisOpenState;publicboolIsHalfOpengetreturnstateisHalfOpenState;internalvoidMoveToClosedState()state=newClosedState(this);internalvoidMoveToOpenState()state=newOpenState(this);internalvoidMoveToHalfOpenState()state=newHalfOpenState(this);internalvoidInc
23、reaseFailureCount(Exceptionex)LastException=ex;FailureCount+;internalvoidResetFailureCount()FailureCount=0;internalboolFailureThresholdReached()returnFailureCount=FailureThreshold;internalvoidIncreaseSuccessCount()ConsecutiveSuccessCount+;internalvoidResetConsecutiveSuccessCount()ConsecutiveSuccessC
24、ount=0;internalboolConsecutiveSuccessThresholdReached()returnConsecutiveSuccessCount=ConsecutiveSuccessThreshold;在该类中首先:定义了一些记录状态的变量,如FailureCount,ConsecutiveSuccessCount 记录失败次数,连续成功次数,以及FailureThreshold,ConsecutiveSuccessThreshold记录最大调用失败次数,连续调用成功次数。这些对象对外部来说是只读的。定义了一个 CircuitBreakerState类型的state变量
25、,以表示当前系统的状态。定义了一些列获取当前状态的方法IsOpen,IsClose,IsHalfOpen,以及表示状态转移的方法MoveToOpenState,MoveToClosedState等,这些方法比较简单,根据名字即可看出用意。然后,可以通过构造函数将在Close状态下最大失败次数,HalfOpen状态下使用的最大连续成功次数,以及Open状态下的超时时间通过构造函数传进来:publicCircuitBreaker(intfailedthreshold,intconsecutiveSuccessThreshold,TimeSpantimeout)if(failedthreshold1
26、|consecutiveSuccessThreshold1)thrownewArgumentOutOfRangeException(threshold,Thresholdshouldbegreaterthan0);if(timeout.TotalMilliseconds1)thrownewArgumentOutOfRangeException(timeout,Timeoutshouldbegreaterthan0);FailureThreshold=failedthreshold;ConsecutiveSuccessThreshold=consecutiveSuccessThreshold;T
27、imeout=timeout;MoveToClosedState();在初始状态下,熔断器切换到闭合状态。然后,可以通过AttempCall调用,传入期望执行的代理方法,该方法的执行受熔断器保护。这里使用了锁来处理并发问题。publicvoidAttemptCall(ActionprotectedCode)using(TimedLock.Lock(monitor)state.ProtectedCodeIsAboutToBeCalled();tryprotectedCode();catch(Exceptione)using(TimedLock.Lock(monitor)state.ActUpon
28、Exception(e);throw;using(TimedLock.Lock(monitor)state.ProtectedCodeHasBeenCalled();最后,提供Close和Open两个方法来手动切换当前状态。publicvoidClose()using(TimedLock.Lock(monitor)MoveToClosedState();publicvoidOpen()using(TimedLock.Lock(monitor)MoveToOpenState();六 测试以上的熔断模式,我们可以对其建立单元测试。首先我们编写几个帮助类以模拟连续执行次数:privatestatic
29、voidCallXAmountOfTimes(ActioncodeToCall,inttimesToCall)for(inti=0;itimesToCall;i+)codeToCall();以下类用来抛出特定异常:privatestaticvoidAssertThatExceptionIsThrown(Actioncode)whereT:Exceptiontrycode();catch(T)return;Assert.Fail(Expectedexceptionoftype0wasnotthrown,typeof(T).FullName);然后,使用NUnit,可以建立如下Case:TestpublicvoidClosesIfProtectedCodeSucceedsInHalfOpenState()varstub=newStub(10);/定义熔断器,失败10次进入断开状态/5秒后进入半断开状态/在半断开状态下,连续成功15次,进入闭合状态varcircuitBreaker=newCircuitBreaker(10,15,TimeSpan.FromMilliseconds(5000);Assert.That(circuitBreaker.IsClosed)
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 山东省菏泽市2025-2026学年高二语文上学期1月教学诊断检测试题
- 2026年耕地地力保护补贴发放题库
- 2026年语文现代文阅读理解训练题
- 2026年炭疽等老疫病防控与突发疫情处置问答
- 2026年灌溉系统版灌区水费计收知识试题
- 2026年新时代文明实践中心所站活动内容把关知识测试
- 2026年自然资源系统自然资源领域生态补偿机制题库
- 2026年公务员考试申论热点及解析
- 2026年2027年政府新媒体运营技能考核政策解读与政务服务发布
- 2026年校园欺凌防治工作考核知识
- 大学试用期考核管理办法
- 江苏棋牌室管理暂行办法
- 小学教育专业专升本试题带答案
- 聚合工艺作业培训课件
- 2024年中国烟草总公司江西省公司考试真题试卷及答案
- 2025年苏州市中考历史试卷真题(含标准答案)
- 心血管疾病的三级预防
- 爱永在 二部合唱简谱
- 上海市浦东新区2024-2025学年高一下学期期中考试英语试卷(含答案)
- 电梯有限空间作业安全专项施工方案
- 承插型盘扣式钢管脚手架安全技术标准JGJT231-2021规范解读
评论
0/150
提交评论