Spring Cloud学习(六)Hystrix断路器

1、概念

Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。“断路器"本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝) ,向调用方返回一个符合预期的、可处理的备选响应(FallBack ,而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。

服务降级:

服务器忙,请稍候再试,不让客户端等待并立刻返回一个友好提示,fallback

服务降级情况:

  • 程序运行异常
  • 超时
  • 程序熔断触发服务降级
  • 线程池/信号量打满也会导致服务降级

服务熔断:

类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示

服务限流:

秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行

2、如何使用

新建cloud-hystrix-pay-8007模块

<dependencies>
        <dependency><!-- 引用自己定义的api通用包,可以使用Payment支付Entity -->
            <groupId>com.yu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--监控-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- hystrix-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <!--eureka client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

application.yml

server:
  port: 8007
spring:
  application:
    name: cloud-provider-hystrix-payment

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url: http://eureka7001.com:7001/eureka

主启动类

@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8007 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8007.class, args);
    }
}

业务类

@Service
public class PaymentService {
    /**
     * 正常访问,肯定ok
     *
     * @param id
     * @return
     */
    public String paymentInfo_OK(Integer id) {
        return "线程池: " + Thread.currentThread().getName() + " PaymenyInfo_OK,id: " + id + "\t" + "O(∩_∩)O哈哈~";
    }

    /**
     * paymentInfo_TimeOut
     *
     * @param id
     * @return
     */

    public String paymentInfo_TimeOut(Integer id) {

        int timeNumber = 3;
        try {
            TimeUnit.MILLISECONDS.sleep(timeNumber);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "线程池: " + Thread.currentThread().getName() + " PaymenyInfo_TimeOut,id: " + id + "\t" + "O(∩_∩)O哈哈~" + " 耗时" + timeNumber + "毫秒";
    }
}

controller

@RestController
@Slf4j
public class paymentController {
    @Resource
    private PaymentService paymentService;

    @Value("${server.port}")
    private String serverPort;

    @GetMapping("/payment/hystrix/ok/{id}")
    public String paymentInfoOk(@PathVariable("id") Integer id){
        String result = paymentService.paymentInfo_OK(id);
        log.info("*****result"+result);
        return result;
    }

    @GetMapping("/payment/hystrix/timeout/{id}")
    public String paymentInfoTimeOut(@PathVariable("id") Integer id){
        String result = paymentService.paymentInfo_TimeOut(id);
        log.info("*****result"+result);
        return result;
    }
}

测试

此时使用压测工具(Jmeter),并发20000个请求,请求会延迟的那个方法,
在压力测试中,发现另外一个方法并没有被压测,但是我们访问它时,却需要等待
这就是因为被压测的方法它占用了服务器大部分资源,导致其他请求也变慢了

创建cloud-hystrix-order-80模块

pom

<dependencies>
        <dependency><!-- 引用自己定义的api通用包,可以使用Payment支付Entity -->
            <groupId>com.yu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- openfeign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--监控-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <!--eureka client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

yml

server:
  port: 8090

eureka:
  client:
    #是否将自己注册到注册中心, 默认true
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka  #单机版
ribbon:
  ReadTimeout: 15000
  ConnectTimeout: 15000

主启动类

@SpringBootApplication
@EnableFeignClients
@EnableHystrix
public class OrderHystrixMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderHystrixMain80.class);
    }
}

远程调用提供者模块的接口

@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixService {

    @GetMapping("/payment/hystrix/ok/{id}")
    String paymentInfo_OK(@PathVariable("id") Integer id);

    @GetMapping("/payment/hystrix/timeout/{id}")
    String paymentInfo_TimeOut(@PathVariable("id") Integer id);

}

controller

@RestController
@Slf4j
public class OrderHystrixController {

    @Resource
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id) {
        String result = paymentHystrixService.paymentInfo_OK(id);
        return result;
    }

    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
        String result = paymentHystrixService.paymentInfo_TimeOut(id);
        return result;
    }
}

测试

启动eureka7001,提供者8007和消费者,http://localhost:8090/consumer/payment/hystrix/timeout/1

在高并发压力测试,发现访问没设定超时的方法访问速度也变慢了,因为8007同一层次的其他接口服务被困死,tomcat线程池里面的工作线程已经被挤占完毕,这个时候8090调用8007,客户端访问会响应缓慢。可以配置服务降级来解决。

配置服务降级

在服务端和客户端都可以配置,但一般服务降级都配置在客户端,先修改配置文件

feign:
  hystrix:
    enabled: true

在主启动类上添加@EnableHystrix注解

修改controller

Spring Cloud学习(六)Hystrix断路器

测试

Spring Cloud学习(六)Hystrix断路器

重构

上面出现的问题:
1,降级方法与业务方法写在了一块,耦合度高

​ 2.每个业务方法都写了一个降级方法,重复代码多

解决重复代码的问题:

配置一个全局的降级方法,所有方法都可以走这个降级方法,至于某些特殊创建,再单独创建方法

修改conreoller

Spring Cloud学习(六)Hystrix断路器

测试

