Hystrix服务降级

  服务雪崩:多个微服务之间调用的时候,假设A调用B、C,B、C服务又调用其他服务,这就是所谓的扇出。如果扇出的链路上某个微服务调用的时间过长或者不可用,对微服务A的调用就会占用越来越多的资源,从而引起系统崩溃,这就是所谓的"雪崩效应"。

  对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源在几秒内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他资源紧张,导致整个系统发生其他的级联故障。这些都表示需要对故障和延迟进行隔离和关联,以便单个依赖关系的失败,不能取消整个应用程序或系统。

1.Hystrix简介

1.Hystrix是什么

   在布式系统面临的一个重要问题:应用程序可能有数十个依赖,每个依赖关系不可避免的会出现失败,比如超时、异常等。Hystrix是一个用于分布式系统的延迟和容错的开源库,能够在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。

  "断路器"本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这就保证了服务调用方的线程不会被长时间、不必要的占用,从而避免了故障在分布式系统的蔓延乃至雪崩。

  git地址:https://github.com/Netflix/Hystrix

 2.Hystrix的作用以及重要概念

   可以进行服务降级、服务熔断、接近实时的监控。不过官网上Hystrix已经停止更新。与之对应的还有resilience4j、sentinel。

服务降级(fallback):就是某个服务出现故障了,不让调用方一直等待,返回一个友好的提示(fallback)。下面情况会发出降级:程序运行异常、超时、服务熔断触发服务降级、线程池\信号量打满也会导致服务降级。

服务熔断(break):类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示。通常过程:服务降级-》熔断-》恢复调用链路。

服务限流(flowlimit): 限制接口的访问次数,严禁接口无休止的调用,比如某个接口1秒钟只能调用200次。自己实现的话可以在Controller层用AOP实现。

2.使用

1. 创建payment支付服务

1.新建项目

 2.修改pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud</artifactId>
        <groupId>cn.qz.cloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-provider-hystrix-payment8081</artifactId>


    <dependencies>
        <!--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>cn.qz.cloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <!--web-->
        <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>
        <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>

</project>

3.修改yml

server:
  port: 8081

spring:
  application:
    name: cloud-provider-hystrix-payment

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
      defaultZone: http://localhost:7001/eureka

4.启动类

package cn.qz.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

/**
 * @Author: qlq
 * @Description
 * @Date: 22:08 2020/10/17
 */
@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8081 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8081.class, args);
    }
}

5.业务类:

Service(这里直接用class,不用接口) 

package cn.qz.cloud.service;

import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * @Author: qlq
 * @Description
 * @Date: 22:15 2020/10/17
 */
@Service
public class PaymentService {

    /**
     * 正常
     *
     * @param id
     * @return
     */
    public String success(Integer id) {
        return "success,线程池:  " + Thread.currentThread().getName() + "  success,id:  " + id;
    }

    public String timeout(Integer id) {
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return "timeout,线程池:  " + Thread.currentThread().getName() + "  success,id:  " + id;
    }
}

Controller

package cn.qz.cloud.controller;

import cn.qz.cloud.service.PaymentService;
import cn.qz.cloud.utils.JSONResultUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @Author: qlq
 * @Description
 * @Date: 22:22 2020/10/17
 */
@RestController
@Slf4j
@RequestMapping("/hystrix/payment")
public class PaymentController {

    @Resource
    private PaymentService paymentService;

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

    @GetMapping("/success/{id}")
    public JSONResultUtil<String> success(@PathVariable("id") Integer id) {
        String result = paymentService.success(id);
        log.info("*****result: " + result);
        return JSONResultUtil.successWithData(result);
    }

    @GetMapping("/timeout/{id}")
    public JSONResultUtil timeout(@PathVariable("id") Integer id) {
        String result = paymentService.timeout(id);
        log.info("*****result: " + result);
        return JSONResultUtil.successWithData(result);
    }
}

6.测试正常

2.创建订单服务

1.创建模块

 2.修改pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud</artifactId>
        <groupId>cn.qz.cloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumer-feign-hystrix-order80</artifactId>

    <dependencies>
        <!--hystrix-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--eureka-client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--引入自己抽取的工具包-->
        <dependency>
            <groupId>cn.qz.cloud</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>
        <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>

</project>

3.修改yml文件

server:
  port: 80

eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://localhost:7001/eureka/

feign:
  hystrix:
    enabled: true

4.启动类:

package cn.qz.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
 * @Author: qlq
 * @Description
 * @Date: 14:25 2020/10/18
 */
@SpringBootApplication
@EnableFeignClients
@EnableHystrix
public class OrderHystrixMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderHystrixMain80.class, args);
    }
}

5.业务类

(1)Service

package cn.qz.cloud.service;

import cn.qz.cloud.utils.JSONResultUtil;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @Author: qlq
 * @Description
 * @Date: 14:31 2020/10/18
 */
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixService
{
    @GetMapping("/hystrix/payment/success/{id}")
    public JSONResultUtil<String> success(@PathVariable("id") Integer id);

    @GetMapping("/hystrix/payment/timeout/{id}")
    public JSONResultUtil<String> timeout(@PathVariable("id") Integer id);
}

(2)controller 

package cn.qz.cloud.controller;

import cn.qz.cloud.service.PaymentHystrixService;
import cn.qz.cloud.utils.JSONResultUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: qlq
 * @Description
 * @Date: 14:49 2020/10/18
 */
@RestController
@Slf4j
@RequestMapping("/consumer/hystrix/payment")
public class OrderHystirxController {

    @Autowired
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("/success/{id}")
    public JSONResultUtil<String> success(@PathVariable("id") Integer id) {
        return paymentHystrixService.success(id);
    }

    @GetMapping("/timeout/{id}")
    public JSONResultUtil timeout(@PathVariable("id") Integer id) {
        return paymentHystrixService.timeout(id);
    }
}

6.测试:

 3.压力测试

(1)用jmeter测试2W个并发访问8081服务的timeout方法。

1)新建线程组:

 2)新建HttpRequest请求

 3)新建listener->view results Tree

4)执行jmeter测试。相当于2W个请求同时去请求timeout接口,8081的tomcat会分配线程组处理这2W个请求。

  可以从8081服务查看日志发现也是用线程池处理timeout请求。

5)访问正常的success接口报错。因为没有可分配的线程来处理success请求。

4.解决上面的超时和报错(服务降级)

  服务降级可以在服务消费者端进行,也可以在服务提供者进行,一般是在消费者端进行。

主要从下面三个维度处理:

(1)服务提供者8081超时,调用者80不能一直卡死等待,需要有降级

(2)服务提供者8081down机了,调用者80需要有降级

(3)服务提供者8081服务OK,调用者80自己出故障或者有自我要求(自己的等待时间小于服务的处理时间),需要降级。

1.服务提供者8081进行服务降级,超时或者异常之后走自己指定的fallback方法

(1)主启动类增加注解:

@EnableCircuitBreaker

(2)Service声明HystrixCommand进行降级:

package cn.qz.cloud.service;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * @Author: qlq
 * @Description
 * @Date: 22:15 2020/10/17
 */
@Service
public class PaymentService {

    /**
     * 正常
     *
     * @param id
     * @return
     */
    public String success(Integer id) {
        return "success,线程池:  " + Thread.currentThread().getName() + "  success,id:  " + id;
    }

    @HystrixCommand(fallbackMethod = "timeOutHandler", commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
    })
    public String timeout(Integer id) {
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return "timeout,线程池:  " + Thread.currentThread().getName() + "  success,id:  " + id;
    }

    public String timeOutHandler(Integer id) {
        return "线程池:  " + Thread.currentThread().getName() + "  8081系统繁忙或者运行报错,请稍后再试,id:  " + id;
    }
}

  上面表示超过3s后走 timeOutHandler 降级方法。程序中休眠5s模拟请求耗时五秒。

(3)测试如下:

$ curl -X GET http://localhost:8081/hystrix/payment/timeout/1
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   135    0   135    0     0     44      0 --:--:--  0:00:03 --:--:--    44{"success":true,"code":"200","msg":"","data":"线程池:  HystrixTimer-4  8081系统繁忙或者运行报错,请稍后再试,id:  1"}

  可以看到是Hystrix相关的线程池在处理请求。

(4)修改timeout方法,模拟程序报错:

    public String timeout(Integer id) {
        int i = 10 / 0;
//        try {
//            TimeUnit.SECONDS.sleep(5);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }

        return "timeout,线程池:  " + Thread.currentThread().getName() + "  success,id:  " + id;
    }

