美文网首页程序员Java
微服务架构进阶:Hystrix 如何解决灾难性雪崩及隔离问题

微服务架构进阶:Hystrix 如何解决灾难性雪崩及隔离问题

作者: 该用户已秃头 | 来源:发表于2020-12-16 15:43 被阅读0次

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

    信号量隔离参数

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

    相关文章

      网友评论

        本文标题:微服务架构进阶:Hystrix 如何解决灾难性雪崩及隔离问题

        本文链接:https://www.haomeiwen.com/subject/dmangktx.html