我们继续使用《SpringCloud之Ribbon使用篇》里面集群环境或者是使用《SpringCloud之Eureka使用篇》集群环境。
1.首先我这里有spring-cloud-parent pom工程
2.spring-cloud-eureka-server Eureka Server 子工程 这里端口我们使用90开头
我们这里集群由两个服务实例组成,分别是9090与9091端口
3.spring-cloud-user-service-consumer 用户服务 (也就是咱们的服务消费者)这里端口我们使用80 开头
4.spring-cloud-order-service-provider 订单提供服务 (服务提供者) 这里端口我们使用70开头
我们订单服务提供着也由两个实例组成,分别是7070与7071
如果不想使用该环境,可以根据架构图来搭建自己的环境,这里我使用的spring-cloud版本是Greenwich.RELEASE。
这里 spring-cloud-user-service-consumer 用户服务作为服务调用着我们需要对它改造
我们需要对spring-cloud-user-service-consumer 服务pom.xml中添加Hystrix熔断器依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
也就是我们的xxxApplication.java 文件添加@EnableHystrix或者是@EnableCircuitBreaker 表示启用熔断器
在我们需要熔断的方法上面添加@HystrixCommand 注解,然后并提供降级方案,也就是编写fallback方法。
这里我就在UserCenterController#getTodayStatistic 方法上添加熔断信息。
@RestController
@RequestMapping("/user/data")
public class UserCenterController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/getTodayStatistic/{id}")
@HystrixCommand(
fallbackMethod = "getTodayStatisticFallback",// 服务降级方法
// 使用commandProperties 可以配置熔断的一些细节信息
commandProperties = {
// 类似kv形式
//这里这个参数意思是熔断超时时间2s,表示过了多长时间还没结束就进行熔断
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "2000")
}
)
public Integer getTodayStatistic(@PathVariable("id") Integer id){
String url ="http://spring-cloud-order-service-provider/order/data/getTodayFinishOrderNum/"+id;
return restTemplate.getForObject(url, Integer.class);
}
// 服务降级方法 ,这里参数与返回值需要原方法保持一直
public Integer getTodayStatisticFallback(Integer id){
return -1;
}
}
这里我对getTodayStatistic 添加熔断信息,如果2s没有处理完就进行熔断,然后服务降级方法是getTodayStatisticFallback,熔断了之后接着执行getTodayStatisticFallback方法。
我这里是spring-cloud-order-service-provider ,然后使用springboot spring.profiles特性分别启动7070,7071端口这两个实例。
这里为了演示方便,我需要把7070 端口的服务方法 进行睡眠,然后执行时长要超过咱们的熔断超时时长。
7070 端口的服务方法 进行睡眠
@RestController
@RequestMapping("/order/data")
public class OrderStatisticServiceController {
@Value("${server.port}")
private Integer port;
/**
* 根据用户id获取今日完单数
* @param id 用户ID
* @return 完单数
*/
@GetMapping("/getTodayFinishOrderNum/{id}")
public Integer getTodayFinishOrderNum(@PathVariable("id") Integer id){
if (port==7070){
try {// 睡眠10s
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return port;
}
}
启动服务提供者,分别是7070,7071,我们看到服务提供者已经注册上去了
第一次调用
我们可以看到第一次就调用到了7070端口,发生了熔断,进行了服务降级。
第二次调用
由于使用Ribbon进行负载均衡第二次调用到了7071端口的服务提供者。
首先舱壁模式就是保证微服务健壮性的一种模式,舱壁模式通过隔离每个工作负载或服务的关键资源,如连接池、内存和CPU来防止由一个服务引起的级联故障来增加系统的弹性。例如我们的船舱,我们船就一个船舱的话,如果破了一个洞,然后海水就慢慢灌满了整艘船,到最后船就沉下去了,但是如果我们将整艘船隔成若干个船舱,像下图这样,如果船破了一个洞,海水顶多会灌满一个船舱,不会导致整艘船沉下去。
在Hystrix中,使用了线程池隔离策略,Hystrix中有一个线程池(默认是10个线程),然后供所有添加了@HystrixCommand注解的方法使用,如果那些方法的请求超过10个,其他请求就得等待或者拒绝,像下图这样。
这里如果不进行设置的话,默认会共用一个线程池,这样很容易出问题,比如说我请求量很大,然后方法A 把这10个线程全用了,而且A后面调用的服务方法处理很慢,然后我方法B跟方法C就没有线程用了,然后就得等待,然后超时被熔断,这里并不是我们后面的服务不可用,不是服务1的某个方法,服务2的某个方法不可用,而是服务调用者压根没有线程去处理这些请求。
然后为了避免这种情况的发生,Hystrix没有通过增加线程数来处理这个问题,而是对每一个添加@HystrixCommand 注解的方法创建线程池来进行隔离,就算是某个方法出了问题也不会影响到其他方法,这就是舱壁模式在Hystrix的应用
这样方法的调用就隔离开了,两个互不影响。
在使用线程池隔离配置之前,我们先看下Hystrix默认线程池情况。我们把服务调用者也就是用户服务的controller修改一下,将getTodayStatistic方法cp一份,服务降级的方法也cp一份
@RestController
@RequestMapping("/user/data")
public class UserCenterController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/getTodayStatistic/{id}")
@HystrixCommand(
fallbackMethod = "getTodayStatisticFallback",// 服务降级方法
// 使用commandProperties 可以配置熔断的一些细节信息
commandProperties = {
// 类似kv形式
//这里这个参数意思是熔断超时时间2s,表示过了多长时间还没结束就进行熔断
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "2000")
}
)
public Integer getTodayStatistic(@PathVariable("id") Integer id){
String url ="http://spring-cloud-order-service-provider/order/data/getTodayFinishOrderNum/"+id;
return restTemplate.getForObject(url, Integer.class);
}
// 服务降级方法 ,这里参数与返回值需要原方法保持一直
public Integer getTodayStatisticFallback(Integer id){
return -1;
}
@GetMapping("/getTodayStatisticA/{id}")
@HystrixCommand(
fallbackMethod = "getTodayStatisticFallbackA",// 服务降级方法
// 使用commandProperties 可以配置熔断的一些细节信息
commandProperties = {
// 类似kv形式
//这里这个参数意思是熔断超时时间2s,表示过了多长时间还没结束就进行熔断
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "2000")
}
)
public Integer getTodayStatisticA(@PathVariable("id") Integer id){
String url ="http://spring-cloud-order-service-provider/order/data/getTodayFinishOrderNum/"+id;
return restTemplate.getForObject(url, Integer.class);
}
// 服务降级方法 ,这里参数与返回值需要原方法保持一直
public Integer getTodayStatisticFallbackA(Integer id){
return -1;
}
}
然后我们使用postman进行批量请求,将这两个连接放到一个ccollection下面,一定要保存下子,然后重启服务。
接着点击collection run
修改Iterations,让它执行20次
执行完成后,我们使用jstack查看一下服务调用者的hystrix线程,我们看到只有10个线程。
我可以使用@HystrixCommand 注解里面的参数threadPoolKey 来定义使用的线程池key,这个需要唯一,不唯一的话共用。然后使用threadPoolProperties属性来配置线程池的一些细节参数
我们修改下服务调用者也就是用户服务的controller
@RestController
@RequestMapping("/user/data")
public class UserCenterController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/getTodayStatistic/{id}")
@HystrixCommand(
// 线程池标识
threadPoolKey = "getTodayStatistic",
threadPoolProperties = {
@HystrixProperty(name="coreSize",value = "3"), // 这个就是咱们那个线程池core线程核心数
@HystrixProperty(name="maxQueueSize",value="100") //这个是队列大小
},
fallbackMethod = "getTodayStatisticFallback",// 服务降级方法
// 使用commandProperties 可以配置熔断的一些细节信息
commandProperties = {
// 类似kv形式
//这里这个参数意思是熔断超时时间2s,表示过了多长时间还没结束就进行熔断
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "2000")
}
)
public Integer getTodayStatistic(@PathVariable("id") Integer id){
String url ="http://spring-cloud-order-service-provider/order/data/getTodayFinishOrderNum/"+id;
return restTemplate.getForObject(url, Integer.class);
}
// 服务降级方法 ,这里参数与返回值需要原方法保持一直
public Integer getTodayStatisticFallback(Integer id){
return -1;
}
@GetMapping("/getTodayStatisticA/{id}")
@HystrixCommand(
// 线程池标识
threadPoolKey = "getTodayStatisticA",
threadPoolProperties = {
@HystrixProperty(name="coreSize",value = "2"), // 这个就是咱们那个线程池core线程核心数
@HystrixProperty(name="maxQueueSize",value="100") //这个是队列大小
},
fallbackMethod = "getTodayStatisticFallbackA",// 服务降级方法
// 使用commandProperties 可以配置熔断的一些细节信息
commandProperties = {
// 类似kv形式
//这里这个参数意思是熔断超时时间2s,表示过了多长时间还没结束就进行熔断
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "2000")
}
)
public Integer getTodayStatisticA(@PathVariable("id") Integer id){
String url ="http://spring-cloud-order-service-provider/order/data/getTodayFinishOrderNum/"+id;
return restTemplate.getForObject(url, Integer.class);
}
// 服务降级方法 ,这里参数与返回值需要原方法保持一直
public Integer getTodayStatisticFallbackA(Integer id){
return -1;
}
}
重启一下服务调用者项目,然后使用postman进行批量请求,跑完之后使用jstack看线程情况。
这里我们就可以看到,咱们getTodayStatistic 是有3个线程的,然后getTodayStatisticA是有2个线程的。