发现程序也是走的  timeOutHandler 方法,可以满足实际中的需求。

(5)还原方法,认为5秒钟是正常请求,线程休眠3s模拟实际处理耗时3s

    @HystrixCommand(fallbackMethod = "timeOutHandler", commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000")
    })
    public String timeout(Integer id) {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return "timeout,线程池:  " + Thread.currentThread().getName() + "  success,id:  " + id;
    }

2.服务消费者端80进行服务降级

(1)修改OrderHystirxController

package cn.qz.cloud.controller;

import cn.qz.cloud.service.PaymentHystrixService;
import cn.qz.cloud.utils.JSONResultUtil;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: qlq
 * @Description
 * @Date: 14:49 2020/10/18
 */
@RestController
@Slf4j
@RequestMapping("/consumer/hystrix/payment")
public class OrderHystirxController {

    @Autowired
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("/success/{id}")
    public JSONResultUtil<String> success(@PathVariable("id") Integer id) {
        return paymentHystrixService.success(id);
    }

    @GetMapping("/timeout/{id}")
    @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
    })
    public JSONResultUtil timeout(@PathVariable("id") Integer id) {
        return paymentHystrixService.timeout(id);
    }

    public JSONResultUtil paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) {
        return JSONResultUtil.successWithData("消费者80,paymentTimeOutFallbackMethod, 线程池:  " + Thread.currentThread().getName() + "  8081系统繁忙或者运行报错,请稍后再试,id:  " + id);
    }
}

(2)测试:(调用服务提供者的timeout接口走的是paymentTimeOutFallbackMethod方法)

$ curl http://localhost/consumer/hystrix/payment/timeout/1
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   199    0   199    0     0    187      0 --:--:--  0:00:01 --:--:--   193{"success":true,"code":"200","msg":"","data":"消费者80,paymentTimeOutFallbackMethod, 线程池:  hystrix-OrderHystirxController-2  8081系统繁忙或者运行报错,请稍后再试,id:  1"}

3.上面降级存在的问题:

(1)每个方法配置一个fallback降级方法,代码膨胀

(2)降级方法和业务逻辑混在一起,代码混乱

解决办法:

(1)defaultFallback解决上面问题一,实现默认降级处理

controller方法增加全局默认的降级处理,如下:

package cn.qz.cloud.controller;

import cn.qz.cloud.service.PaymentHystrixService;
import cn.qz.cloud.utils.JSONResultUtil;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: qlq
 * @Description
 * @Date: 14:49 2020/10/18
 */
@RestController
@Slf4j
@RequestMapping("/consumer/hystrix/payment")
@DefaultProperties(defaultFallback = "paymentGlobalFallbackMethod")
public class OrderHystirxController {

    @Autowired
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("/success/{id}")
    public JSONResultUtil<String> success(@PathVariable("id") Integer id) {
        return paymentHystrixService.success(id);
    }

    @GetMapping("/timeout/{id}")
//    @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = {
//            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
//    })
    // 采用默认的全局fallback
    @HystrixCommand
    public JSONResultUtil timeout(@PathVariable("id") Integer id) {
        int i = 1 / 0;
        return paymentHystrixService.timeout(id);
    }

    public JSONResultUtil paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) {
        return JSONResultUtil.successWithData("消费者80,paymentTimeOutFallbackMethod, 线程池:  " + Thread.currentThread().getName() + "  8081系统繁忙或者运行报错,请稍后再试,id:  " + id);
    }

    // 下面是全局fallback方法
    public JSONResultUtil paymentGlobalFallbackMethod() {
        return JSONResultUtil.successWithData("消费者80全局服务降级,paymentGlobalFallbackMethod, 线程池:  " + Thread.currentThread().getName());
    }
}

  定义了defaultFallback的只需要在方法声明HystrixCommand注解出错或调用的服务超时即可调用paymentGlobalFallbackMethod全局降级方法。也可以对方法单独设置,单独设置的会优先取方法上设置的降级方法。如果没有声明 HystrixCommand注解,不会进行服务的降级,报错和超时都会直接走error。

测试如下:

