1 引入Hystrix需要了解的知识点

1.1 服务的雪崩效应

    服务的雪崩效应:默认情况下,tomcat只有一个线程池去处理所有客户端发生的所有请求,这样在高并发下的情况下,  
    如果客户端所有的请求堆积到同一个接口上(也就是客户端访问同一个接口),tomcat的所有线程去处理请求,可能  
    会导致其他服务接口无法访问。  

    假设tomcat最大请求数为20,客户端发送的请求数为100,会发生80个请求产生延迟等待  

1.2 Hystrix服务保护框架能解决什么问题

  1. 断路器
  2. 服务降级
  3. 服务熔断
  4. 服务隔离机制
  5. 服务雪崩效应,连环雪崩效应

    注意:如果雪崩效应严重的话,可能会造成整个服务崩溃  

1.3 Hystrix解决雪崩效应的原理

  • 服务降级 —-友好提示

    在高并发的情况下,防止用户一直等待,在tomcat中没有线程处理客户端发生的请求时,不应该让客户端一直在等待,  
    使用服务降级(返回一个友好的提示给客户端),目的是为了用户的体验,调用fallback本地方法  
  • 服务隔离 —-线程池隔离和信号量隔离

    信号量的使用示意图:当n个并发请求去调用一个目标服务接口时,都要获取一个信号量才能真正去调用  
    目标服务接口,但信号量有限,默认是10个,可以使用maxConcurrentRequests参数配置,如果并发请求数多于  
    信号量个数,就有线程需要进入队列排队,但排队队列也有上限,默认是 5,如果排队队列也满,则必定有请求线  
    程会走fallback流程,从而达到限流和防止雪崩的目的。  
    
    信号量模式从始至终都只有请求线程自身,是同步调用模式,不支持超时调用,不支持直接熔断,由于没有线程的  
    切换,开销非常小。  
    
    
    线程池的使用示意图如下图所示,当n个请求线程并发对某个接口请求调用时,会先从hystrix管理的线程池里面  
    获得一个线程,然后将参数传递给这个线程去执行真正调用。线程池的大小有限,默认是10个线程,可以使用  
    maxConcurrentRequests参数配置,如果并发请求数多于线程池线程个数,就有线程需要进入队列排队,但排队  
    队列也有上限,默认是 5,如果排队队列也满,则必定有请求线程会走fallback流程。
    
    线程池模式可以支持异步调用,支持超时调用,支持直接熔断,存在线程切换,开销大。
  • 服务熔断

    服务熔断目的是为了保护服务,在高并发的情况下,如果请求达到一定得极限(可以设置阈值),如果流量超过  
    了这是的阈值,自动开启保护服务的功能,使用服务降级方式返回一个友好的提示。服务熔断和服务降级一起使用

2 搭建Hystrix服务保护框架

2.1 feign客户端超时设置

首先了解feign客户端默认超时时间是1秒
    ribbon:
      # 建立连接所用的时间
      ReadTimeout: 5000
      # 建立之后读取所用的时间
      ConnectTimeout: 5000  

2.3 开启hystrix断路器

        feign.hystrix.enabled=true  

2.4 在已经创建的微服务项目中整合Hystrix框架

2.4.1 添加hystrix的依赖

        <!-- 导入springcloud整合hystrix启动器-->
        <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>  

2.4.2 hystrix整合的两种方式

修改订单服务配置文件

                    # 项目的端口号
                    server.port=8001
                    # 服务别名--服务注册到注册中心的名称
                    spring.application.name=app-test-order
                    # 当前服务注册到eureka服务地址
                    eureka.client.service-url.defaultZone=http://localhost:8100/eureka
                    # 需要将服务注册到eureka上面
                    eureka.client.register-with-eureka=true
                    # 是否需要检索服务
                    eureka.client.fetch-registry=true
                    spring.main.allow-bean-definition-overriding=true  

                    #feign客户端建立连接超时时间
                    feign.client.config.default.connect-timeout=10000
                    #feign客户端建立连接后读取资源超时时间
                    feign.client.config.default.read-timeout=10000

                    #开启Hystrix断路器
                    feign.hystrix.enabled=true
                    #配置Hystrix 超时时间   超时关闭
                    #hystrix.command.default.execution.timeout.enabled=false
                    #超时时间(默认1000ms)在调用方配置,被该调用方的所有方法的超时时间都是该值,优先级低于下边的指定配置
                    hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=3000
                    #在调用方配置,被该调用方的指定方法(HystrixCommandKey方法名)的超时时间是该值
                    hystrix.command.HystrixCommandKey.execution.isolation.thread.timeoutInMilliseconds=4000
                    #线程池核心线程数 默认为10
                    hystrix.threadpool.default.coreSize=10
                    #最大排队长度。默认-1  如果要从-1换成其他值则需重启,即该值不能动态调整,若要动态调整,需要使用到下边这个配置
                    hystrix.threadpool.default.maxQueueSize=100
                    #排队线程数量阈值,默认为5,达到时拒绝,如果配置了该选项,队列的大小是该队列
                    hystrix.threadpool.default.queueSizeRejectionThreshold=5
                    # 简言之,10s内请求失败数量达到20个,断路器开。  当在配置时间窗口内达到此数量的失败后,进行短路。默认20个
                    hystrix.command.default.circuitBreaker.requestVolumeThreshold=20
                    #短路多久以后开始尝试是否恢复,默认5s
                    hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds=5
                    #出错百分比阈值,当达到此阈值后,开始短路。默认50%
                    hystrix.command.default.circuitBreaker.errorThresholdPercentage=50%
                    #调用线程允许请求HystrixCommand.GetFallback()的最大数量,默认10。超出时将会有异常抛出,注意:该项配置对于THREAD隔离模式也起作用
                    hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests=50000

