4 JMeter高并发测试
直到目前为止,一切都是"易凡峰顺"的,来点异常场景。
4.1 JMeter简介
JMeter是开源软件Apache基金会下的一个性能测试工具,用来测试部署在服务器端的应用程序的性能。近来,JMeter因为其使用简单,现在也被社区作为接口测试工具… 举个栗子,你开了一个网店,兴冲冲地准备双十一大干一把,没想当天活动的时候大量用户一访问你的网店,你的网店挂了,那怎么办?办法就是在实际搞活动之前,先测试一下以确认系统能承受那么多的用户,当然测试的时候我们不需要请真正的这么多实际用户,否则得花多少钱啊,JMeter就是那个能帮助模拟大量用户访问你网站的一个软件。
4.2 下载、安装JMeter
JMeter下载地址:http://jmeter.apache.org/download_jmeter.cgi
下载后解压到你系统下的任意目录,然后运行%JMETER_HOME%\bin\jmeter.bat文件
4.3 JMeter压力测试
新建测试计划,鼠标右键点击计划设置200*100 = 20000并发量。
新增http请求。
配置如下。
保存,启动压测。
后台不断地看到接收到的请求。
这个时候你访问:http://localhost:8001/payment/hystrix/ok/1
发现也开始转圈圈了,访问速度肉眼可见变慢。
这是因为tomcat的默认工作线程被占满了,没有多余的线程来分解压力和处理了。
如果此时外部消费者80也来访问,只能干等,最终导致消费者80不满意。8001被拖死。
5 客户端测试集成
5.1 建项目
新建cloud-consumer-feign-hystrix-order80(一般将Hystrix用于客户端,但其实她也可以应用于服务端)
5.2 写pom
<dependencies> <!-- openfeign --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!-- hystrix --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <!--eureka client--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!-- 引用自己定义的api通用包,可以使用Payment支付Entity --> <dependency> <groupId>com.wangzhou.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--热部署--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
5.3 写yml
server: port: 80 eureka: client: register-with-eureka: false service-url: defaultZone: http://localhost:7001/eureka #需要加上,否则会报错 ribbon: ReadTimeout: 4000 ConnectTimeout: 4000
5.4 主启动
@SpringBootApplication @EnableFeignClients @EnableEurekaClient public class OrderHystrixMain80 { public static void main(String[] args) { SpringApplication.run(OrderHystrixMain80.class, args); } }
5.5 业务类
(1)service
@Component @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT") public interface PaymentHystrixService { @GetMapping("/payment/hystrix/ok/{id}") public String paymentOk(@PathVariable("id")Integer id); @GetMapping("/payment/hystrix/timeout/{id}") public String paymentInfo_timeout(@PathVariable("id") Integer id); }
(2)Controller
@Slf4j @RestController public class OrderHystrixController { @Resource private PaymentHystrixService paymentHystrixService; @GetMapping("/consumer/payment/hystrix/ok/{id}") public String paymentOk(@PathVariable("id")Integer id) { String result = paymentHystrixService.paymentOk(id); return result; } @GetMapping("/consumer/payment/hystrix/timeout/{id}") public String paymentInfo_timeout(@PathVariable("id") Integer id) { String result = paymentHystrixService.paymentInfo_timeout(id); return result; } }
5.6 正常测试
启动80
http://localhost/consumer/payment/hystrix/ok/1
http://localhost/consumer/payment/hystrix/tmeout/1
5.7 高并发测试
启动JMeter对8001的压力测试,再通过80访问
http://localhost/consumer/payment/hystrix/ok/1
开始转圈圈了。狂点还可能报错。
服务端这么慢,用户不得骂你吗?我们来看看怎么解决。
6 Hystrix的服务降级
6.1 降级容错解决的维度要求
6.2 服务端的降级处理
在8001的timeout接口新增@HystrixCommand
注解,进行如下改造,设置兜底机制。
/* 通过@HystrixCommand来指定哪个方法由Hystrix来接管 fallbackMethod属性: 指定哪个方法作为兜底方法 */ @HystrixCommand(fallbackMethod ="paymentInfo_TimeoutHandler", commandProperties = { //设置自身超时调用时间的峰值为 3 秒,峰值内可以正常运行,超过了需要有兜底的方法处理,服务降级fallback @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000") }) public String paymentInfo_Timeout(Integer id) throws InterruptedException { int timeout = 3; // Hystrix底层调用的是Tomcat的线程池,我们在这里将线程名放回 TimeUnit.SECONDS.sleep(timeout); return "线程: "+Thread.currentThread().getName()+" paymentInfo_OK, id = "+id + "\t" + "耗时"+ timeout +"s"; } public String paymentInfo_TimeoutHandler(Integer id) { return "线程: "+Thread.currentThread().getName()+" paymentInfo_TimeoutHandler, id = "+id + "\t/(ㄒoㄒ)/~~"; }
主启动类增加@EnableCircuitBreaker
注解触发熔断功能
测试下,访问:http://localhost/consumer/payment/hystrix/timeout/1
可以注意到,此时处理的线程也变成了Hystrix...
开头了。说明对于超时的情况使用了其它线程池的线程进行单独处理了。
将接口的核心方法改造下。从超时的情况改造称为异常。
@HystrixCommand(fallbackMethod ="paymentInfo_TimeoutHandler", commandProperties = { //设置自身超时调用时间的峰值为 3 秒,峰值内可以正常运行,超过了需要有兜底的方法处理,服务降级fallback @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000") }) public String paymentInfo_Timeout(Integer id) throws InterruptedException { int timeout = 3; int temp = 3 / 0; // Hystrix底层调用的是Tomcat的线程池,我们在这里将线程名放回 // TimeUnit.SECONDS.sleep(timeout); return "线程: "+Thread.currentThread().getName()+" paymentInfo_OK, id = "+id + "\t" + "耗时"+ timeout +"s"; }
再测试。
这说明对于异常或者超时的情况都将会使用兜底方案。
6.3 客户端的降级处理
之前我们说过,一般会将服务降级放在客户端,这是为了在上游及时发先问题,及时处理。现在就来实践下。
在80的yml中添加。
@SpringBootApplication @EnableFeignClients @EnableEurekaClient @EnableHystrix public class OrderHystrixMain80 { public static void main(String[] args) { SpringApplication.run(OrderHystrixMain80.class, args); } }
改造80 controller中的paymentInfo_timeout接口。
@GetMapping("/consumer/payment/hystrix/timeout/{id}") @HystrixCommand(fallbackMethod ="paymentInfo_TimeoutHandler", commandProperties = { //设置自身超时调用时间的峰值为 2 秒,峰值内可以正常运行,超过了需要有兜底的方法处理,服务降级fallback @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000") }) public String paymentInfo_timeout(@PathVariable("id") Integer id) { String result = paymentHystrixService.paymentInfo_timeout(id); return result; } public String paymentInfo_TimeoutHandler(Integer id) { return "我是消费者80,对方系统繁忙,请稍后再试,/(ㄒoㄒ)/~~"; }
测试。
在测试之前记得将支付微服务8001的paymentInfo_timeout逻辑从异常改为sleep哟。
测试结果如下。
6.4 全局服务降级的配置
上面的代码有如下问题。
- 业务逻辑和异常处理被我们混在一块了,耦合度极高。
- 每一个方法都需要有兜底方法
解决方法,使用@DefaultProperties
设置全局的fallback方法。
80的OrderHystrixController中添加全局fallback方法:
//全局fallback方法,不能有传参 public String payment_Global_FallbackMethod(){ return "Global异常处理信息,请稍后再试!"; }
并在OrderHystrixController类上加上@DefaultProperties(defaultFallback = “payment_Global_FallbackMethod”),指定设置全局fallback方法。
将之前的@HystrixCommand
注掉,稍微对方法进行下改动。重启微服务80.
@GetMapping("/consumer/payment/hystrix/timeout/{id}") // @HystrixCommand(fallbackMethod ="paymentInfo_TimeoutHandler", commandProperties = { // //设置自身超时调用时间的峰值为 2 秒,峰值内可以正常运行,超过了需要有兜底的方法处理,服务降级fallback // @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000") // }) @HystrixCommand public String paymentInfo_timeout(@PathVariable("id") Integer id) { int var = 10/0; String result = paymentHystrixService.paymentInfo_timeout(id); return result; }
结果如下。
6.5 通配服务降级
前面一节我们已经解决了代码膨胀的问题,接下来解决下代码耦合度过高的问题。
在80的service包下新建PaymentFallbackService类,实现PaymentHystrixService接口
public class PaymentFallbackService implements PaymentHystrixService{ @Override public String paymentOk(Integer id) { return "------PaymentFallbackService paymentOk /(ㄒoㄒ)/~~"; } @Override public String paymentInfo_timeout(Integer id) { return "------PaymentFallbackService paymentInfo_timeout /(ㄒoㄒ)/~~"; } }
在PaymentHystrixService
中的@FeignClient
注解增加fallback
参数即可。
访问ok接口:http://localhost/consumer/payment/hystrix/ok/1,没有任何毛病
7 Hystrix的服务熔断
7.1 熔断理论
前面介绍过服务熔断,这里再解释下,帮助读者理解。
(1)调用正常方法失败会触发降级,而降级会触发fallback方法
(2)但无论如何,降级一定是先调用正常方法,再调用fallback方法
(3)假如单位时间降级次数过多,会触发熔断
(4)熔断以后将会跳过正常方法直接调用fallback方法
(5)所谓的熔断后服务不可用,就是因为跳过了正常方法,而直接执行了fallback方法
7.2 服务熔断案例
在8001的PaymentService中添加
//====服务熔断 @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = { @HystrixProperty(name = "circuitBreaker.enabled", value = "true"), //开启断路器 @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), //请求总数阈值(默认20) @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), //休眠时间窗口期(休眠多久进入半开模式(单位毫秒,默认5秒)) @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"), //请求次数的错误率达到多少跳闸(百分率%,默认50%) }) public String paymentCircuitBreaker(@PathVariable("id") Integer id) { if(id < 0){ throw new RuntimeException("****id 不能为负数"); } String serialNumber = IdUtil.simpleUUID(); return Thread.currentThread().getName() + "\t" + "调用成功,流水号:" + serialNumber; } public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){ return "id 不能为负数,请稍后再试, id: " + id; }
对上面的参数解释如下。
在HystrixCommandProperties.class
可用看到所有可配置的参数。
在8001的PaymentController中添加
@GetMapping("/payment/circuit/{id}") public String paymentCircuitBreaker(@PathVariable("id") Integer id){ String result = paymentService.paymentCircuitBreaker(id); log.info("******result:" + result); return result; }
启动8001,执行测试。
正数放行,http://localhost:8001/payment/circuit/1
负数,http://localhost:8001/payment/circuit/-1
借助Jmeter大量进行如上请求,使服务熔断,熔断10秒内就算是正确的请求也返回错误信息
10秒后进入半开模式,对请求进行处理,此时如果是正确的请求,那么就关闭熔断,否则再次进入熔断,10秒后再次开启半开模式,对请求进行处理,直到半开模式处理到正确请求。
7.3 熔断规则总结
(1)熔断状态的变化
下图总结了熔断的状态变化的规则
官网还给出了详细的流程图
(2)熔断开启的条件
(3)断路器打开之后