springcloud混沌工程实践例子(一)(hystrix + chaos monkey)

一、eureka使用

https://www.cnblogs.com/yxth/p/10845640.html

访问地址:http://127.0.0.1:8761/

 

二、chaos-monkey-spring-boot

1、chaos-monkey的目标是什么

混沌工程的原则的启发,基于springboot的分布式系统,我想测试知道应用程序的最佳表现,尤其是正在生产环境 的使用情况。

尽管写了很多单元测试和集成测试,覆盖率能够到达70%到80%,依然感觉到测试不充分,到底我们的程序在生产环境的行为是怎么样的?

很多问题得不到答案:

我们的应急预案生效吗?

网络不稳定时我们的应用是什么的行为?

如果其中一个服务挂了怎么办?

服务发现正常,但是我们的客户端负载均衡生效吗?

就你说看到那样,我们还有很多问题和话题不得不处理。

正由于这样我们深入到混沌工程并且开启一个工程区分享我们的想法和经验。

如何使用:

详见:https://codecentric.github.io/chaos-monkey-spring-boot/2.3.0/

举例:

pom.xml

<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>chaos-monkey-spring-boot</artifactId>
    <version>2.0.0</version>
</dependency>

application.properties

spring.profiles.active=chaos-monkey
chaos.monkey.enabled=true

 

chaos.monkey.enabled 是可选配置,你可以在服务运行情况下修改。

 

Spring Boot Actuator Endpoints

chaos-monkey-spring-boot 通过jmx和http提供了接口访问

application.properties:

management.endpoint.chaosmonkey.enabled=true
management.endpoint.chaosmonkeyjmx.enabled=true

# inlcude all endpoints
management.endpoints.web.exposure.include=*

# include specific endpoints
management.endpoints.web.exposure.include=health,info,chaosmonkey

 

详细的接口

查看接口 http://127.0.0.1:8080/actuator;查找chaosmonkey接口入口(这里有坑,按照接口文档是不知道入口在 /actuator下)

{
	"_links": {
		"self": {
			"href": "http://127.0.0.1:8080/actuator",
			"templated": false
		},
		"health-component": {
			"href": "http://127.0.0.1:8080/actuator/health/{component}",
			"templated": true
		},
		"health-component-instance": {
			"href": "http://127.0.0.1:8080/actuator/health/{component}/{instance}",
			"templated": true
		},
		"health": {
			"href": "http://127.0.0.1:8080/actuator/health",
			"templated": false
		},
		"info": {
			"href": "http://127.0.0.1:8080/actuator/info",
			"templated": false
		},
		"chaosmonkey": {
			"href": "http://127.0.0.1:8080/actuator/chaosmonkey",
			"templated": false
		}
	}
}

官方文档有详细的说明,这里只是描述具体场景的使用:

首先chaos-monkey-spring-boot只支持下面springboot注解的监听;通过spring aop实现,只能识别pulbic方法并且不执行任何动作或者发起其中一个攻击;这些攻击都支持自动化。

@Controller
@RestController
@Service
@Repository
@Component

 

设置监听的注解:(例如监听 controller和restController)

通过接口设置 /actuator/chaosmonkey/watchers

{
    "controller": true,
    "restController": true,
    "service": false,
    "repository": false,
    "component": false
}

 

1、Latency Assault(时延攻击)

请求添加随机延迟

通过接口设置/actuator/chaosmonkey/assaults

{
"level": 5,
"latencyRangeStart": 2000,
"latencyRangeEnd": 5000,
"latencyActive": true,
"watchedCustomServices": [ "xxx.TestController.getV2"]
}

 

feign配置超时时间:

#连接超时 
feign.client.config.default.connectTimeout=1000 
#读超时
feign.client.config.default.readTimeout=2000

#对当前实例的重试次数,默认0  注意自定义FeignApiBuilder要自己加重试
ribbon.MaxAutoRetries=0

#对切换实例的重试次数,默认1
ribbon.MaxAutoRetriesNextServer=0

 

2、Exception Assault(异常攻击)

随机抛出异常

{
"level": 5,
"latencyActive": false,
"exceptionsActive": true,
"watchedCustomServices": [ "xxx.TestController.getV2"],
"exception": {
    "type": "java.lang.IllegalArgumentException",
    "arguments": [{
      "className": "java.lang.String",
      "value": "custom illegal argument exception"}] }
}

