快放开那些捣乱的猴子!

开发 架构
粗看标题你可能感觉莫名其妙,什么「捣乱的猴子」,还要放开。不急,且听我说说为什么不光要放开这些捣乱的猴子,还要欢迎他们。

 

[[357021]]

粗看标题你可能感觉莫名其妙,什么「捣乱的猴子」,还要放开。不急,且听我说说为什么不光要放开这些捣乱的猴子,还要欢迎他们。

0.背景信息

在构建高可用性软件架构领域,有个词叫「混沌工程」,对应的英文是Chaos Engineering,通过 Chaos 的测试,可以发现系统的潜在风险,特别对于分布式系统,找出脆弱的地方进行增强,提升可用性,避免系统间级联影响。

混沌工程是在分布式系统上进行实验的学科, 目的是建立对系统抵御生产环境中失控条件的能力以及信心。

大规模分布式软件系统的发展正在改变软件工程。作为一个行业,我们很快采用了提高开发灵活性和部署速度的实践。紧随着这些优点的一个迫切问题是:我们对投入生产的复杂系统有多少信心?

即使分布式系统中的所有单个服务都正常运行, 这些服务之间的交互也会导致不可预知的结果。 这些不可预知的结果, 由影响生产环境的罕见且破坏性的事件复合而成,令这些分布式系统存在内在的混沌。

https://principlesofchaos.org/zh/

后来Netflix 开源了其关于混沌工程的实现 ChaosMonkey,以猴子的形象来代表在系统里出其不意的破坏者。

比如

  • 机器或者一个机房挂了
  • 一部分网络延迟严重
  • CPU、内存占用严重
  • 随机让某些服务异常或者响应延迟

再看Chaos 原则里提到的这些:

  • 当服务不可用时的不正确回滚设置;
  • 不当的超时设置导致的重试风暴;
  • 由于下游依赖的流量过载导致的服务中断;
  • 单点故障时的级联失败等。

我们自己在代码层面,在部署层面仅能关注应用的功能正常,但上述这些意想不到的出错,是我们在代码层面不太容易控制,也不易去测试的。

而ChaosMonkey 就是用来做这个的。所以,对于这些捣乱的猴子,我们是应该欢迎的,是不是像犀牛鸟之于犀牛?

关于ChaosMonkey,各个语言,各个公司也都有一些实现,其中Netflix的最出名。是go语言实现的。

在 Java Spring Boot 技术栈中,我发现一个容易理解和上手的实现。

https://github.com/codecentric/chaos-monkey-spring-boot

我们一起来看下如何上手以及它是怎样实现的。

1. 上手

添加maven 依赖

  1. <dependency> 
  2.   <groupId>de.codecentric</groupId> 
  3.   <artifactId>chaos-monkey-spring-boot</artifactId> 
  4.   <version>2.3.0-SNAPSHOT</version> 
  5. </dependency> 

application.yml 中增加关于chaosmonkey的配置:

  1. chaos: 
  2.   monkey: 
  3.     enabled: true 
  4.     assaults: 
  5.       level: 1 
  6.       latencyRangeStart: 1000 
  7.       latencyRangeEnd: 10000 
  8.       exceptionsActive: true 
  9.       killApplicationActive: true 
  10.     watcher: 
  11.       repository: true 
  12.       controller: true 
  13. #      restController: true 
  14. #      service: true 

