本文我们来演示下Hystrix中解决雪崩效应的第五种方式隔离的实现
Hystrix隔离
在应对服务雪崩效应时,除了前面介绍的降级,缓存,请求合并及熔断外还有一种方式就是隔离,隔离又分为线程池隔离和信号量隔离。接下来我们分别来介绍。
一、线程池隔离
1.概念介绍
我们通过以下几个图片来解释线程池隔离到底是怎么回事
在没有使用线程池隔离时
当接口A压力增大,接口C同时也会受到影响
使用线程池的场景
当服务接口A访问量增大时,因为接口C在不同的线程池中所以不会受到影响
通过上面的图片来看,线程池隔离的作用还是蛮明显的。但线程池隔离的使用也不是在任何场景下都适用的,线程池隔离的优缺点如下: 优点
- 使用线程池隔离可以完全隔离依赖的服务(例如图中的A,B,C服务),请求线程可以快速放回
- 当线程池出现问题时,线程池隔离是独立的不会影响其他服务和接口
- 当失败的服务再次变得可用时,线程池将清理并可立即恢复,而不需要一个长时间的恢复
- 独立的线程池提高了并发性
缺点 线程池隔离的主要缺点是它们增加计算开销(CPU).每个命令的执行涉及到排队,调度和上下文切换都是在一个单独的线程上运行的。
2.案例演示
2.1 创建项目
创建一个普通的SpringCloud项目。
2.2 添加Hystrix依赖
将Hystrix依赖添加进来
代码语言:javascript复制<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.3.2.RELEASE</version>
</dependency>
2.3 修改配置文件
2.4 业务层处理
注意方法头部的接口,在各个方法中添加了打印当前线程的方法,用来演示当前方法执行时所处的线程,
代码语言:javascript复制@Service
public class UserService {
/**
* ribbon 负载均衡
* LoadBalancerClient 通过服务名称可以获取对应的服务的相关信息 ip port等
*/
@Autowired
private LoadBalancerClient loadBalancerClient;
@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());
// ServiceInstance 封装的有服务的基本信息 IP和端口等
ServiceInstance si = this.loadBalancerClient.choose("eureka-ribbon-provider");
StringBuilder sb = new StringBuilder();
sb.append("http://")
.append(si.getHost())
.append(":")
.append(si.getPort())
.append("/user");
System.out.println("服务地址:" sb.toString());
// SpringMVC RestTemplate
RestTemplate rt = new RestTemplate();
ParameterizedTypeReference<List<User>> type = new ParameterizedTypeReference<List<User>>() {};
// ResponseEntity:封装了返回值的信息
ResponseEntity<List<User>> response = rt.exchange(sb.toString(), HttpMethod.GET,null,type);
List<User> list = response.getBody();
return list;
}
/**
* 服务降级
* 返回托底数据的方法
* @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.5 控制器编写
控制器中仅仅完成方法调用
代码语言:javascript复制@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.6 测试
分别启动provider和consumer服务。先正常访问,查看控制台输出的线程名称
控制台打印的线程名称如下
在访问没有线程隔离的方法
由此可以看到访问provider服务的方法是处在了和主线程不同的子线程中了,实现了线程隔离,再关闭provider服务,我们查看fallback方法处的线程名称
fallback方法也是在隔离的线程池中执行的
2.7 线程池隔离参数
二、信号量隔离
信号量隔离其实就是我们定义的队列并发时最多支持多大的访问,其他的访问通过托底数据来响应,如下结构图
案例实现
信号量隔离效果不太好实现,以下给出了具体的配置。案例代码和线程池隔离大部分是一样的,只是在service的方法头部的注解不同,具体如下
代码语言:javascript复制@Service
public class UserService {
/**
* 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(){
// ServiceInstance 封装的有服务的基本信息 IP和端口等
ServiceInstance si = this.loadBalancerClient.choose("eureka-ribbon-provider");
StringBuilder sb = new StringBuilder();
sb.append("http://")
.append(si.getHost())
.append(":")
.append(si.getPort())
.append("/user");
System.out.println("服务地址:" sb.toString());
// SpringMVC RestTemplate
RestTemplate rt = new RestTemplate();
ParameterizedTypeReference<List<User>> type = new ParameterizedTypeReference<List<User>>() {};
// ResponseEntity:封装了返回值的信息
ResponseEntity<List<User>> response = rt.exchange(sb.toString(), HttpMethod.GET,null,type);
List<User> list = response.getBody();
return list;
}
/**
* 服务降级
* 返回托底数据的方法
* @return
*/
public List<User> fallback(){
List<User> list = new ArrayList<>();
list.add(new User(3,"我是托底数据",22));
return list;
}
}