3、AppKiller Assault(kill应用攻击)

You can schedule Chaos Monkey Runtime Assaults (Memory, AppKiller) using cron expressions.

{
"level": 5,
"killApplicationActive": true,
"runtimeAssaultCronExpression": "*/1 * * * * ?"
}

4、内存攻击

待补充

 

 

 

文档资料:

chaos-monkey-spring-boot 官方使用手册

https://codecentric.github.io/chaos-monkey-spring-boot/2.3.0

 

chaos-monkey-spring-boot kill应用和增长内存方法,必须使用cron表达式

You can schedule Chaos Monkey Runtime Assaults (Memory, AppKiller) using cron expressions.

https://stackoverflow.com/questions/62803738/springboot-chaos-monkey-assaults-killapplicationactive-and-memoryactive-not-wo

https://codecentric.github.io/chaos-monkey-spring-boot/2.3.0/#_chaos_monkey_assault_scheduler

 

 

三、服务容错(限流、熔断和降级)

背景:在高并发领域,在分布式系统中,可能因为小小功能扛不住压力不可用,例如超时和报错;导致其他服务也跟着超时和报错,最终效果是整个系统不可用。这种情况影响面太大,小则部分时间内用户体验差不能提供正常使用,重则导致公司严重的经济损失。

目的:从服务可用性和可靠性看,分布式系统为例防止系统由于小部分功能异常而导致系统的整体缓慢和崩溃;从技术上怎么避免这方面问题,业界采用了熔断和降级的解决方案。

技术上原因:复杂分布式架构通常具有很多依赖,当一个应用高度耦合其他服务时非常容易失败,这种失败不仅伤害服务的调用者,最后一个接一个的联系错误,应用本身就处于被拖垮的风险中;再高流量的系统中,某个一个后端服务发生延迟,就会在数秒内导致所有系统资源被消耗。大量对微服务的调用可能由于慢请求阻塞远程服务的线程池,如果线程池没有和应用服务的线程池隔离,最终导致整个服务挂机。

技术上解决方案:Hystix官方对它描述为:Hystrix是一个延迟和容错库,旨在隔离远程系统、服务和第三方库,阻止级联故障,在复杂系统中实现恢复能力。Hystrix使用自己的线程池和应用线程池隔离,如果调用时间太长会停止调用,不同请求任务和任务组别配置它们各自的线程池,可以隔离不同的服务。

Hystrix熔断降级效果:

a、对于用户体验来说是部分功能暂时不可达和不可用。

b、控制的颗粒度是某个服务,熔断是基于策略自动触发,降级是可以通过人工干预,但是靠人工干预明显是不可靠的,科学方式是通过开关预置和配置中心配置。

c、降级是针对外围服务开始,熔断是一个框架上处理,每个服务都需要。

 

基本的容错模式

1.主动超时:Http请求主动设置一个超时时间,超时就直接返回,不会造成服务堆积

2.限流:限制最大并发数

3.熔断:当错误数超过阈值时快速失败,不调用后端服务,同时隔一定时间放几个请求去重试后端服务是否能正常调用,如果成功则关闭熔断状态,失败则继续快速失败,直接返回。(此处有个重试,重试就是弹性恢复的能力)

4.隔离:把每个依赖或调用的服务都隔离开来,防止级联失败引起整体服务不可用

5.降级:服务失败或异常后,返回指定的默认信息

 

Hystrix 容错的流程图:(遇到熔断、超时、异常、线程池/队列/信号量满就降级,运行错误超时反馈Calculate Circuit Health判断是触发熔断)

官方文档:https://github.com/Netflix/Hystrix/wiki

 

 

 

上面有 9 个步骤,下面分别讲解每一步骤:

1.每个请求都会封装到 HystrixCommand 中

2.请求会以同步或异步的方式进行调用

3.判断熔断器是否打开,如果打开,它会直接跳转到 8 ,进行降级

4.判断线程池/队列/信号量是否跑满,如果跑满进入降级步骤8

5.如果前面没有错误,就调用 run 方法,运行依赖逻辑

5.运行方法可能会超时,超时后从 5a 到 8,进行降级

6.运行过程中如果发生异常,会从 6b 到 8,进行降级

6.运行正常会进入 6a,正常返回回去,同时把错误或正常调用结果告诉 7 (Calculate Circuit Health)

