【SpringCloud】9. 服务保护的实现

258 阅读4分钟

Hystrix组件

Hystrix是一个用于处理分布式系统的延迟和容错的开源库。在分布式系统中,许多依赖不可避免的会调用失败,超时异常等。Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障(服务雪崩现象),提高分布式系统的健壮性。

Hystrix断路器原理

1. 原理

断路器是一个开关装置,有全开(OPEN),半开(HALF_OPEN),关闭(CLOSED)三种状态,默认处于CLOSED状态

在服务调用的过程中,代理类维护了最近调用失败的次数。如果某次调用失败,则使失败次数加1。如果最近失败次数超过了在给定时间内允许失败的阈值,则代理类切换到OPEN状态。此时代理开启了一个超时时钟,当该时钟计时结束,自动切换到HALF_OPEN状态。该超时时间的设定是给了系统一次机会来修正导致调用失败的错误。

2. 三种状态

  • CLOSED:关闭状态。所有请求都正常访问。

  • OPEN:全开状态。每次开启有时间限制(默认5s),这时所有请求访问失败,自动触发相应的降级方法。

  • HALF_OPEN:半开状态。OPEN状态开启后,进入5s休眠计时,5s后自动进入HALF_OPEN状态。此时会释放1次请求通过,若这个请求是健康的,则会关闭断路器。否则继续保持打开,再次进行5秒休眠计时。

3. 断路器打开的条件

  • 规定时间内请求次数达到一定的阀值的时候(默认10秒内超过20个请求次数)。

  • 规定时间内请求次数的失败率达到一定的阀值时候(默认10秒内超过50%的请求失败)。

服务熔断的实现

服务熔断在服务端 (被调用方) 实现,由服务端对异常服务进行降级,返回错误信息。

1. 指定降级方法

  1. 被调用方引入依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
    
  2. 被调用方开启断路器

    @SpringBootApplication
    @EnableDiscoveryClient
    // Hystrix开启链路监控
    @EnableCircuitBreaker
    public class ProductsApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ProductsApplication.class, args);
        }
    
    }
    
  3. 被调用方创建降级方法

    /**
     * 指定降级方法,只有当绑定的被监控方法中出现异常,服务器线程资源耗尽时等触发熔断机制时被调用
     * 参数必须和绑定的被监控方法一致
     * @param id 绑定方法的参数
     * @return 绑定方法出现异常时返回的fallback
     */
    public Map<String, Object> findOneFallback(int id) {
        Map<String, Object> fallback = new HashMap<>();
        fallback.put("status", false);
        fallback.put("msg", "findOne出现异常");
        fallback.put("id", id);
        return fallback;
    }
    
    
  4. 被调用方给被监控的方法绑定降级方法

    @GetMapping("/product/findOne")
    // 熔断策略
    @HystrixCommand(fallbackMethod = "findOneFallback")
    public Map<String, Object> findOne(@RequestParam("id") int id) {
        // 如果传递的id小于0,出现异常,但是异常不会被抛出
        // 同时断路器半开,熔断方法自动触发
        if (id < 0) {
            throw new RuntimeException("id不能小于0");
        }
        Map<String, Object> result = new HashMap<>();
        result.put("status", true);
        result.put("msg", "商品");
        result.put("id", id);
        return result;
    }
    

2. 默认降级方法

如果为每一个服务方法开发一个降级方法,对于我们来说,可能会出现大量的代码的冗余,不利于维护。这个时候就需要加入默认服务降级处理方法。

  1. 被调用方引入依赖,开启断路器。

  2. 被调用方创建默认降级方法

    /**
     * 默认降级方法,可以与多个被监控的方法进行绑定
     * @return 通用的fallback
     */
    public Map<String, Object> defaultFallback() {
        Map<String, Object> fallback = new HashMap<>();
        fallback.put("status", false);
        fallback.put("msg", "服务提供方出现异常");
        return fallback;
    }
    
  3. 被调用方给多个方法绑定同一个降级方法

    @PostMapping("/product/updateOne")
    // 熔断策略
    @HystrixCommand(defaultFallback = "defaultFallback")
    public Map<String, Object> updateOne(@RequestBody Product product) {
        Map<String, Object> result = new HashMap<>();
        result.put("status", true);
        result.put("msg", "修改商品");
        result.put("product", product);
        return result;
    }
    