应用启动时,记得激活chaosmonkey的配置:

  1. 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 的拦截为例

  1. /** @author Benjamin Wilms */ 
  2. @Aspect 
  3. @AllArgsConstructor 
  4. @Slf4j 
  5. public class SpringControllerAspect extends ChaosMonkeyBaseAspect { 
  6.   private final ChaosMonkeyRequestScope chaosMonkeyRequestScope; 
  7.  
  8.   private MetricEventPublisher metricEventPublisher; 
  9.  
  10.   private WatcherProperties watcherProperties; 
  11.  
  12.   @Pointcut("within(@org.springframework.stereotype.Controller *)"
  13.   public void classAnnotatedWithControllerPointcut() {} 
  14.  
  15.   @Around( 
  16.       "classAnnotatedWithControllerPointcut() && allPublicMethodPointcut() && !classInChaosMonkeyPackage()"
  17.   public Object intercept(ProceedingJoinPoint pjp) throws Throwable { 
  18.  
  19.     if (watcherProperties.isController()) { 
  20.       log.debug("Watching public method on controller class: {}", pjp.getSignature()); 
  21.  
  22.       if (metricEventPublisher != null) { 
  23.         metricEventPublisher.publishMetricEvent( 
  24.             calculatePointcut(pjp.toShortString()), MetricType.CONTROLLER); 
  25.       } 
  26.  
  27.       MethodSignature signature = (MethodSignature) pjp.getSignature(); 
  28.  
  29.       chaosMonkeyRequestScope.callChaosMonkey(createSignature(signature)); 
  30.     } 
  31.     return pjp.proceed(); 
  32.   } 
  1. public void callChaosMonkey(String simpleName) { 
  2.     if (isEnabled() && isTrouble()) { 
  3.  
  4.       if (metricEventPublisher != null) { 
  5.         metricEventPublisher.publishMetricEvent(MetricType.APPLICATION_REQ_COUNT, "type""total"); 
  6.       } 
  7.  
  8.       // Custom watched services can be defined at runtime, if there are anyonly 
  9.       // these will be attacked! 
  10.       if (chaosMonkeySettings.getAssaultProperties().isWatchedCustomServicesActive()) { 
  11.         if (chaosMonkeySettings 
  12.             .getAssaultProperties() 
  13.             .getWatchedCustomServices() 
  14.             .contains(simpleName)) { 
  15.           // only all listed custom methods will be attacked 
  16.           chooseAndRunAttack(); 
  17.         } 
  18.       } else { 
  19.         // default attack if no custom watched service is defined 
  20.         chooseAndRunAttack(); 
  21.       } 
  22.     } 
  23.   } 

这里是 Controller AOP的代码,基本没门槛。先判断 Controller 的开关是否打开,然后再看是否需要事件通知,紧接着,就是重头戏,召唤 Chaos Monkey 来搞破坏了。

注意这里,从激活的几种攻击方式里,选择一种去调用。

  1. private void chooseAndRunAttack() { 
  2.     List<ChaosMonkeyAssault> activeAssaults = 
  3.         assaults.stream().filter(ChaosMonkeyAssault::isActive).collect(Collectors.toList()); 
  4.     if (isEmpty(activeAssaults)) { 
  5.       return
  6.     } 
  7.     getRandomFrom(activeAssaults).attack();  // 注意这里,从激活的几种攻击方式里,选择一种去调用。 
  8.  
  9.     if (metricEventPublisher != null) { 
  10.       metricEventPublisher.publishMetricEvent( 
  11.           MetricType.APPLICATION_REQ_COUNT, "type""assaulted"); 
  12.     } 
  13.   } 

延迟攻击

比如LatencyAssault,就是要执行延迟攻击,此时,会生成一个随机的延迟时间

  1. public void attack() { 
  2.     Logger.debug("Chaos Monkey - timeout"); 
  3.  
  4.     atomicTimeoutGauge.set(determineLatency()); 
  5.  
  6.     // metrics 
  7.     if (metricEventPublisher != null) { 
  8.       metricEventPublisher.publishMetricEvent(MetricType.LATENCY_ASSAULT); 
  9.       metricEventPublisher.publishMetricEvent(MetricType.LATENCY_ASSAULT, atomicTimeoutGauge); 
  10.     } 
  11.  
  12.     assaultExecutor.execute(atomicTimeoutGauge.get()); 
  13.   } 

然后把这个值传在线程池中进行这个时间的

sleep。 assaultExecutor.execute(atomicTimeoutGauge.get());

  1. public class LatencyAssaultExecutor implements ChaosMonkeyLatencyAssaultExecutor { 
  2.  public void execute(long durationInMillis) { 
  3.    try { 
  4.      Thread.sleep(durationInMillis); 
  5.   } catch (InterruptedException e) { 
  6.   } 

Exception攻击

再来看Exception 攻击,攻击的时候,则是构造一个Exception 直接抛出

  1. @Override 
  2.   public void attack() { 
  3.     Logger.info("Chaos Monkey - exception"); 
  4.  
  5.     AssaultException assaultException = this.settings.getAssaultProperties().getException(); 
  6.     assaultException.throwExceptionInstance(); 
  7.   } 
  1. @SneakyThrows 
  2.   public void throwExceptionInstance() { 
  3.     Exception instance; 
  4.     try { 
  5.       Class<? extends Exception> exceptionClass = getExceptionClass(); 
  6.       if (arguments == null) { 
  7.         Constructor<? extends Exception> constructor = exceptionClass.getConstructor(); 
  8.         instance = constructor.newInstance(); 
  9.       } else { 
  10.         Constructor<? extends Exception> constructor = 
  11.             exceptionClass.getConstructor(this.getExceptionArgumentTypes().toArray(new Class[0])); 
  12.         instance = 
  13.             constructor.newInstance(this.getExceptionArgumentValues().toArray(new Object[0])); 
  14.       } 
  15.     } catch (ReflectiveOperationException e) { 
  16.       Logger.warn( 
  17.           "Cannot instantiate the class for provided type: {}. Fallback: Throw RuntimeException"
  18.           type); 
  19.       instance = new RuntimeException("Chaos Monkey - RuntimeException"); 
  20.     } 
  21.  
  22.     throw instance;  // 哈哈,直接抛出 
  23.   } 

KillApp 就直接执行应用的退出操作,System.exit.

本文转载自微信公众号「 Tomcat那些事儿」,可以通过以下二维码关注。转载本文请联系 Tomcat那些事儿公众号。

 

责任编辑:武晓燕 来源: Tomcat那些事儿
相关推荐

2016-08-04 13:42:25

ExcelExcel快捷键

2010-06-07 10:31:19

2010-05-13 16:48:10

网络故障

2010-08-24 13:32:20

故障排除

2010-08-24 11:13:47

2011-05-07 13:39:35

2013-07-30 10:26:37

2018-02-05 11:10:05

A站APP腾讯

2013-11-24 20:01:57

2022-04-11 07:51:53

鸭子类型猴子补丁Python

2024-04-15 00:02:00

Java补丁技术

2020-05-12 15:20:04

ifswitchJava

2023-11-10 20:28:45

ChatGPT微软OpenAI

2020-12-21 08:32:07

内存性能优化

2019-10-31 08:59:23

Python工具万圣节

2009-07-03 09:08:34

Mono微软

2022-10-27 08:31:31

架构

2011-03-14 14:36:38

ICANN顶级域名

2017-08-24 08:18:03

WindowsLinux编辑器

2012-05-31 09:53:38

点赞
收藏

51CTO技术栈公众号