Spring Cloud学习(六)Hystrix断路器

解决耦合度(基于order模块)

  1. PaymentHystrixService接口是远程调用pay模块的,可以创建一个PaymentFallbackServiceImpl类实现此接口,在实现类中统一异常处理

    @Component
    public class PaymentFallbackServiceImpl implements PaymentHystrixService {
        @Override
        public String paymentInfoOk(Integer id) {
            return "-------PaymentFallbackService fall back-[paymentInfo_OK],o(╥﹏╥)o";
        }
    
        @Override
        public String paymentInfoTimeOut(Integer id) {
            return "-------PaymentFallbackService fall back-[paymentInfo_TimeOut],o(╥﹏╥)o";
        }
    }
    
  2. 让PaymentHystrixService的实现类生效Spring Cloud学习(六)Hystrix断路器

服务熔断

修改cloud-hystrix-pay-8007模块

  1. 修改PaymentService接口

    /**
         * 服务的降级->进而熔断->恢复调用链路
         *
         * @param id
         * @return
         */
        @HystrixCommand(fallbackMethod = "paymentCircuitBreakerFallback", commandProperties = {
                @HystrixProperty(name = "circuitBreaker.enabled", value = "true"), //是否开启断路器
                @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), //请求次数
                @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), //时间窗口期
                @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"),//失败率达到多少后跳闸
        })
        public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
            if (id < 0) {
                throw new RuntimeException("******id 不能为负数");
            }
            String serialNumber = IdUtil.simpleUUID();
            return Thread.currentThread().getName() + "\t" + "调用成功,流水号:" + serialNumber;
        }
    
        public String paymentCircuitBreakerFallback(@PathVariable("id") Integer id) {
            return "id 不能负数,请稍后再试,o(╥﹏╥)o id:" + id;
        }
    
  2. 修改controller

    /**
         * 服务熔断
         *
         * @param id
         * @return
         */
        @GetMapping("/payment/circuit/{id}")
        public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
            String result = paymentService.paymentCircuitBreaker(id);
            log.info("------------result: " + result);
            return result;
        }
    
  3. 修改主启动Spring Cloud学习(六)Hystrix断路器

  4. 测试

    启动eureka7001,启动当前模块,先输入正数Id,访问成功,然后连续输入负数id多次(这里属性整体意思是:10秒之内
    (窗口,会移动),如果并发==超过==10个,或者10个并发中,失败了6个,就开启熔断器),当断路器开启后输入正数id
    发现也是错误页面,过一会后(默认5秒),服务自动恢复,访问正数id页面正常
    

总结:

服务监控

Spring Cloud学习(六)Hystrix断路器

新建cloud-consumer-hystrix-dashboard9001

pom

<dependencies>
        <dependency><!-- 引用自己定义的api通用包,可以使用Payment支付Entity -->
            <groupId>com.yu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
        </dependency>
        <!--监控-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <!--eureka client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

yml

server:
  port: 9001

主启动

@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain9001 {
    public static void main(String[] args) {
        SpringApplication.run(HystrixDashboardMain9001.class, args);
    }
}

修改cloud-hystrix-pay-8007的主启动

@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentHystrixMain8007 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8007.class, args);
    }
    /**
     * 此配置是为了服务监控而配置,与服务器容错本身无关,springcloud升级后的坑
     * ServletRegistrationBean因为springboot的默认路径不是/hystrix.stream
     * 只要在自己的项目里配置上下文的servlet就可以了
     */
    @Bean
    public ServletRegistrationBean getservlet() {
        HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
        ServletRegistrationBean<HystrixMetricsStreamServlet> registrationBean = new ServletRegistrationBean<>(streamServlet);
        registrationBean.setLoadOnStartup(1);
        registrationBean.addUrlMappings("/hystrix.stream");
        registrationBean.setName("HystrixMetricsStreamServlet");
        return registrationBean;
    }
}

测试

  1. 启动eureka7001
  2. 启动服务监控9001
  3. 启动提供者8007
  4. 浏览器输入http://localhost:9001/hystrixSpring Cloud学习(六)Hystrix断路器
  5. 访问http://localhost:8007/payment/circuit/1,然后点击上图的Monitor Stream即可看见
  6. 断路器启动Spring Cloud学习(六)Hystrix断路器
  7. 正常状态Spring Cloud学习(六)Hystrix断路器
如何看图

实心圆:共有两种含义。它通过颜色的变化代表了实例的健康程度,它的健康程度从绿色-黄色-橙色-红色递减。该实心圆除了颜色的变化外,它的大小也会根据实例的请求量发生变化,流量越大该实心圆也就越大。所以通过实心圆的展示,可以在大量的实例中快速的发现故障实例和高压力实例。

**曲线:**用来记录两分钟内流量的相对变化,可以通过它来观察到流量的上升和下降趋势

整图说明

  1. Spring Cloud学习(六)Hystrix断路器Spring Cloud学习(六)Hystrix断路器
  2. Spring Cloud学习(六)Hystrix断路器
上一篇:Hystrix和Sentinel对比,如何选择


下一篇:Vue 嵌套路由