服务降级的实现

服务降级在客户端 (调用方) 实现,当某个服务熔断之后,服务器将不再被调用,此刻客户端可以自己准备一个本地的fallback回调,返回一个错误信息。

当前Spring Cloud处理方案中,一般使用OpenFeign + Hystrix解决服务降级。

  1. 调用方引入OpenFeign和Hystrix的依赖。

  2. 调用方application.yml开启OpenFeign对Hystrix的支持

    feign:
      # feign对hystrix支持
      hystrix:
        enabled: true
    
  3. 调用方创建fallback包。

  4. 创建Feign客户端对应的Fallback类,实现Feign客户端接口,并对客户端中的每一个方法分别做降级方法

    @Component
    public class ProductClientFallback implements ProductClient {
    
        @Override
        public Map<String, Object> showProducts() {
            Map<String, Object> fallback = new HashMap<>();
            fallback.put("states", false);
            fallback.put("msg", "showProducts服务关闭");
            return fallback;
        }
    
        @Override
        public Map<String, Object> findOne(int id) {
            Map<String, Object> fallback = new HashMap<>();
            fallback.put("states", false);
            fallback.put("msg", "findOne服务关闭");
            return fallback;
        }
    
        @Override
        public Map<String, Object> updateOne(Product product) {
            Map<String, Object> fallback = new HashMap<>();
            fallback.put("states", false);
            fallback.put("msg", "updateOne服务关闭");
            return fallback;
        }
    
    }
    
  5. 将Feign客户端指向对应的Fallback类

    // 注解中填写服务名和绑定的Fallback类
    @FeignClient(value = "products", fallback = ProductClientFallback.class)
    public interface ProductClient {
    
        @GetMapping("/product/showProducts")
        Map<String, Object> showProducts();
    
        @GetMapping("/product/findOne")
        Map<String, Object> findOne(@RequestParam("id") int id);
    
        @PostMapping("/product/updateOne")
        Map<String, Object> updateOne(@RequestBody Product product);
        
    }
    

注意:

  1. 在构建Feign客户端对应的Fallback类时,以下代码会报错。因为@Autowired是通过对象类型注入对象的,如果ProductClient接口下实现了多个类,那么@Autowired就不能准确的指定到底是哪一个实现类,从而报错。

    @Autowired
    private ProductClient productClient;
    

    解决方案:在Feign客户端上加@Primary,让其优先注入接口而不是它的实现类

    // 解决注入ProductClient实例优先注入接口而不是实现类
    @Primary
    // 注解中填写服务名和绑定的Fallback类
    @FeignClient(value = "products", fallback = ProductClientFallback.class)
    public interface ProductClient {
        ...
    }
    
  2. 如果调用端和被调用端对于同一个方法都做了降级方法,那么两个降级方法的返回类型必须一致

    比如对于showProducts方法,在调用端中降级策略为:

    @Override
    public Map<String, Object> showProducts() {
        Map<String, Object> fallback = new HashMap<>();
        fallback.put("states", false);
        fallback.put("msg", "showProducts服务关闭");
        return fallback;
    }
    

    在被调用端中降级策略为:

    public Map<String, Object> defaultFallback() {
        Map<String, Object> fallback = new HashMap<>();
        fallback.put("status", false);
        fallback.put("msg", "服务提供方出现异常");
        return fallback;
    }
    

    降级方法的返回类型必须都是Map<String, Object>。

关于Hystrix Dashboard

Hystrix Dashboard的一个主要优点是它收集了关于每个HystrixCommand的一组度量。Hystrix仪表板以高效的方式显示每个断路器的运行状况。

由于Hystrix已经进入维护状态,Spring Cloud在F版本后适配的JQuery版本和Hystrix Dashboard中的JQuery版本不一致,导致Hystrix Dashboard无法正常使用。在新版G,H中已经基本不会使用Hystrix Dashboard。