方法一:采用新建fallbackMethod指定服务降级的方式

1.使用@HystrixCommand注解指定fallbackMethod方法
        /**
         * 解决服务的雪崩效应
         * @HystrixCommand:默认开启服务隔离方式(以线程池方式)、服务降级、熔断机制
         *
         * Hystrix提供两种方式解决服务的雪崩效应
         * 方法一:自定义方法
         * */
        @RequestMapping("orderToMemberHystrix")
        @HystrixCommand(fallbackMethod = "getFallBack")
        public Map<String,Object> orderToMemberHystrix(){
            Map<String,Object> map = new HashMap<>();
            System.out.println("orderToMemberHystrix :线程池名称 "+Thread.currentThread().getName());
            String member = memberFeign.getMember("张三");
            map.put("name",member);
            map.put("state","200");
            return map;
        }
        public Map<String,Object>  getFallBack(){
            Map<String,Object> map = new HashMap<>();
            map.put("msg","返回一个友好的提示:服务器忙,请稍后提示");
            map.put("state","400");
            return map;
        }


        注意:这里需要明确几个点  

            1. @HystrixCommand注解默认开启服务隔离(以线程池方式)、服务降级、服务熔断机制  
            2. fallbackMethod指定的方法的返回值必须与@HystrixCommand注解标识的方法的返回值一致  
2. 在订单服务的启动类上开启Hystrix并启动Eureka服务、会员服务、订单服务
        /**
         * @author fyzn12
         * @version 1.0
         * @date 2020/7/23 10:18
         * @description:TODO
         */
        @SpringBootApplication
        @EnableEurekaClient
        @EnableFeignClients
        @EnableHystrix
        public class OrderApp {
            public static void main(String[] args) {
                SpringApplication.run(OrderApp.class,args);
            }
        }  
3.使用工具Apache Jemeter进行测试
1). 首先指定线程组每次执行多少条请求,以及循环多少次
2). 指定访问的路径并启动http:localhost:8001/orderToMemberHystrix
3). 在该工具启动之后再次在浏览器端访问http:localhost:8001/orderToMemberHystrix,目的就是模拟高并发情况下,是否开启hystrix服务保护功能
4). 通过上图可以看出,当高并发情况下,服务开启了服务降级。查看控制台是否开启服务隔离机制
    从图中可以得出,@HystrixCommand默认开启服务降级、隔离、熔断机制  
    另外,从控制台打印的结果得出,tomcat线程池默认每次执行请求的条数为10条请求  
5). 这种方法总结
如上图所示,在使用这种方式整合hystrix服务保护框架时,他的服务隔离机制是同步线程池,即整个方法体都是一个线程池执行处理,真正开发中需要做到只有在调用getMember这个借口时才更换另外的线程池去处理,做到异步线程池隔离,显然这种方法不能做到,效率自然也没有那么高。
6). 这种方法优化
        类上添加@DefaultProperties注解指定默认的熔断处理方法后,只需要在方法上添加@HystrixCommand注解即可:  

方法二:定义统一的fallback接口—-类的方式

1 定义一个类实现feign客户端调用接口并将其注入到Spring容器中
    @Component
    public class ImemberFallBackService implements MemberFeign {
        @Override
        public String getMember(@RequestParam("name") String name){
            System.out.println("ImemberFallBackService---线程池名称:"+Thread.currentThread().getName()+"name"+name);
            return "采用类的方式做服务降级---服务器忙请稍后重试";
        }
    }  
2 修改其注入feign接口的注解为@Resource,因为使用@Autowired时,当接口存在歧义时容易出错
     @Resource
     private MemberFeign memberFeign;  
3 调用该接口
        @Override
        @RequestMapping("/orderToMember")
        public String orderToMember(@RequestParam("name") String name) {

            System.out.println("orderToMember---线程池名称:"+Thread.currentThread().getName()+" name "+name);
            return memberFeign.getMember(name);
        }  
4 总结
    采用类方法时,当高并发的情况下,隔离机制采用的是信号量隔离,当请求数超过默认的阈值时(默认是10),  
    会有请求进入到队列中进行等待(默认为5),但排队队列也有上限,默认是 5,如果排队队列也满,则必定有  
    请求线程会走fallback流程,从而达到限流和防止雪崩的目的。  

    当请求超时时,会采用线程池隔离机制(采用的是异步线程),具体证明如下  
    控制台打印结果如下  
    这里演示的是当请求超时时,hystrix采用的隔离、降级机制,在配置文件中设置了请求超时时间为5秒,但是在   
    会员服务的接口中我设置了休眠时间为6秒,这导致了在订单服务端调用时,请求超时,从而证明了在不同情况下  
    hystrix采用的隔离机制是什么,这种方式采用的异步线程池隔离机制比较符合实际开发。  

        @RestController
        public class IMemberServiceImpl  implements IMemberService {
            @Override
            @RequestMapping("/getMember")
            public String getMember(@RequestParam("name") String name) {
                System.out.println("跳转到会员服务");
                IUser user = new IUser();
                user.setName(name);
                user.setAge(23);
                try {
                    Thread.sleep(6000);
                }catch (Exception e){

                }
                System.out.println("IMemberServiceImpl :线程池名称 "+Thread.currentThread().getName());
                System.out.println("跳转到会员服务:"+user.toString()+"---------------------------------*******************************************");
                return user.toString();
            }
        }

    删掉上面代码中try-catch部分,即去掉相应超时,再次测试