本文我们来演示下Hystrix中解决雪崩效应的第五种方式隔离的实现

在应对服务雪崩效应时,除了前面介绍的降级,缓存,请求合并及熔断外还有一种方式就是隔离,隔离又分为线程池隔离和信号量隔离。接下来我们分别来介绍。

1.线程池隔离

1.1.概念介绍

我们通过以下几个图片来解释线程池隔离到底是怎么回事

在没有使用线程池隔离时
玩转SpringCloud专题(十八)-SpringCloud之Hystrix隔离_java
当接口A压力增大,接口C同时也会受到影响
玩转SpringCloud专题(十八)-SpringCloud之Hystrix隔离_线程池_02
使用线程池的场景
玩转SpringCloud专题(十八)-SpringCloud之Hystrix隔离_java_03
当服务接口A访问量增大时,因为接口C在不同的线程池中所以不会受到影响
玩转SpringCloud专题(十八)-SpringCloud之Hystrix隔离_java_04
通过上面的图片来看,线程池隔离的作用还是蛮明显的。但线程池隔离的使用也不是在任何场景下都适用的,线程池隔离的优缺点如下:

优点

使用线程池隔离可以完全隔离依赖的服务(例如图中的A,B,C服务),请求线程可以快速放回
当线程池出现问题时,线程池隔离是独立的不会影响其他服务和接口
当失败的服务再次变得可用时,线程池将清理并可立即恢复,而不需要一个长时间的恢复
独立的线程池提高了并发性

缺点

线程池隔离的主要缺点是它们增加计算开销(CPU).每个命令的执行涉及到排队,调度和上下文切换都是在一个单独的线程上运行的。

2.案例演示

2.1.添加Hystrix依赖

将Hystrix依赖添加进来

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.3.2.RELEASE</version>
</dependency>

2.2.业务层处理

注意方法头部的接口,在各个方法中添加了打印当前线程的方法,用来演示当前方法执行时所处的线程,

package com.bruceliu.api;

import com.bruceliu.bean.User;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;

/**
* @BelongsProject: springcloud0310
* @BelongsPackage: com.bruceliu.api
* @Author: bruceliu
* @CreateTime: 2020-03-11 20:00
* @Description: TODO
*/
@Service
public class UserService {

@Autowired
UserClientService userClientService;

@HystrixCommand(groupKey = "ego-product-provider",
commandKey = "getUsers",
threadPoolKey = "ego-product-provider",
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "30"),//线程池大小
@HystrixProperty(name = "maxQueueSize", value = "100"),//最大队列长度
@HystrixProperty(name = "keepAliveTimeMinutes", value = "2"),//线程存活时间
@HystrixProperty(name = "queueSizeRejectionThreshold", value = "15")//拒绝请求
},
fallbackMethod = "fallback")
public List<User> getUsers() {
// 获取当前线程的名称
System.out.println(Thread.currentThread().getName());
return userClientService.queryUsers();
}

/**
* 服务降级
* 返回托底数据的方法
*
* @return
*/
public List<User> fallback() {
System.out.println(Thread.currentThread().getName());
List<User> list = new ArrayList<>();
list.add(new User(3, "我是托底数据", 22));
return list;
}

public void show() {
System.out.println("show:" + Thread.currentThread().getName());
}
}

2.3.控制器编写

控制器中仅仅完成方法调用

@RestController
public class UserController {

@Autowired
private UserService userService;

@RequestMapping("/consumer")
public List<User> getUsers(){
return this.userService.getUsers();
}
@RequestMapping("/show")
public void show(){
this.userService.show();
}
}

2.4.测试

分别启动provider和consumer服务。先正常访问,查看控制台输出的线程名称
玩转SpringCloud专题(十八)-SpringCloud之Hystrix隔离_线程池_05
控制台打印的线程名称如下
玩转SpringCloud专题(十八)-SpringCloud之Hystrix隔离_java_06
在访问没有线程隔离的方法
玩转SpringCloud专题(十八)-SpringCloud之Hystrix隔离_spring_07
由此可以看到访问provider服务的方法是处在了和主线程不同的子线程中了,实现了线程隔离,再关闭provider服务,我们查看fallback方法处的线程名称
玩转SpringCloud专题(十八)-SpringCloud之Hystrix隔离_spring_08
玩转SpringCloud专题(十八)-SpringCloud之Hystrix隔离_java_09
fallback方法也是在隔离的线程池中执行的

2.5 线程池隔离参数

玩转SpringCloud专题(十八)-SpringCloud之Hystrix隔离_spring_10

3.信号量隔离

信号量隔离其实就是我们定义的队列并发时最多支持多大的访问,其他的访问通过托底数据来响应,如下结构图
玩转SpringCloud专题(十八)-SpringCloud之Hystrix隔离_线程池_11
案例实现
信号量隔离效果不太好实现,以下给出了具体的配置。案例代码和线程池隔离大部分是一样的,只是在service的方法头部的注解不同,具体如下

package com.bruceliu.api;

import com.bruceliu.bean.User;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;

/**
* @BelongsProject: springcloud0310
* @BelongsPackage: com.bruceliu.api
* @Author: bruceliu
* @QQ:1241488705
* @CreateTime: 2020-03-11 20:00
* @Description: TODO
*/
@Service
public class UserService {

@Autowired
UserClientService userClientService;

/**
* ribbon 负载均衡
* LoadBalancerClient 通过服务名称可以获取对应的服务的相关信息 ip port等
*/
@Autowired
private LoadBalancerClient loadBalancerClient;

@HystrixCommand(fallbackMethod = "fallback",
commandProperties = {
@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY, value = "SEMAPHORE"),// 信号量 隔离
@HystrixProperty
(name = HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS, value = "100")//信号量最大并度
})
public List<User> getUsers() {
// 获取当前线程的名称
System.out.println(Thread.currentThread().getName());
return userClientService.queryUsers();
}

/**
* 服务降级
* 返回托底数据的方法
*
* @return
*/
public List<User> fallback() {
System.out.println(Thread.currentThread().getName());
List<User> list = new ArrayList<>();
list.add(new User(3, "我是托底数据", 22));
return list;
}

public void show() {
System.out.println("show:" + Thread.currentThread().getName());
}
}

信号量隔离参数
玩转SpringCloud专题(十八)-SpringCloud之Hystrix隔离_spring_12

4.线程池隔离和信号量隔离的区别

玩转SpringCloud专题(十八)-SpringCloud之Hystrix隔离_spring_13