$ curl http://localhost/consumer/hystrix/payment/timeout/1
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   155    0   155    0     0   3297      0 --:--:-- --:--:-- --:--:--  4843{"success":true,"code":"200","msg":"","data":"消费者80全局服务降级,paymentGlobalFallbackMethod, 线程池:  hystrix-OrderHystirxController-2"}

(2)通配服务降级FeignFallback:解决上面问题2,相当于每个方法fallback和业务分离

修改PaymentHystrixService增加 fallback属性

package cn.qz.cloud.service;

import cn.qz.cloud.utils.JSONResultUtil;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @Author: qlq
 * @Description
 * @Date: 14:31 2020/10/18
 */
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT", fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {
    @GetMapping("/hystrix/payment/success/{id}")
    public JSONResultUtil<String> success(@PathVariable("id") Integer id);

    @GetMapping("/hystrix/payment/timeout/{id}")
    public JSONResultUtil<String> timeout(@PathVariable("id") Integer id);
}

增加PaymentFallbackService类:相当于处理上面接口中对应方法的fallback

package cn.qz.cloud.service;

import cn.qz.cloud.utils.JSONResultUtil;
import org.springframework.stereotype.Component;

/**
 * @Author: qlq
 * @Description
 * @Date: 20:57 2020/10/18
 */
@Component
public class PaymentFallbackService implements PaymentHystrixService {
    @Override
    public JSONResultUtil<String> success(Integer id) {
        return JSONResultUtil.successWithData("PaymentFallbackService fallback,success 方法, threadName: " + Thread.currentThread().getName() + "\tid: " + id);
    }

    @Override
    public JSONResultUtil<String> timeout(Integer id) {
        return JSONResultUtil.successWithData("PaymentFallbackService fallback,timeout 方法, threadName: " + Thread.currentThread().getName() + "\tid: " + id);
    }
}

测试:

$ curl http://localhost/consumer/hystrix/payment/timeout/1
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   129    0   129    0     0     49      0 --:--:--  0:00:02 --:--:--    50{"success":true,"code":"200","msg":"","data":"PaymentFallbackService fallback,timeout 方法, threadName: HystrixTimer-1\tid: 1"}

 

5.服务熔断

  熔断机制是应对雪崩效应的一种微服务链路保护机制,当扇出链路的某个服务出错不可用或响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。

  在SpringCloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务调用的状况,当失败的调用达到一定的阈值,缺省是5s内20次调用失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand。

参考:https://martinfowler.com/bliki/CircuitBreaker.html

断路器的3种状态:

  关闭 - 当一切正常时,断路器保持闭合状态,所有调用都能访问到服务。当故障数超过预定阈值时,断路器跳闸,并进入打开状态。
  打开 - 断路器在不执行该服务的情况下为调用返回错误。
  半开 - 超时后,断路器切换到半开状态,以测试问题是否仍然存在。如果在这种半开状态下单个调用失败,则断路器再次打开。如果成功,则断路器重置回正常关闭状态。补充一下链路回复的过程:断路器开启一段时间之后(默认5s),这个时候断路器是半开状态,会让其中一个请求进行处理,如果成功则关闭断路器,若失败,继续开启断路器。

支付服务设置服务熔断:

(1)修改PaymentService增加熔断设置

package cn.qz.cloud.service;

import cn.hutool.core.util.IdUtil;
import cn.qz.cloud.utils.JSONResultUtil;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * @Author: qlq
 * @Description
 * @Date: 22:15 2020/10/17
 */
@Service
public class PaymentService {

    /**
     * 正常
     *
     * @param id
     * @return
     */
    public String success(Integer id) {
        return "success,线程池:  " + Thread.currentThread().getName() + "  success,id:  " + id;
    }

    @HystrixCommand(fallbackMethod = "timeOutHandler", commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000")
    })
    public String timeout(Integer id) {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return "timeout,线程池:  " + Thread.currentThread().getName() + "  success,id:  " + id;
    }

    public String timeOutHandler(Integer id) {
        return "线程池:  " + Thread.currentThread().getName() + "  8081系统繁忙或者运行报错,请稍后再试,id:  " + id;
    }

    //=====服务熔断
    @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback", 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 JSONResultUtil<String> circuit(Integer id) {
        if (id < 0) {
            throw new RuntimeException("******id 不能负数");
        }

        String serialNumber = IdUtil.simpleUUID();
        return JSONResultUtil.successWithData(Thread.currentThread().getName() + " 调用成功,id: " + id + " ,流水号: " + serialNumber);
    }

    public JSONResultUtil<String> paymentCircuitBreaker_fallback(Integer id) {
        return JSONResultUtil.successWithData("paymentCircuitBreaker_fallback 降级处理, " + Thread.currentThread().getName() + " 调用失败, id 不能负数, id: " + id);
    }
}

  需要注意方法上面三个重要的参数。关于更详细的配置,可以参考类:HystrixCommandProperties

