1、Hystrix熔断应用
目的:简历微服务长时间没有响应,服务消费者—>自动投递微服务快速失败给用户提示
1.1、服务消费者工程(自动投递微服务)中引入Hystrix依赖坐标(也可以添加在父工程中)
<!--熔断器Hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
1.2、服务消费者工程(自动投递微服务)的启动类中添加熔断器开启注解@EnableCircuitBreaker
package com.lagou.edu;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableDiscoveryClient
// @EnableHystrix // 打开Hystrix功能
@EnableCircuitBreaker // 开启熔断器功能(这个与@EnableHystrix的功能相同,只不过它是通用注解)
// @SpringCloudApplication // 综合性注解 @SpringCloudApplication = @SpringBootApplication + @EnableDiscoveryClient + @EnableCircuitBreaker
public class AutoDeliverApplication {
public static void main(String[] args) {
SpringApplication.run(AutoDeliverApplication.class, args);
}
// 使用RestTemplate模板对象远程调用
@Bean
@LoadBalanced
public RestTemplate gerRestTemplate() {
return new RestTemplate();
}
}
1.3、定义服务降级处理方法,并在业务方法上使用@HystrixCommand的fallbackMethod属性关联到服务降级处理方法
@RestController
@RequestMapping("/autodeliver")
public class AutoDeliverController {
@Autowired
private RestTemplate restTemplate;
/**
* 模拟处理超时,调用方法添加Hystrix控制
*
* @param userId
* @return
*/
// 使用HystrixCommand注解进行熔断控制
@HystrixCommand(
// 线程池标识,要保持唯一,不唯一的话就共用了
threadPoolKey = "findResumeOpenStateTimeout",
// 线程池属性细节配置
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "1"), // 线程数
@HystrixProperty(name="maxQueueSize",value="20") // 等待队列长度
},
// commandProperties 熔断的一些细节属性配置
commandProperties = {
// 每一个属性都是一个HystrixProperty
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000")
},
fallbackMethod = "myFallback" // 回退方法
)
@GetMapping("/checkStateTimeout/{userId}")
public Integer findResumeOpenStateTimeout(@PathVariable Long userId) {
// 使用Ribbon不需要我们自己获取服务实例,然后选择一个那种方式去访问了(自己负载均衡)
String url = "http://lagou-service-resume/resume/openstate/" + userId; // 指定服务名
Integer forObject = restTemplate.getForObject(url, Integer.class);
return forObject;
}
/*
* 定义回退⽅法,返回预设默认值
* 注意:该⽅法形参和返回值与原始⽅法保持一致
*/
public Integer myFallback(Long userId) {
return -1; // 兜底数据
}
}
1.4、服务提供者端(简历微服务)模拟请求超时(线程休眠10s),只修改8080实例,8081不修改,对比观察
package com.lagou.edu.controller;
import com.lagou.edu.service.ResumeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/resume")
public class ResumeController {
@Autowired
private ResumeService resumeService;
@Value("${server.port}")
private Integer port;
@GetMapping("/openstate/{userId}")
public Integer findDefaultResumeState(@PathVariable Long userId) {
// 模拟处理超时
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// return resumeService.findDefaultResumeByUserId(userId).getIsOpenResume();
return port;
}
}
因为我们在消费方,也就是简历投递微服务中,Hystrix中设置的超时时间是2s,此时简历微服务中的我们使用线程休眠了10s,这时候调用服务,就会触发熔断机制,如果我们没有设置fallback回调方法,这个时候页面显示的就是一系列错误提示信息。
而如果我们设置了fallback回调方法,这个时候当发生熔断后,会调用我们预先设置方法,然后展示到页面,避免了错误信息在信息在页面的展示,从而影响了用户的使用体验。
因为我们已经使用了Ribbon负载(轮询),所以我们在请求的时候,一次熔断降级,一次正常返回。
注意:
- 降级(兜底)方法必须和被降级方法相同的方法签名(相同参数列表、相同返回值)
- 可以在类上使用@DefaultProperties注解统一指定整个类中共用的降级(兜底)方法
@DefaultProperties(// 线程池标识,要保持唯一,不唯一的话就共用了 threadPoolKey = "findResumeOpenStateTimeout", // 线程池属性细节配置 threadPoolProperties = { @HystrixProperty(name = "coreSize", value = "1"), // 线程数 @HystrixProperty(name="maxQueueSize",value="20") // 等待队列长度 }, // commandProperties 熔断的一些细节属性配置 commandProperties = { // 每一个属性都是一个HystrixProperty @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000") })
2、Hystrix舱壁模式(线程池隔离策略)
如果不进行任何设置,所有熔断方法使用一个Hystrix线程池(10个线程),那么这样的话会导致问题,这个问题并不是扇出链路微服务不可用导致的,而是我们的线程机制导致的,如果方法A的请求把10个线程都用了,方法2请求处理的时候压根都没法去访问B,因为没有线程可用,并不是B服务不可用。
为了避免问题服务请求过多导致正常服务无法访问,Hystrix 不是采用增加线程数,而是单独的为每一个控制方法创建一个线程池的方式,这种模式叫做“舱壁模式",也是线程隔离的手段。
我们可以使用一些手段查看线程情况
发起请求,可以使用PostMan模拟批量请求
在之前的代码中我们也看到了,如何设置舱壁模式
@RestController
@RequestMapping("/autodeliver")
public class AutoDeliverController {
@Autowired
private RestTemplate restTemplate;
/**
* 使用Ribbon负载均衡
*
* @param userId
* @return
*/
@GetMapping("/checkState/{userId}")
public Integer findResumeOpenState(@PathVariable Long userId) {
// 使用Ribbon不需要我们自己获取服务实例,然后选择一个那种方式去访问了(自己负载均衡)
String url = "http://lagou-service-resume/resume/openstate/" + userId; // 指定服务名
Integer forObject = restTemplate.getForObject(url, Integer.class);
return forObject;
}
/**
* 模拟处理超时,调用方法添加Hystrix控制
*
* @param userId
* @return
*/
// 使用HystrixCommand注解进行熔断控制
@HystrixCommand(
// 线程池标识,要保持唯一,不唯一的话就共用了
threadPoolKey = "findResumeOpenStateTimeout",
// 线程池属性细节配置
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "1"), // 线程数
@HystrixProperty(name="maxQueueSize",value="20") // 等待队列长度
},
// commandProperties 熔断的一些细节属性配置
commandProperties = {
// 每一个属性都是一个HystrixProperty
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000")
}
)
@GetMapping("/checkStateTimeout/{userId}")
public Integer findResumeOpenStateTimeout(@PathVariable Long userId) {
// 使用Ribbon不需要我们自己获取服务实例,然后选择一个那种方式去访问了(自己负载均衡)
String url = "http://lagou-service-resume/resume/openstate/" + userId; // 指定服务名
Integer forObject = restTemplate.getForObject(url, Integer.class);
return forObject;
}
// 设置触发熔断后调用的fallback回调方法
@GetMapping("/checkStateTimeoutFallback/{userId}")
@HystrixCommand(
// 线程池标识,要保持唯一,不唯一的话就共用了
threadPoolKey = "findResumeOpenStateTimeoutFallback",
// 线程池属性细节配置
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "2"), // 线程数
@HystrixProperty(name="maxQueueSize",value="20") // 等待队列长度
},
// commandProperties 熔断的一些细节属性配置
commandProperties = {
// 每一个属性都是一个HystrixProperty
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000")
},
fallbackMethod = "myFallback" // 回退方法
)
public Integer findResumeOpenStateTimeoutFallback(@PathVariable Long userId) {
// 使用Ribbon不需要我们自己获取服务实例,然后选择一个那种方式去访问了(自己负载均衡)
String url = "http://lagou-service-resume/resume/openstate/" + userId; // 指定服务名
Integer forObject = restTemplate.getForObject(url, Integer.class);
return forObject;
}
/*
* 定义回退⽅法,返回预设默认值
* 注意:该⽅法形参和返回值与原始⽅法保持一致
*/
public Integer myFallback(Long userId) {
return -1; // 兜底数据
}
/**
* 1)服务提供者处理超时,熔断,返回错误信息
* 2)有可能服务提供者出现异常直接抛出异常信息
* 以上信息,都会返回到消费者这⾥,很多时候消费者服务不希望把收到异常/错误信息再抛到它的上游去
* ⽤户微服务 — 注册微服务 — 优惠券微服务
* 1、登记注册
* 2、分发优惠券(并不是核⼼步骤),这⾥如果调⽤优惠券微服务返回了异常信息或者是熔断后的错误信息,这些信息如果抛给⽤户很不友好
* 此时,我们可以返回⼀个兜底数据,预设的默认值(服务降级)
*/
}
通过jstack命令查看线程情况,和我们程序设置相符合