7.Calculate Circuit Health它是 Hystrix 的大脑,是否进行熔断是它通过错误和成功调用次数计算出来的

8.降级方法(8a没有实现降级、8b实现降级且成功运行、8c实现降级方法,但是出现异常)

8a.没有实现降级方法,直接返回异常信息回去

8b.实现降级方法,且降级方法运行成功,则返回降级后的默认信息回去

8c.实现降级方法,但是降级也可能出现异常,则返回异常信息回去

熔断理念:

凡是依赖都可能会失败

凡是资源都有限制(cpu、内存、IO和线程池)

网络不可靠

延迟是应用最大杀手(由于延迟导致拖垮整个微服务,给服务设置一个超时时间来解决延迟引起的问题)

 

 

 

1、业务feign超时配置(注意:适用于使用Feign.Builder自定义的情况)

情况1:

feign.Feign.Builder 设置了默认值;需要代码修改默认参数

feign.Feign.Builder 
public Builder() {
    this.logLevel = Level.NONE;
    this.contract = new Default();
    this.client = new feign.Client.Default((SSLSocketFactory)null, (HostnameVerifier)null);
    this.retryer = new feign.Retryer.Default();
    this.logger = new NoOpLogger();
    this.encoder = new feign.codec.Encoder.Default();
    this.decoder = new feign.codec.Decoder.Default();
    this.queryMapEncoder = new feign.QueryMapEncoder.Default();
    this.errorDecoder = new feign.codec.ErrorDecoder.Default();
    this.options = new Options();
    this.invocationHandlerFactory = new feign.InvocationHandlerFactory.Default();
    this.closeAfterDecode = true;
}
feign.Request.Options#Options()
public Options() {
    this(10000, 60000);
}

情况2:org.springframework.cloud.netflix.feign.ribbon.FeignLoadBalancer.execute() 中 Feign.Options是默认配置

public FeignLoadBalancer.RibbonResponse execute(FeignLoadBalancer.RibbonRequest request, IClientConfig configOverride) throws IOException {
    Options options;
    if (configOverride != null) {
        RibbonProperties override = RibbonProperties.from(configOverride);
        options = new Options(override.connectTimeout(this.connectTimeout), override.readTimeout(this.readTimeout));
    } else {
        options = new Options(this.connectTimeout, this.readTimeout);
    }

    Response response = request.client().execute(request.toRequest(), options);
    return new FeignLoadBalancer.RibbonResponse(request.getUri(), response);
}
public static class Options {
    private final int connectTimeoutMillis;
    private final int readTimeoutMillis;
    private final boolean followRedirects;

    public Options(int connectTimeoutMillis, int readTimeoutMillis, boolean followRedirects) {
        this.connectTimeoutMillis = connectTimeoutMillis;
        this.readTimeoutMillis = readTimeoutMillis;
        this.followRedirects = followRedirects;
    }

    public Options(int connectTimeoutMillis, int readTimeoutMillis) {
        this(connectTimeoutMillis, readTimeoutMillis, true);
    }

    public Options() {
        this(10000, 60000);
    }

    public int connectTimeoutMillis() {
        return this.connectTimeoutMillis;
    }

    public int readTimeoutMillis() {
        return this.readTimeoutMillis;
    }

    public boolean isFollowRedirects() {
        return this.followRedirects;
    }
}

解决方案:

https://github.com/spring-cloud/spring-cloud-netflix/issues/696

https://blog.csdn.net/varyall/article/details/105282678

 

#连接超时 
feign.client.config.default.connectTimeout=1000 
#读超时
feign.client.config.default.readTimeout=2000

https://www.pianshen.com/article/187038775/

ribbon:
  OkToRetryOnAllOperations: false #对所有操作请求都进行重试,默认false
  ReadTimeout: 2000   #负载均衡超时时间,默认值5000
  ConnectTimeout: 1000 #ribbon请求连接的超时时间,默认值2000
  MaxAutoRetries: 0     #对当前实例的重试次数,默认0
  MaxAutoRetriesNextServer: 1 #对切换实例的重试次数,默认1

 

ribbon.MaxAutoRetries=0

ribbon.MaxAutoRetriesNextServer=0

 

 

设置重试次数:

feign.Feign.Builder#Builder 的 this.retryer = new feign.Retryer.Default(); 设置了默认重试次数,需要修改feign实例化代码。

