SpringCloud-Hystrix【解决灾难性雪崩-隔离】

768 阅读4分钟

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

Hystrix隔离

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

一、线程池隔离

1.概念介绍

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

在没有使用线程池隔离时

在这里插入图片描述

当接口A压力增大,接口C同时也会受到影响

在这里插入图片描述

使用线程池的场景

在这里插入图片描述

当服务接口A访问量增大时,因为接口C在不同的线程池中所以不会受到影响

在这里插入图片描述

通过上面的图片来看,线程池隔离的作用还是蛮明显的。但线程池隔离的使用也不是在任何场景下都适用的,线程池隔离的优缺点如下: 优点

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

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

2.案例演示

2.1 创建项目

  创建一个普通的SpringCloud项目。

在这里插入图片描述

2.2 添加Hystrix依赖

  将Hystrix依赖添加进来

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

2.3 修改配置文件

在这里插入图片描述

2.4 业务层处理

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

@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 控制器编写

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

@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的方法头部的注解不同,具体如下

@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;
    }
}

信号量隔离参数

在这里插入图片描述

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

在这里插入图片描述