快照时间窗:断路器确定是否打开统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。

请求次数:在快照时间窗内必须达到请求次数才有资格熔断,默认为20.如果达不到总次数,即使全部失败也不会开启断路器。

错误百分比阈值:默认是50,就是一半失败的情况下会开启断路。

(2)controller增加方法:

package cn.qz.cloud.controller;

import cn.qz.cloud.service.PaymentService;
import cn.qz.cloud.utils.JSONResultUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @Author: qlq
 * @Description
 * @Date: 22:22 2020/10/17
 */
@RestController
@Slf4j
@RequestMapping("/hystrix/payment")
public class PaymentController {

    @Resource
    private PaymentService paymentService;

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

    @GetMapping("/success/{id}")
    public JSONResultUtil<String> success(@PathVariable("id") Integer id) {
        String result = paymentService.success(id);
        log.info("*****result: " + result);
        return JSONResultUtil.successWithData(result);
    }

    @GetMapping("/timeout/{id}")
    public JSONResultUtil timeout(@PathVariable("id") Integer id) {
        String result = paymentService.timeout(id);
        log.info("*****result: " + result);
        return JSONResultUtil.successWithData(result);
    }

    //====服务熔断
    @GetMapping("/circuit/{id}")
    public JSONResultUtil<String> circuit(@PathVariable("id") Integer id) {
        JSONResultUtil<String> result = paymentService.circuit(id);
        log.info("****result: " + result);
        return result;
    }
}

(3)测试:

-先测试成功:

liqiang@root MINGW64 ~/Desktop
$ curl http://localhost:8081/hystrix/payment/circuit/5
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   139    0   139    0     0   4483      0 --:--:-- --:--:-- --:--:--  8687{"success":true,"code":"200","msg":"","data":"hystrix-PaymentService-10 调用成功,id: 5 ,流水号: 2f5966b1f6cd469ab8b05ef83067c156"}

liqiang@root MINGW64 ~/Desktop
$ curl http://localhost:8081/hystrix/payment/circuit/-5
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   158    0   158    0     0   3361      0 --:--:-- --:--:-- --:--:--  9875{"success":true,"code":"200","msg":"","data":"paymentCircuitBreaker_fallback 降级处理, hystrix-PaymentService-10 调用失败, id 不能负数, id: -5"}

liqiang@root MINGW64 ~/Desktop
$ curl http://localhost:8081/hystrix/payment/circuit/5
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   139    0   139    0     0   4483      0 --:--:-- --:--:-- --:--:--  8687{"success":true,"code":"200","msg":"","data":"hystrix-PaymentService-10 调用成功,id: 5 ,流水号: a528efccc26444cdac3219328616b491"}

-多调用几次http://localhost:8081/hystrix/payment/circuit/-5连接,使得10秒钟达到10次请求,并且每次都是失败。断路器会自动开启,过段时间又会关闭断路器,如下:

  可以看到即使调用成功的ID,也是走的降级的方法。待自动关闭断路器之后又自动进行链路恢复。

liqiang@root MINGW64 ~/Desktop
$ curl http://localhost:8081/hystrix/payment/circuit/5
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   152    0   152    0     0   4903      0 --:--:-- --:--:-- --:--:-- 10133{"success":true,"code":"200","msg":"","data":"paymentCircuitBreaker_fallback 降级处理, http-nio-8081-exec-3 调用失败, id 不能负数, id: 5"}

liqiang@root MINGW64 ~/Desktop
$ curl http://localhost:8081/hystrix/payment/circuit/5
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   152    0   152    0     0   4903      0 --:--:-- --:--:-- --:--:--  148k{"success":true,"code":"200","msg":"","data":"paymentCircuitBreaker_fallback 降级处理, http-nio-8081-exec-4 调用失败, id 不能负数, id: 5"}

