天天看点

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

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

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

1.线程池隔离

1.1.概念介绍

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

在没有使用线程池隔离时

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

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

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

使用线程池的场景

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

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

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

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

优点

使用线程池隔离可以完全隔离依赖的服务(例如图中的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隔离

控制台打印的线程名称如下

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

在访问没有线程隔离的方法

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

由此可以看到访问provider服务的方法是处在了和主线程不同的子线程中了,实现了线程隔离,再关闭provider服务,我们查看fallback方法处的线程名称

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

fallback方法也是在隔离的线程池中执行的

2.5 线程池隔离参数

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

3.信号量隔离

信号量隔离其实就是我们定义的队列并发时最多支持多大的访问,其他的访问通过托底数据来响应,如下结构图

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

案例实现

信号量隔离效果不太好实现,以下给出了具体的配置。案例代码和线程池隔离大部分是一样的,只是在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());
    }
}      

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