简单看标题,你可能会觉得莫名其妙。你必须放开“麻烦的猴子”。不着急,让我解释一下为什么不仅要放走这些惹人厌的猴子,还要欢迎他们。0.背景资料在构建高可用软件架构领域,有一个词叫“混沌工程”,对应英文ChaosEngineering。通过混沌测试,可以发现系统的潜在风险,尤其是分布式系统,找出易受攻击的地方,进行局部增强,提高可用性,避免系统间的级联效应。混沌工程是在分布式系统上进行实验的学科,目的是建立对系统在生产环境中承受失控条件的能力的信心。大规模分布式软件系统的发展正在改变软件工程。作为一个行业,我们很快就会采用提高开发灵活性和部署速度的做法。在这些优势之后是一个紧迫的问题:我们对投入生产的复杂系统有多大信心?即使分布式系统中的所有单个服务都正常运行,这些服务之间的交互也可能导致不可预测的结果。这些不可预测的结果,再加上影响生产环境的罕见和破坏性事件,使这些分布式系统天生就很混乱。https://principlesofchaos.org/zh/后来,Netflix开源了ChaosMonkey,它实现了混沌工程,用猴子的形象来代表系统中意想不到的破坏者。比如某台机器或者机房挂了一部分网络,CPU和内存占用严重,导致一些服务异常或者响应延迟。看看混沌原理中提到的这些:服务不可用时回滚设置不正确;不正确的超时设置重试风暴;由于下游依赖项的流量过载而导致的服务中断;单点故障等级联故障。在代码层面,我们只能关注部署层面应用的功能是否正常,而上面提到的意外错误,我们在代码上是不容易控制和测试的等级。而ChaosMonkey就是用来做这件事的。因此,我们应该欢迎这些麻烦的猴子。犀牛之于犀牛,鸟之于犀牛?关于ChaosMonkey,各种语言和公司也有一些实现,其中Netflix最著名。它是用go语言实现的。在JavaSpringBoot技术栈中,我找到了一个易于理解和使用的实现。https://github.com/codecentric/chaos-monkey-spring-boot让我们看看如何开始以及它是如何实现的。1.添加maven依赖de.codecentricchaos-monkey-spring-boot2.3.0-SNAPSHOTapplication添加.yml中关于chaosmonkey的配置:chaos:monkey:enabled:trueassaults:level:1latencyRangeStart:1000latencyRangeEnd:10000exceptionsActive:truekillApplicationActive:truewatcher:repository:truecontroller:true#restController:true#service:true应用启动时,记得激活chaosmonkey配置:java-jaryour-app.jar--spring.profiles.active=chaos-monkey然后请求你应用的controller,有没有发现异常?这是猴子在捣乱……关于上面的配置,再简单说明一下:你会发现在chaos-monkey配置下,除了enabled之外,还有两个比较大的配置项,一个是Assault和the另一个是守望者。其中,Assault表示做了什么破坏,比如超时、内存占用、杀进程、抛异常等破坏类型。LatencyAssaultExceptionAssaultAppKillerAssaultMemoryAssault和Watcher表示应该在哪里进行破坏。一个是什么,另一个是哪里。Watcher支持多种类型,比如Spring常用的组件:@Controller@RestController@Service@Repository@Component那你说What和Where,为什么没有When呢?真的有水平。chaos.monkey.enabled用于打开和关闭ChaosMonkey。在相应的配置中,除了可以设置Assault之外,不同的Assault还可以设置攻击的频率。配置项为chaos.monkey.assaults.level。比如1表示每一个请求攻击一次,10表示每10个请求攻击一次。chaos.monkey.assaults.latencyRangeStart和chaos.monkey.assaults.latencyRangeEnd这两个配置项用于配置LatencyAssault攻击的延迟取值范围。如下图所示,实际部署后,每个ChaosMonkey都会隐藏在各个服务中,出其不意地出其不意。现在配置和使用就明白了。让我们再看看实现。2.实现原理aaa其实大家想一想。如果你之前配置了Watcher,之后决定攻击,必须在攻击前被Watcher停止。所以在Spring中常用拦截:AOP。原理如图所示:以Controller的拦截为例/**@authorBenjaminWilms*/@Aspect@AllArgsConstructor@Slf4jpublicclassSpringControllerAspectextendsChaosMonkeyBaseAspect{privatefinalChaosMonkeyRequestScopechaosMonkeyRequestScope;privateMetricEventPublishermetricEventPublisher;privateWatcherPropertieswatcherProperties;@Pointcut("within(@org.springframework.stereotype.Controller*)")publicvoidclassAnnotatedWithControllerPointcut(){}@Around("classAnnotatedWithControllerPointcut()&&allPublicMethodPointcut()&&!classInChaosMonkeyPackage()")publicObjectintercept(ProceedingJoinPointpjp)throwsThrowable{if(watcherProperties.isController()){log.debug("WatchingpublicMethodon:concept}getSignature());if(metricEventPublisher!=null){metricEventPublisher.publishMetricEvent(calculatePointcut(pjp.toShortString()),MetricType.CONTROLLER);}MethodSignaturesignature=(MethodSignature)pjp.getSignature();chaosMonkeyRequestScope.callChaosMon键(createSignature(签名));}returnpjp.proceed();}publicvoidcallChaosMonkey(StringsimpleName){if(isEnabled()&&isTrouble()){if(metricEventPublisher!=null){metricEventPublisher.publishMetricEvent(MetricType.APPLICATION_REQ_COUNT,“类型","total");}//Customwatchedservicescanbedefinedatruntime,iftheresany,only//thesewillbeattacked!if(chaosMonkeySettings.getAssaultProperties().isWatchedCustomServicesActive()){if(chaosMonkeySettings.getAssaultProperties().getWatchedCustomServices().contains(simpleName)){//onlyalllistedcustommethodswillbeattackedchooseAndRunAttack();}}else{//defaultattackifnocustomwatchedserviceisdefinedchooseAndRunAttack();}}}这里是ControllerAOP的代码。基本没有阈值判断Controller的开关是否打开,然后判断是否需要事件通知。那么,就是重头戏了。召唤混沌猴子进行破坏。请注意,在这里,选择一种激活的攻击方法来调用。privatevoidchooseAndRunAttack(){ListactiveAssaults=assaults.stream().filter(ChaosMonkeyAssault::isActive).collect(Collectors.toList());if(isEmpty(activeAssaults)){return;}getRandomFrom(activeAssaults).attack();//这里注意,选择激活的攻击方法中的一种调用。if(metricEventPublisher!=null){metricEventPublisher.publishMetricEvent(MetricType.APPLICATION_REQ_COUNT,"type","assaulted");}}LatencyAssault等延迟攻击就是进行延迟攻击。这时候会产生一个随机的延迟时间MetricType.LATENCY_ASSAULT);metricEventASPublisher.publishGetricEventTime(NCaul_SAULT,MetricgeSAL);}assaultExecutor.execute(atomicTimeoutGauge.get());}然后将这个值传给线程池进行本次sleep。assaultExecutor.execute(atomicTimeoutGauge.get());publicclassLatencyAssaultExecutorimplementsChaosMonkeyLatencyAssaultExecutor{publicvoidexecute(longdurationInMillis){try{Thread.sleep(durationInMillis);}catch(InterruptedExceptione){}}}Exception攻击再来看Exception的攻击,攻击是攻击一个异常直接抛出@Overridepublicvoidattack(){Logger.info("ChaosMonkey-exception");AssaultExceptionassaultException=this.settings.getAssaultProperties().getException();assaultException.throwExceptionInstance();}@SneakyThrowspublicvoidthrowExceptionInstance(){Exceptioninstance;try{ClassexceptionClass=getExceptionClass();if(arguments==null){Constructorconstructor=exceptionClass.getConstructor();instance=constructor.newInstance();}else{Constructor构造函数=exceptionClass.getConstructor(this.getExceptionArgumentTypes().toArray(newClass[0]));实例=constructor.newInstance(this.getExceptionArgumentValues().toArray(newObject[0]));}}catch(ReflectiveOperationExceptione){Logger.warn("无法为提供的类型实例化类:{}.Fallback:ThrowRuntimeException",type);instance=newRuntimeException("ChaosMonkey-RuntimeException");}throwinstance;//哈哈,直接扔}KillApp直接执行应用程序的退出操作,System.exit。事情公众号。