粗看标题你可能感觉莫名其妙,快放开那什么「捣乱的捣乱的猴猴子」,还要放开。快放开那不急,捣乱的猴且听我说说为什么不光要放开这些捣乱的快放开那猴子,还要欢迎他们。捣乱的猴
0.背景信息
在构建高可用性软件架构领域,快放开那有个词叫「混沌工程」,捣乱的猴对应的快放开那英文是Chaos Engineering,通过 Chaos 的捣乱的猴测试,可以发现系统的快放开那潜在风险,特别对于分布式系统,捣乱的猴找出脆弱的快放开那地方进行增强,提升可用性,捣乱的猴避免系统间级联影响。快放开那
混沌工程是在分布式系统上进行实验的学科, 目的是建立对系统抵御生产环境中失控条件的源码下载能力以及信心。
大规模分布式软件系统的发展正在改变软件工程。作为一个行业,我们很快采用了提高开发灵活性和部署速度的实践。紧随着这些优点的一个迫切问题是:我们对投入生产的复杂系统有多少信心?
即使分布式系统中的所有单个服务都正常运行, 这些服务之间的交互也会导致不可预知的结果。 这些不可预知的结果, 由影响生产环境的罕见且破坏性的事件复合而成,令这些分布式系统存在内在的混沌。
https://principlesofchaos.org/zh/
后来Netflix 开源了其关于混沌工程的实现 ChaosMonkey,以猴子的形象来代表在系统里出其不意的破坏者。
比如
机器或者一个机房挂了 一部分网络延迟严重 CPU、内存占用严重 随机让某些服务异常或者响应延迟再看Chaos 原则里提到的这些:
当服务不可用时的不正确回滚设置; 不当的超时设置导致的重试风暴; 由于下游依赖的流量过载导致的服务中断; 单点故障时的级联失败等。云南idc服务商我们自己在代码层面,在部署层面仅能关注应用的功能正常,但上述这些意想不到的出错,是我们在代码层面不太容易控制,也不易去测试的。
而ChaosMonkey 就是用来做这个的。所以,对于这些捣乱的猴子,我们是应该欢迎的,是不是像犀牛鸟之于犀牛?
关于ChaosMonkey,各个语言,各个公司也都有一些实现,其中Netflix的最出名。是go语言实现的。
在 Java Spring Boot 技术栈中,我发现一个容易理解和上手的实现。
https://github.com/codecentric/chaos-monkey-spring-boot
我们一起来看下如何上手以及它是怎样实现的云服务器提供商。
1. 上手
添加maven 依赖
<dependency> <groupId>de.codecentric</groupId> <artifactId>chaos-monkey-spring-boot</artifactId> <version>2.3.0-SNAPSHOT</version> </dependency>application.yml 中增加关于chaosmonkey的配置:
chaos: monkey: enabled: true assaults: level: 1 latencyRangeStart: 1000 latencyRangeEnd: 10000 exceptionsActive: true killApplicationActive: true watcher: repository: true controller: true # restController: true # service: true应用启动时,记得激活chaosmonkey的配置:
java -jar your-app.jar --spring.profiles.active=chaos-monkey再去请求你应用的controller,是不是发现异常产生了?这就是猴子在努力的捣乱中...
关于上面这些配置,再简单解释下:
你会发现chaos - monkey 配置下,除了 enabled,还有两项比较大的配置项,一个是Assault,一个是Watcher。
其中Assault代表是搞什么破坏,比如破坏类型有超时、内存占用、杀死进程、抛出异常等等
Latency Assault Exception Assault AppKiller Assault Memory Assault而Watcher 表示都要在哪些地方搞破坏。一个是What,一个是Where。
Watcher支持多种类型,比如Spring 常用的组件:
@Controller @RestController @Service @Repository @Component那你说都 What 和 Where 了,怎么没有When?还真有Level就是。
chaos.monkey.enabled 用来打开和关闭ChaosMonkey。对应的配置中,除了设置Assault之外,不同的Assault也可以设置攻击的频率,配置项是chaos.monkey.assaults.level比如1代表每次请求都攻击,10代表每十次请求攻击一次。
chaos.monkey.assaults.latencyRangeStart 和chaos.monkey.assaults.latencyRangeEnd 这两个配置项用来配置LatencyAssault这个攻击的延迟时间值范围。
如下图所示,实际部署之后,每个ChaosMonkey会藏身于各个服务中,出其不意进行攻击。
这下子配置和使用就明白了。我们再来看看实现。
2.实现原理
aaa实际我们想一下,前面配置Watcher,后面决定进行攻击,那必须得是Watcher把它拦下来再攻击,所以在Spring 里拦截常用的,就是它:AOP。
原理如图所示:
以Controller 的拦截为例
/** @author Benjamin Wilms */ @Aspect @AllArgsConstructor @Slf4j public class SpringControllerAspect extends ChaosMonkeyBaseAspect { private final ChaosMonkeyRequestScope chaosMonkeyRequestScope; private MetricEventPublisher metricEventPublisher; private WatcherProperties watcherProperties; @Pointcut("within(@org.springframework.stereotype.Controller *)") public void classAnnotatedWithControllerPointcut() { } @Around( "classAnnotatedWithControllerPointcut() && allPublicMethodPointcut() && !classInChaosMonkeyPackage()") public Object intercept(ProceedingJoinPoint pjp) throws Throwable { if (watcherProperties.isController()) { log.debug("Watching public method on controller class: { }", pjp.getSignature()); if (metricEventPublisher != null) { metricEventPublisher.publishMetricEvent( calculatePointcut(pjp.toShortString()), MetricType.CONTROLLER); } MethodSignature signature = (MethodSignature) pjp.getSignature(); chaosMonkeyRequestScope.callChaosMonkey(createSignature(signature)); } return pjp.proceed(); } public void callChaosMonkey(String simpleName) { if (isEnabled() && isTrouble()) { if (metricEventPublisher != null) { metricEventPublisher.publishMetricEvent(MetricType.APPLICATION_REQ_COUNT, "type", "total"); } // Custom watched services can be defined at runtime, if there are any, only // these will be attacked! if (chaosMonkeySettings.getAssaultProperties().isWatchedCustomServicesActive()) { if (chaosMonkeySettings .getAssaultProperties() .getWatchedCustomServices() .contains(simpleName)) { // only all listed custom methods will be attacked chooseAndRunAttack(); } } else { // default attack if no custom watched service is defined chooseAndRunAttack(); } } }这里是 Controller AOP的代码,基本没门槛。先判断 Controller 的开关是否打开,然后再看是否需要事件通知,紧接着,就是重头戏,召唤 Chaos Monkey 来搞破坏了。
注意这里,从激活的几种攻击方式里,选择一种去调用。
private void chooseAndRunAttack() { List<ChaosMonkeyAssault> activeAssaults = 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,就是要执行延迟攻击,此时,会生成一个随机的延迟时间
public void attack() { Logger.debug("Chaos Monkey - timeout"); atomicTimeoutGauge.set(determineLatency()); // metrics if (metricEventPublisher != null) { metricEventPublisher.publishMetricEvent(MetricType.LATENCY_ASSAULT); metricEventPublisher.publishMetricEvent(MetricType.LATENCY_ASSAULT, atomicTimeoutGauge); } assaultExecutor.execute(atomicTimeoutGauge.get()); }然后把这个值传在线程池中进行这个时间的
sleep。 assaultExecutor.execute(atomicTimeoutGauge.get());
public class LatencyAssaultExecutor implements ChaosMonkeyLatencyAssaultExecutor { public void execute(long durationInMillis) { try { Thread.sleep(durationInMillis); } catch (InterruptedException e) { } } }Exception攻击
再来看Exception 攻击,攻击的时候,则是构造一个Exception 直接抛出
@Override public void attack() { Logger.info("Chaos Monkey - exception"); AssaultException assaultException = this.settings.getAssaultProperties().getException(); assaultException.throwExceptionInstance(); } @SneakyThrows public void throwExceptionInstance() { Exception instance; try { Class<? extends Exception> exceptionClass = getExceptionClass(); if (arguments == null) { Constructor<? extends Exception> constructor = exceptionClass.getConstructor(); instance = constructor.newInstance(); } else { Constructor<? extends Exception> constructor = exceptionClass.getConstructor(this.getExceptionArgumentTypes().toArray(new Class[0])); instance = constructor.newInstance(this.getExceptionArgumentValues().toArray(new Object[0])); } } catch (ReflectiveOperationException e) { Logger.warn( "Cannot instantiate the class for provided type: { }. Fallback: Throw RuntimeException", type); instance = new RuntimeException("Chaos Monkey - RuntimeException"); } throw instance; // 哈哈,直接抛出 }KillApp 就直接执行应用的退出操作,System.exit.
本文转载自微信公众号「 Tomcat那些事儿」,可以通过以下二维码关注。转载本文请联系 Tomcat那些事儿公众号。