liqiang@root MINGW64 ~/Desktop
$ curl http://localhost:8081/hystrix/payment/circuit/5
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   139    0   139    0     0   2957      0 --:--:-- --:--:-- --:--:--  8687{"success":true,"code":"200","msg":"","data":"hystrix-PaymentService-10 调用成功,id: 5 ,流水号: c6438748e5a44ca9a619161c84a8296b"}

 

6.服务限流

  暂时不做研究。之后研究alibaba的sentinel。

7.hystrix工作流程

注意分为如下步骤:

1.Construct a HystrixCommand or HystrixObservableCommand Object
2.Execute the Command
3.Is the Response Cached?
4.Is the Circuit Open?
5.Is the Thread Pool/Queue/Semaphore Full?
6.HystrixObservableCommand.construct() or HystrixCommand.run()
7.Calculate Circuit Health
8.Get the Fallback
9.Return the Successful Response

 补充:Hystrix的资源隔离策略

1.为什么需要资源隔离:

  例如,我们容器(Tomcat)配置的线程个数为1000,服务A-服务B,其中服务A的并发量非常的大,需要500个线程来执行,此时,服务A又挂了,那么这500个线程很可能就夯死了,那么剩下的服务,总共可用的线程为500个,随着并发量的增大,剩余服务挂掉的风险就会越来越大,最后导致整个系统的所有服务都不可用,直到系统宕机。这就是服务的雪崩效应。

  Hystrix就是用来做资源隔离的,比如说,当客户端向服务端发送请求时,给服务A分配了10个线程,只要超过了这个并发量就走降级服务,就算服务A挂了,最多也就导致服务A不可用,容器的10个线程不可用了,但是不会影响系统中的其他服务。

2.Hystrix的资源隔离策略有两种,分别为:线程池和信号量。

(1)线程池隔离模式:使用一个线程池来存储当前的请求,线程池对请求作处理,设置任务返回处理超时时间,堆积的请求堆积入线程池队列。这种方式需要为每个依赖的服务申请线程池,有一定的资源消耗,好处是可以应对突发流量(流量洪峰来临时,处理不完可将数据存储到线程池队里慢慢处理)。

(2)信号量隔离模式:使用一个原子计数器(或信号量)来记录当前有多少个线程在运行,请求来先判断计数器的数值,若超过设置的最大线程个数则丢弃改类型的新请求,若不超过则执行计数操作请求来计数器+1,请求返回计数器-1。这种方式是严格的控制线程且立即返回模式,无法应对突发流量(流量洪峰来临时,处理的线程超过数量,其他的请求会直接返回,不继续去请求依赖的服务)

3.可以切换hystrix的资源隔离方式,默认是线程池模式。可以对某个方法单独切换,也可以切换全局的,切换全局的如下:

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 1000
          strategy: SEMAPHORE  # 信号量隔离
#          strategy: THREAD  # 线程池
          semaphore:
            maxConcurrentRequests: 100  # 最大信号量上限

(1) 默认是线程池模式,HystrixCommand 降级以及熔断方法完全采用hystrix的线程池
(2) 设置信号量模式: 会使用tomcat的线程池,可以通过信号量的多少控制并发量。

 

  参考:https://github.com/Netflix/Hystrix/wiki/How-it-Works

3.Hystrixdashboard实现服务监控

  Hystrix提供了对于微服务调用状态的监控信息,但是需要结合spring-boot-actuator模块一起使用。Hystrix Dashboard是Hystrix的一个组件,Hystrix Dashboard提供一个断路器的监控面板,可以使我们更好的监控服务和集群的状态。

1.新建监控微服务

1.新建模块cloud-consumer-hystrix-dashboard9001

 2.完善pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud</artifactId>
        <groupId>cn.qz.cloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumer-hystrix-dashboard9001</artifactId>

    <dependencies>
        <!--引入自己抽取的工具包-->
        <dependency>
            <groupId>cn.qz.cloud</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>

</project>

3.修改yml

server:
  port: 9001

4.启动类:

package cn.qz.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;

@SpringBootApplication
// 开启仪表盘监控注解
@EnableHystrixDashboard
public class HystrixDashboardMain9001 {

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

2.修改原来的cloud-provider-hystrix-payment8081服务:

注意pom需要加上:

