本文我们来演示下Hystrix中解决雪崩效应的第五种方式隔离的实现
在应对服务雪崩效应时,除了前面介绍的降级,缓存,请求合并及熔断外还有一种方式就是隔离,隔离又分为线程池隔离和信号量隔离。接下来我们分别来介绍。
1.线程池隔离
1.1.概念介绍
我们通过以下几个图片来解释线程池隔离到底是怎么回事
在没有使用线程池隔离时
当接口A压力增大,接口C同时也会受到影响
使用线程池的场景
当服务接口A访问量增大时,因为接口C在不同的线程池中所以不会受到影响
通过上面的图片来看,线程池隔离的作用还是蛮明显的。但线程池隔离的使用也不是在任何场景下都适用的,线程池隔离的优缺点如下:
优点
使用线程池隔离可以完全隔离依赖的服务(例如图中的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
*/
public class UserService {
UserClientService userClientService;
(groupKey = "ego-product-provider",
commandKey = "getUsers",
threadPoolKey = "ego-product-provider",
threadPoolProperties = {
(name = "coreSize", value = "30"),//线程池大小
(name = "maxQueueSize", value = "100"),//最大队列长度
(name = "keepAliveTimeMinutes", value = "2"),//线程存活时间
(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.控制器编写
控制器中仅仅完成方法调用
public class UserController {
private UserService userService;
("/consumer")
public List<User> getUsers(){
return this.userService.getUsers();
}
("/show")
public void show(){
this.userService.show();
}
}
2.4.测试
分别启动provider和consumer服务。先正常访问,查看控制台输出的线程名称
控制台打印的线程名称如下
在访问没有线程隔离的方法
由此可以看到访问provider服务的方法是处在了和主线程不同的子线程中了,实现了线程隔离,再关闭provider服务,我们查看fallback方法处的线程名称
fallback方法也是在隔离的线程池中执行的
2.5 线程池隔离参数
3.信号量隔离
信号量隔离其实就是我们定义的队列并发时最多支持多大的访问,其他的访问通过托底数据来响应,如下结构图
案例实现
信号量隔离效果不太好实现,以下给出了具体的配置。案例代码和线程池隔离大部分是一样的,只是在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
*/
public class UserService {
UserClientService userClientService;
/**
* ribbon 负载均衡
* LoadBalancerClient 通过服务名称可以获取对应的服务的相关信息 ip port等
*/
private LoadBalancerClient loadBalancerClient;
(fallbackMethod = "fallback",
commandProperties = {
(name = HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY, value = "SEMAPHORE"),// 信号量 隔离
(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());
}
}
信号量隔离参数
4.线程池隔离和信号量隔离的区别