修改maxAttempts的值

feign.Feign.Builder#Builder
public Builder() {
    this.logLevel = Level.NONE;
    this.contract = new Default();
    this.client = new feign.Client.Default((SSLSocketFactory)null, (HostnameVerifier)null);
    this.retryer = new feign.Retryer.Default();
    this.logger = new NoOpLogger();
    this.encoder = new feign.codec.Encoder.Default();
    this.decoder = new feign.codec.Decoder.Default();
    this.queryMapEncoder = new feign.QueryMapEncoder.Default();
    this.errorDecoder = new feign.codec.ErrorDecoder.Default();
    this.options = new Options();
    this.invocationHandlerFactory = new feign.InvocationHandlerFactory.Default();
    this.closeAfterDecode = true;
}

修改代码(测试后完美解决):

@Bean
@Autowired
public FeignApiBuilder feignApiBuilder(Decoder decoder, Encoder encoder, Client client,
                                       Contract contract, Optional<List<RequestInterceptor>> requestInterceptors) {
    Request.Options options = new Request.Options(connectTimeout, readTimeout);
    Retryer retryer = new Retryer.Default(100L, TimeUnit.SECONDS.toMillis(1L), maxAutoRetries);
    return new FeignApiBuilder(decoder, encoder, client, contract, requestInterceptors.orElse(null), options, retryer);
}

 

2、feign + Hystrix 熔断配置(采用@FeignClient注解方式)

前提feign是采用@FeignClient注解方式进行服务调用

 

接入说明:

SpringBootApplication启动熔断:

@EnableCircuitBreaker

配置文件:

feign.hystrix.enabled=true

 

feign 配置

feign.client.config.default.connectTimeout=1000

feign.client.config.default.readTimeout=2000

 

依赖:

compile('org.springframework.cloud:spring-cloud-starter-netflix-hystrix')

 

调用第三方服务

@RestController
public class InfoController  {

    @Autowired
    private InfoApi infoApi;

    @GetMapping(value = "/service-a/getV2Test")
    public Response getV2Test() {
        return infoApi.getV2Test();
    }
}

调用第三方服务FeignClient实例化InfoApi:

@FeignClient(name = "service-b", qualifier = "serviceB", fallback = InfoApiFallback.class)
public interface InfoApi {
    @GetMapping(value = "/service-b/getV2Test")
    Response getV2Test();
}
@Component
public class InfoApiFallback implements InfoApi {

    @Override
    public Response getV2Test() {
        return Response.fail(-1,"触发熔断");
    }
}

坑一:

错误原因:

SpringMvc在做mapping映射的时候发现RequestMapping重复了

Caused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'com.thoughtworks.demo.consumer.service.ConsumerFeignService' method

解决方案:

将feign接口的@RequestMapping去掉

 

坑二、默认关闭

feign:
 hystrix:
  enabled: true

 

坑三:HystrixCommand 不能使用在方法

https://github.com/Netflix/Hystrix/issues/1458

 

资料清单:

feign熔断的坑:https://www.jb51.net/article/138758.htm

feign使用Hystrix:https://www.cnblogs.com/linjiqin/p/10195442.html

springcloud hystrix 讲解:https://blog.csdn.net/mamamalululu00000000/article/details/105188914

springcloud HystrixCommand的threadPoolKey默认值及线程池初始化:https://www.cnblogs.com/trust-freedom/p/9956427.html

Hystrix快速入门https://www.cnblogs.com/xiong2ge/p/hystrix_faststudy.html

 

四、例子执行效果:

demo源代码:https://gitee.com/kekefish/feign-hystrix-chaos-monkey-demo.git

模块

描述

service-a

服务a

service-b

服务b

service-b-api

服务B定义了接口feign和熔断降级回调

eureka-server

注册中心

调用关系:service-a ->service-b

压测接口:http://127.0.0.1:8080/service-a/getV2Test

配置chaos-monkey超时

http://127.0.0.1:8081/actuator/chaosmonkey/assaults

"latencyRangeStart": 5000,

"latencyRangeEnd": 10000,

"latencyActive": true,

postman脚本:https://download.csdn.net/download/kekefisht/16810627

 

 

jmeter压测结果可见触发熔断了:

jmeter脚步:https://download.csdn.net/download/kekefisht/16810607

 

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值