        <!--web-->
        <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>

修改主启动类:(必须增加下面的getServlet配置,否则报错连接不到)

package cn.qz.cloud;

import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;

/**
 * @Author: qlq
 * @Description
 * @Date: 22:08 2020/10/17
 */
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
@EnableHystrix
public class PaymentHystrixMain8081 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8081.class, args);
    }

    /**
     * 此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
     * ServletRegistrationBean因为SpringBoot的默认路径不是 “/hystrix.stream"
     * 只要在自己的项目里配置上下的servlet就可以了
     */
    @Bean
    public ServletRegistrationBean getServlet() {
        HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
        registrationBean.setLoadOnStartup(1);
        registrationBean.addUrlMappings("/hystrix.stream");
        registrationBean.setName("HystrixMetricsStreamServlet");
        return registrationBean;
    }
}

修改后启动服务

3.启动hystrixdashboard服务

(1)访问首页如下:

 (2)多次访问http://localhost:8081/hystrix/payment/circuit/-1,使其断路器打开

(3)输入以下地址查看:http://localhost:8081/hystrix.stream

 (4)进入monitor

(5)上面测试的效果不是很明显,可以用jmeter批量进行测试

补充:可以设置hystrix默认执行时长,超时进行降级处理,这里需要注意下Ribbon链接时长和等待请求处理时长的影响

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 1000

这些配置的属性都可以从类HystrixPropertiesManager、 HystrixCommandProperties 中查看。

补充:如果通过feign调用服务没有进行服务的降级。比如A服务调B服务,B服务抛出除0异常,A服务报错如下:

会报feign相关的错误:

2020-11-26 14:54:17.603 ERROR 27404 --- [o-auto-1-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is com.netflix.hystrix.exception.HystrixRuntimeException: PaymentHystrixService#error() failed and no fallback available.] with root cause

feign.FeignException$InternalServerError: status 500 reading PaymentHystrixService#error()
    at feign.FeignException.serverErrorStatus(FeignException.java:195) ~[feign-core-10.4.0.jar:na]
    at feign.FeignException.errorStatus(FeignException.java:144) ~[feign-core-10.4.0.jar:na]
    at feign.FeignException.errorStatus(FeignException.java:133) ~[feign-core-10.4.0.jar:na]
    at feign.codec.ErrorDecoder$Default.decode(ErrorDecoder.java:92) ~[feign-core-10.4.0.jar:na]
    at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:151) ~[feign-core-10.4.0.jar:na]
    at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:80) ~[feign-core-10.4.0.jar:na]
    at feign.hystrix.HystrixInvocationHandler$1.run(HystrixInvocationHandler.java:109) ~[feign-hystrix-10.4.0.jar:na]
    at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:302) ~[hystrix-core-1.5.18.jar:1.5.18]
    at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:298) ~[hystrix-core-1.5.18.jar:1.5.18]
    at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:46) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:35) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.Observable.unsafeSubscribe(Observable.java:10327) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:51) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:35) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.Observable.unsafeSubscribe(Observable.java:10327) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:41) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:30) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.Observable.unsafeSubscribe(Observable.java:10327) ~[rxjava-1.3.8.jar:1.3.8]
    at rx.internal.operators.OperatorSubscribeOn$SubscribeOnSubscriber.call(OperatorSubscribeOn.java:100) ~[rxjava-1.3.8.jar:1.3.8]
    at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction$1.call(HystrixContexSchedulerAction.java:56) ~[hystrix-core-1.5.18.jar:1.5.18]
    at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction$1.call(HystrixContexSchedulerAction.java:47) ~[hystrix-core-1.5.18.jar:1.5.18]
    at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction.call(HystrixContexSchedulerAction.java:69) ~[hystrix-core-1.5.18.jar:1.5.18]
    at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55) ~[rxjava-1.3.8.jar:1.3.8]
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) ~[na:1.8.0_171]
    at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[na:1.8.0_171]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[na:1.8.0_171]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[na:1.8.0_171]
    at java.lang.Thread.run(Thread.java:748) [na:1.8.0_171]

补充:另外每个应用可以通过全局异常拦截器进行规避一些错误,拦截到错误之后将错误信息返回给上个服务。

 

posted @ 2020-10-19 22:47  QiaoZhi  阅读(1461)  评论(0编辑  收藏  举报