|
这是第343篇不掺水的,想要了解更多,请戳下方卡片关注我们吧~一、认识 HystrixHystrix 是 Netflix 开源的一款容错框架,包含常用的容错方法:线程池隔离、信号量隔离、熔断、降级回退。在高并发访问下,系统所依赖的服务的稳定性对系统的影响非常大,依赖有很多不可控的因素,比如网络连接变慢,资源突然繁忙,暂时不可用,服务脱机等。我们要构建稳定、可靠的分布式系统,就必须要有这样一套容错方法。二、Hystrix 解决了什么问题复杂分布式体系结构中的应用程序有几十个依赖项,每个依赖项都不可避免地会在某个时刻失败。如果主机应用程序没有与这些外部故障隔离开来,那么它就有被这些故障摧毁的风险。 例如,对于一个依赖 30 个服务的应用程序,其中每个服务都有 99.99% 的正常运行时间,您可以期待以下内容:99.9930 = 99.7% 正常运行时间 10 亿次请求中的 0.3% = 3000000 次失败 2 小时以上的停机时间/月,即使所有依赖项都具有良好的正常运行时间。现实情况通常更糟。 即使所有依赖关系都表现良好,如果不对整个系统进行弹性设计,数十项服务中每项服务的 0.01% 停机时间的总影响也相当于每月可能停机数小时。 当一切正常时,请求流可能如下所示:当许多后端系统中的一个变得潜在时,它可以阻止整个用户请求:在高流量的情况下,一个潜在的后端依赖可能会导致所有服务器上的所有资源在几秒钟内饱和。应用程序中通过网络或进入客户端库可能导致网络请求的每一点都是潜在故障的根源。比故障更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,从而备份队列、线程和其他系统资源,从而导致系统中更多的级联故障。当通过第三方客户端执行网络访问时,这些问题会加剧。第三方客户就是一个“黑匣子”,其中实施细节被隐藏,并且可以随时更改,网络或资源配置对于每个客户端库都是不同的,通常难以监视和 更改。通过的故障包括:网络连接失败或降级。 服务和服务器失败或变慢。 新的库或服务部署会改变行为或性能特征。 客户端库有错误。所有这些都代表需要隔离和管理的故障和延迟,以便单个故障依赖关系不能导致整个应用程序或系统的故障。三、Hystrix 是怎么实现它的设计目标的?当您使用 Hystrix 包装每个底层依赖项时,上图所示的体系结构如下图所示。 每个依赖关系彼此隔离,在延迟发生时可以饱和的资源受到限制,迅速执行 fallback 的逻辑,该逻辑决定了在依赖关系中发生任何类型的故障时会做出什么响应:四、业务场景使用 Hystrix (熔断器组件)来进行 TOMCAT 线程池的隔离1、线程池的隔离1.1线程隔离依赖隔离是 Hystrix 的核心目的。依赖隔离其实就是资源隔离,把对依赖使用的资源隔离起来,统一控制和调度。那为什么需要把资源隔离起来呢?主要有以下几点:合理分配资源,把给资源分配的控制权交给用户,某一个依赖的故障不会影响到其他的依赖调用,访问资源也不受影响。可以方便的指定调用策略,比如超时异常,熔断处理。对依赖限制资源也是对下游依赖起到一个保护作用,避免大量的并发请求在依赖服务有问题的时候造成依赖服务瘫痪或者更糟的雪崩效应。对依赖调用进行封装有利于对调用的监控和分析,类似于 hystrix-dashboard 的使用。Hystrix 提供了两种依赖隔离方式:线程池隔离 和 信号量隔离。如下图,线程池隔离,Hystrix 可以为每一个依赖建立一个线程池,使之和其他依赖的使用资源隔离,同时限制他们的并发访问和阻塞扩张。每个依赖可以根据权重分配资源(这里主要是线程),每一部分的依赖出现了问题,也不会影响其他依赖的使用资源。1.2线程池隔离如果简单的使用异步线程来实现依赖调用会有如下问题:1.2.1 线程的创建和销毁;1.2.2 线程上下文空间的切换,用户态和内核态的切换带来的性能损耗。使用线程池的方式可以解决第一种问题,但是第二个问题计算开销是不能避免的。Netflix在使用过程中详细评估了使用异步线程和同步线程带来的性能差异,结果表明在 99% 的情况下,异步线程带来的几毫秒延迟的完全可以接受的。1.3线程池隔离的优缺点优点:一个依赖可以给予一个线程池,这个依赖的异常不会影响其他的依赖。使用线程可以完全隔离第三方代码,请求线程可以快速放回。当一个失败的依赖再次变成可用时,线程池将清理,并立即恢复可用,而不是一个长时间的恢复。可以完全模拟异步调用,方便异步编程。使用线程池,可以有效的进行实时监控、统计和封装。缺点:使用线程池的缺点主要是增加了计算的开销。每一个依赖调用都会涉及到队列,调度,上下文切换,而这些操作都有可能在不同的线程中执行。2、整合 hystrix 组件整体流程2.1 POM依赖de.ahus1.prometheus.hystrixprometheus-hystrix4.1.0com.netflix.hystrixhystrix-core1.5.18com.netflix.hystrixhystrix-metrics-event-stream1.5.18com.netflix.hystrixhystrix-javanica1.5.182.2 Hystrix 生效2.2.1 HystrixCommonRequestAspectimportcom.netflix.hystrix.HystrixCommandGroupKey;importcom.netflix.hystrix.HystrixThreadPoolKey;importcom.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;importlombok.extern.slf4j.Slf4j;importorg.aspectj.lang.ProceedingJoinPoint;importorg.aspectj.lang.annotation.Around;importorg.aspectj.lang.annotation.Aspect;importorg.aspectj.lang.reflect.MethodSignature;importorg.springframework.core.annotation.Order;importorg.springframework.stereotype.Component;importorg.springframework.web.bind.annotation.DeleteMapping;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.PatchMapping;importorg.springframework.web.bind.annotation.PostMapping;importorg.springframework.web.bind.annotation.PutMapping;importorg.springframework.web.bind.annotation.RequestMapping;importjava.lang.annotation.Annotation;importjava.lang.reflect.Method;@Slf4j@Aspect@Order(1)@ComponentpublicclassHystrixCommonRequestAspect{@Around(value="(within(@org.springframework.stereotype.Controller*)||within(@org.springframework.web.bind.annotation.RestController*))&@annotation(requestMapping)")publicObjectrequestMappingAround(ProceedingJoinPointjoinPoint,RequestMappingrequestMapping)throwsThrowable{returnhandleRequest(joinPoint,requestMapping);}@Around(value="(within(@org.springframework.stereotype.Controller*)||within(@org.springframework.web.bind.annotation.RestController*))&@annotation(getMapping)")publicObjectgetMappingAround(ProceedingJoinPointjoinPoint,GetMappinggetMapping)throwsThrowable{returnhandleRequest(joinPoint,getMapping);}@Around(value="(within(@org.springframework.stereotype.Controller*)||within(@org.springframework.web.bind.annotation.RestController*))&@annotation(postMapping)")publicObjectpostMappingAround(ProceedingJoinPointjoinPoint,PostMappingpostMapping)throwsThrowable{returnhandleRequest(joinPoint,postMapping);}@Around(value="(within(@org.springframework.stereotype.Controller*)||within(@org.springframework.web.bind.annotation.RestController*))&@annotation(putMapping)")publicObjectputMappingAround(ProceedingJoinPointjoinPoint,PutMappingputMapping)throwsThrowable{returnhandleRequest(joinPoint,putMapping);}@Around(value="(within(@org.springframework.stereotype.Controller*)||within(@org.springframework.web.bind.annotation.RestController*))&@annotation(deleteMapping)")publicObjectputMappingAround(ProceedingJoinPointjoinPoint,DeleteMappingdeleteMapping)throwsThrowable{returnhandleRequest(joinPoint,deleteMapping);}@Around(value="(within(@org.springframework.stereotype.Controller*)||within(@org.springframework.web.bind.annotation.RestController*))&@annotation(patchMapping)")publicObjectputMappingAround(ProceedingJoinPointjoinPoint,PatchMappingpatchMapping)throwsThrowable{returnhandleRequest(joinPoint,patchMapping);}privateObjecthandleRequest(ProceedingJoinPointjoinPoint,Annotationmapping)throwsThrowable{if(hasHystrixCommand(joinPoint)){if(log.isDebugEnabled()){log.debug("当前请求有自定义的command,使用自定义的command");}returnjoinPoint.proceed();}else{if(log.isDebugEnabled()){log.debug("当前请求没有自定义的command,使用默认的command");}HttpProceedCommandproceedCommand=newHttpProceedCommand();proceedCommand.setJoinPoint(joinPoint);returnproceedCommand.execute();}}publicstaticclassHttpProceedCommandextendscom.netflix.hystrix.HystrixCommand{privateProceedingJoinPointjoinPoint;publicProceedingJoinPointgetJoinPoint(){returnjoinPoint;}publicHttpProceedCommand(){super(HystrixCommandGroupKey.Factory.asKey("HttpProceedCommand"),HystrixThreadPoolKey.Factory.asKey("HttpProceedCommandThreadPool"));}publicvoidsetJoinPoint(ProceedingJoinPointjoinPoint){this.joinPoint=joinPoint;}@OverrideprotectedObjectrun()throwsException{try{returnjoinPoint.proceed();}catch(Throwablee){thrownewRuntimeException(e);}}}privatebooleanhasHystrixCommand(ProceedingJoinPointjoinPoint){MethodSignaturemethodSignature=(MethodSignature)joinPoint.getSignature();Methodmethod=methodSignature.getMethod();HystrixCommandhystrixCommand=method.getAnnotation(HystrixCommand.class);returnhystrixCommand!=null;}}2.2.2 HystrixCommand 注解@ConfigurationpublicclassHystrixConfig{@ResourceprivateCollectorRegistryregistry;/***用来拦截处理HystrixCommand注解*@return*/@BeanpublicHystrixCommandAspecthystrixCommandAspect(){HystrixPlugins.getInstance().registerCommandExecutionHook(newMyHystrixHook());HystrixPrometheusMetricsPublisher.builder().withRegistry(registry).buildAndRegister();returnnewHystrixCommandAspect();}/***用来向监控中心Dashboard发送stream信息*@return*/@BeanpublicServletRegistrationBeanhystrixMetricsStreamServlet(){ServletRegistrationBeanregistration=newServletRegistrationBean(newHystrixMetricsStreamServlet());registration.addUrlMappings("/hystrix.stream");returnregistration;}}2.2.3 HystrixCommand 注解参数:commandKey: 代表了一类 command,一般来说,代表了底层的依赖服务的一个接口threadPoolKey: 代表使用的线程池 KEY,相同的 threadPoolKey 会使用同一个线程池ignoreExceptions: 调用服务时,除了 HystrixBadRequestException 之外,其他 @HystrixCommand 修饰的函数抛出的异常均会被Hystrix 认为命令执行失败而触发服务降级的处理逻辑 (调用 fallbackMethod 指定的回调函数),所以当需要在命令执行中抛出不触发降级的异常时来使用它,通过这个参数指定,哪些异常抛出时不触发降级(不去调用 fallbackMethod ),而是将异常向上抛出。**fallbackMethod **: 降级使用的方法,需要在同一个类中@PostMapping("/test")@HystrixCommand(commandKey="testCommandKey",threadPoolKey="testThreadPool",ignoreExceptions={RuntimeException.class},fallbackMethod="testHystrixFail")publicStringtest(){System.out.println("test");return"测试";}降级方法publicStringtestHystrixFail(){return"进入降级方法";}对重要数据进行缓存/***首页的查询缓存,缓存48个小时;做降级策略使用*时间小于24h时,进行更新缓存*/publicstaticvoidsetUpHystrixCache(Tt,Stringkey){Longttl=jedisClientUtil.ttl(key);Longday=169200L;if(ttltraceIdVariable=newHystrixRequestVariableDefault();@OverridepublicvoidonStart(HystrixInvokablecommandInstance){HystrixRequestContext.initializeContext();traceIdVariable.set(TraceIdUtil.getCurrentTraceId());}@OverridepublicExceptiononError(HystrixInvokablecommandInstance,HystrixRuntimeException.FailureTypefailureType,Exceptione){HystrixRequestContext.getContextForCurrentThread().shutdown();returnsuper.onError(commandInstance,failureType,e);}@OverridepublicvoidonSuccess(HystrixInvokablecommandInstance){HystrixRequestContext.getContextForCurrentThread().shutdown();super.onSuccess(commandInstance);}@OverridepublicvoidonExecutionStart(HystrixInvokablecommandInstance){TraceIdUtil.initTraceId(traceIdVariable.get());}@OverridepublicvoidonFallbackStart(HystrixInvokablecommandInstance){TraceIdUtil.initTraceId(traceIdVariable.get());}}然后在合适的位置注册:HystrixPlugins.getInstance().registerCommandExecutionHook(newMyHystrixHook());4.2 通过注解的方式可以设置降级方法的 **ignoreExceptions 参数,**拦截器的方式无法设置解决方案publicstaticclassHttpProceedCommandextendscom.netflix.hystrix.HystrixCommand{privateProceedingJoinPointjoinPoint;publicProceedingJoinPointgetJoinPoint(){returnjoinPoint;}publicHttpProceedCommand(){super(HystrixCommandGroupKey.Factory.asKey("HttpProceedCommand"),HystrixThreadPoolKey.Factory.asKey("HttpProceedCommandThreadPool"));}publicvoidsetJoinPoint(ProceedingJoinPointjoinPoint){this.joinPoint=joinPoint;}@OverrideprotectedObjectrun()throwsException{try{returnjoinPoint.proceed();}catch(Throwablee){if(einstanceofRuntimeException){throw(Exception)e;}else{thrownewHystrixBadRequestException(e.getMessage(),e);}}}}在合适的地方抛出 HystrixBadRequestExceptionHystrixBadRequestException 用提供的参数或状态表示错误而不是执行失败的异常。与HystrixCommand抛出的所有其他异常不同,这不会触发回退,不会计算故障指标,因此不会触发断路器。4.3 HystrixRuntimeException: Command fallback execution rejected执行错误了,本应该去执行 fallback 方法,可是却被 reject 了,为什么呢?这种情况下,一般来说是 command 已经熔断了,所有请求都进入 fallback 导致的,因为 fallback 默认是有个并发最大处理的限制,fallback.isolation.semaphore.maxConcurrentRequests,默认是10,这个方法及时很简单,处理很快,可是QPS如果很高,还是很容易达到10这个阈值,导致后面的被拒绝。解决方法也很简单:fallback 尽可能的简单,不要有耗时操作,如果用一个 http 接口来作为另一个 http 接口的降级处理,那你必须考虑这个 http 是不是也会失败;可以适当增大fallback.isolation.semaphore.maxConcurrentRequests4.4 时间较长的接口是否应该进行中断根据实际场景来判断 可通过hystrix.command.[command].execution.isolation.thread.interruptOnTimeout = false来配置关闭5、注意问题需要注意本身就是耗时的请求下载请求上传请求6、Hystrix配置command 和 pool 和 collapser 的配置参数HystrixCommandProperties 命令执行相关配置:hystrix.command.[commandkey].execution.isolation.strategy隔离策略THREAD或SEMAPHORE默认HystrixCommands使用THREAD方式HystrixObservableCommands使用SEMAPHOREhystrix.command.[commandkey].execution.timeout.enabled是否开启超时设置,默认true。hystrix.command.[commandkey].execution.isolation.thread.timeoutInMilliseconds默认超时时间默认1000mshystrix.command.[commandkey].execution.isolation.thread.interruptOnTimeout是否打开超时线程中断默认值truehystrix.command.[commandkey].execution.isolation.thread.interruptOnFutureCancel当隔离策略为THREAD时,当执行线程执行超时时,是否进行中断处理,即Future#cancel(true)处理,默认为false。hystrix.command.[commandkey].execution.isolation.semaphore.maxConcurrentRequests信号量最大并发度默认值10该参数当使用ExecutionIsolationStrategy.SEMAPHORE策略时才有效。如果达到最大并发请求数,请求会被拒绝。理论上选择semaphoresize的原则和选择threadsize一致,但选用semaphore时每次执行的单元要比较小且执行速度快(ms级别),否则的话应该用thread。hystrix.command.[commandkey].fallback.isolation.semaphore.maxConcurrentRequestsfallback方法的信号量配置,配置getFallback方法并发请求的信号量,如果请求超过了并发信号量限制,则不再尝试调用getFallback方法,而是快速失败,默认信号量为10。hystrix.command.[commandkey].fallback.enabled是否启用降级处理,如果启用了,则在超时或异常时调用getFallback进行降级处理,默认开启。hystrix.command.[commandkey].circuitBreaker.enabled是否开启熔断机制,默认为true。hystrix.command.[commandkey].circuitBreaker.forceOpen强制开启熔断,默认为false。hystrix.command.[commandkey].circuitBreaker.forceClosed强制关闭熔断,默认为false。hystrix.command.[commandkey].circuitBreaker.sleepWindowInMilliseconds熔断窗口时间,默认为5s。hystrix.command.[commandkey].circuitBreaker.requestVolumeThreshold当在配置时间窗口内达到此数量后的失败,进行短路。默认20个hystrix.command.[commandkey].circuitBreaker.errorThresholdPercentage出错百分比阈值,当达到此阈值后,开始短路。默认50%hystrix.command.[commandkey].metrics.rollingStats.timeInMilliseconds设置统计滚动窗口的长度,以毫秒为单位。用于监控和熔断器默认10shystrix.command.[commandkey].metrics.rollingStats.numBuckets设置统计窗口的桶数量默认10hystrix.command.[commandkey].metrics.rollingPercentile.enabled设置执行时间是否被跟踪,并且计算各个百分比,50%,90%等的时间默认truehystrix.command.[commandkey].metrics.rollingPercentile.timeInMilliseconds设置执行时间在滚动窗口中保留时间,用来计算百分比默认60000mshystrix.command.[commandkey].metrics.rollingPercentile.numBuckets设置rollingPercentile窗口的桶数量默认6。hystrix.command.[commandkey].metrics.rollingPercentile.bucketSize此属性设置每个桶保存的执行时间的最大值默认100。如果bucketsize=100,window=10s,若这10s里有500次执行,只有最后100次执行会被统计到bucket里去。增加该值会增加内存开销以及排序的开销。hystrix.command.[commandkey].metrics.healthSnapshot.intervalInMilliseconds记录health快照(用来统计成功和错误绿)的间隔,默认500mshystrix.command.[commandkey].requestCache.enabled设置是否缓存请求,request-scope内缓存默认值truehystrix.command.[commandkey].requestLog.enabled设置HystrixCommand执行和事件是否打印到HystrixRequestLog中默认值truehystrix.command.[commandkey].threadPoolKeyOverride命令的线程池key,决定该命令使用哪个线程池。HystrixThreadPoolProperties线程池相关配置:hystrix.threadpool.[threadkey].coreSize线程池核心线程数默认值10;hystrix.threadpool.[threadkey].maximumSize线程池最大线程数默认值10;hystrix.threadpool.[threadkey].allowMaximumSizeToDivergeFromCoreSize当线程数大于核心线程数时,是否需要回收。与keepAliveTimeMinutes配合使用。hystrix.threadpool.[threadkey].keepAliveTimeMinutes当实际线程数超过核心线程数时,线程存活时间默认值1minhystrix.threadpool.[threadkey].maxQueueSize最大等待队列数默认不开启使用SynchronousQueue不可动态调整hystrix.threadpool.[threadkey].queueSizeRejectionThreshold允许在队列中的等待的任务数量默认值5hystrix.threadpool.[threadkey].metrics.rollingStats.timeInMilliseconds设置统计滚动窗口的长度,以毫秒为单位默认值10000。hystrix.threadpool.[threadkey].metrics.rollingStats.numBuckets设置统计窗口的桶数量默认10HystrixCollapserProperties批处理相关配置:hystrix.collapser.[collapserKey].maxRequestsInBatch单次批处理的最大请求数,达到该数量触发批处理,默认Integer.MAX_VALUEhystrix.collapser.[collapserKey].timerDelayInMilliseconds触发批处理的延迟,也可以为创建批处理的时间+该值,默认值10hystrix.collapser.[collapserKey].requestCache.enabled默认值truehystrix.collapser.[collapserKey].metrics.rollingStats.timeInMilliseconds默认值10000hystrix.collapser.[collapserKey].metrics.rollingStats.numBuckets默认值10hystrix.collapser.[collapserKey].metrics.rollingPercentile.enabled默认值truehystrix.collapser.[collapserKey].metrics.rollingPercentile.timeInMilliseconds默认值60000hystrix.collapser.[collapserKey].metrics.rollingPercentile.numBuckets默认值6hystrix.collapser.[collapserKey].metrics.rollingPercentile.bucketSize默认值100五、总结以上文档是借鉴互联网经验和项目接入经验总结而来,相关配置仅供参考,具体配置请以实际情况而定。参考 : https://github.com/Netflix/Hystrix/wiki#what看完两件事如果你觉得这篇内容对你挺有启发,我想邀请你帮我两件小事1.点个「在看」,让更多人也能看到这篇内容(点了「在看」,bug -1 )2.关注公众号「政采云技术」,持续为你推送精选好文招贤纳士政采云技术团队(Zero),Base 杭州,一个富有激情和技术匠心精神的成长型团队。规模 500 人左右,在日常业务开发之外,还分别在云原生、区块链、人工智能、低代码平台、中间件、大数据、物料体系、工程平台、性能体验、可视化等领域进行技术探索和实践,推动并落地了一系列的内部技术产品,持续探索技术的新边界。此外,团队还纷纷投身社区建设,目前已经是 google flutter、scikit-learn、Apache Dubbo、Apache Rocketmq、Apache Pulsar、CNCF Dapr、Apache DolphinScheduler、alibaba Seata 等众多优秀开源社区的贡献者。如果你想改变一直被事折腾,希望开始折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊……如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的技术团队的成长过程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 zcy-tc@cai-inc.com
|
|