zoukankan      html  css  js  c++  java
  • Spring Cloud 之 Netflix Hystrix 服务容错

    本文较大篇幅引用https://www.mrhelloworld.com/hystrix-circuit-breaker/,版权归该文章作者所有

    hystrix是什么?

    Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比 如超时、异常等,

    Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分 布式系统的弹性。

    “断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,

    这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。

    大型项目中会出现的一些问题

    典型的一个案例就是服务血崩效应 我们来看一张图:

     上图是一条微服务调用链, 正常的情况我们就不必在讨论了, 我们来说一下非正常情况, 假设现在 微服务H 响应 时间过长,或者微服务H直接down机了如图:

     来看下上图, 我们联想一下上图, 如果发生这种情况, 也就是说所有发给微服务D的请求 都会被卡在微服务H 那, 就会导致线程一直累计在这里, 那么其他的微服务(比如A,B,C...) 就没有可用线程了, 导致整个服务器 崩溃,这就是服务血崩。
    导致服务雪崩的情况我们来总结一下,再看看怎么解决:程序BUG,数据不匹配,响应时间过长,服务不可用等等.....

    针对上面的问题,我们来看看有哪些解决方案 :

      服务限流

      超时监控

      服务熔断

      服务降级

      hystrix-demo 聚合工程。SpringBoot 2.2.4.RELEASESpring Cloud Hoxton.SR1

    • eureka-server:注册中心eureka(端口8761)
    • eureka-server02:注册中心eureka(端口8762,两个注册中心相互注册,搭建过程省略)
    • product-service:商品服务,提供了 /product/{id} 接口,/product/list 接口,/product/listByIds 接口
    • order-service-rest:订单服务,基于 Ribbon 通过 RestTemplate 调用商品服务
    • order-server-feign:订单服务,基于 Feign 通过声明式服务调用商品服务
    <?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">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.example</groupId>
        <artifactId>product-service</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <!-- 继承父依赖 -->
        <parent>
            <groupId>com.example</groupId>
            <artifactId>hystrix-demo</artifactId>
            <version>1.0-SNAPSHOT</version>
        </parent>
    
        <!-- 项目依赖 -->
        <dependencies>
            <!-- netflix eureka client 依赖 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
            <!-- spring boot web 依赖 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!-- lombok 依赖 -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <scope>provided</scope>
            </dependency>
    
            <!-- spring boot test 依赖 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
        </dependencies>
    
    </project>
    server:
      port: 7070 # 端口
    
    spring:
      application:
        name: product-service # 应用名称
    
    # 配置 Eureka Server 注册中心
    eureka:
      instance:
        prefer-ip-address: true       # 是否使用 ip 地址注册
        instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
      client:
        service-url:                  # 设置服务注册中心地址
          defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/
    package com.example.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.io.Serializable;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Product implements Serializable {
    
        private Integer id;
        private String productName;
        private Integer productNum;
        private Double productPrice;
    
    }
    package com.example.service;
    
    import com.example.pojo.Product;
    
    import java.util.List;
    
    /**
     * 商品服务
     */
    public interface ProductService {
    
        /**
         * 查询商品列表
         *
         * @return
         */
        List<Product> selectProductList();
    
        /**
         * 根据多个主键查询商品
         *
         * @param ids
         * @return
         */
        List<Product> selectProductListByIds(List<Integer> ids);
    
        /**
         * 根据主键查询商品
         *
         * @param id
         * @return
         */
        Product selectProductById(Integer id);
    
    }

    实现类

    package com.example.service.impl;
    
    import com.example.pojo.Product;
    import com.example.service.ProductService;
    import org.springframework.stereotype.Service;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    
    /**
     * 商品服务
     */
    @Service
    public class ProductServiceImpl implements ProductService {
    
        /**
         * 查询商品列表
         *
         * @return
         */
        @Override
        public List<Product> selectProductList() {
            return Arrays.asList(
                    new Product(1, "华为手机", 1, 5800D),
                    new Product(2, "联想笔记本", 1, 6888D),
                    new Product(3, "小米平板", 5, 2020D)
            );
        }
    
        /**
         * 根据多个主键查询商品
         *
         * @param ids
         * @return
         */
        @Override
        public List<Product> selectProductListByIds(List<Integer> ids) {
            List<Product> products = new ArrayList<>();
            ids.forEach(id -> products.add(new Product(id, "电视机" + id, 1, 5800D)));
            return products;
        }
    
        /**
         * 根据主键查询商品
         *
         * @param id
         * @return
         */
        @Override
        public Product selectProductById(Integer id) {
            return new Product(id, "冰箱", 1, 2666D);
        }
    
    }
    package com.example.controller;
    
    import com.example.pojo.Product;
    import com.example.service.ProductService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
    
    import java.util.List;
    
    @RestController
    @RequestMapping("/product")
    public class ProductController {
    
        @Autowired
        private ProductService productService;
    
        /**
         * 查询商品列表
         *
         * @return
         */
        @GetMapping("/list")
        public List<Product> selectProductList() {
            return productService.selectProductList();
        }
    
        /**
         * 根据多个主键查询商品
         *
         * @param ids
         * @return
         */
        @GetMapping("/listByIds")
        public List<Product> selectProductListByIds(@RequestParam("id") List<Integer> ids) {
            return productService.selectProductListByIds(ids);
        }
    
        /**
         * 根据主键查询商品
         *
         * @param id
         * @return
         */
        @GetMapping("/{id}")
        public Product selectProductById(@PathVariable("id") Integer id) {
            return productService.selectProductById(id);
        }
    
    }
    package com.example;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    // 开启 EurekaClient 注解,目前版本如果配置了 Eureka 注册中心,默认会开启该注解
    //@EnableEurekaClient
    public class ProductServiceApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ProductServiceApplication.class, args);
        }
    
    }
    <?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">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.example</groupId>
        <artifactId>order-service-rest</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <!-- 继承父依赖 -->
        <parent>
            <groupId>com.example</groupId>
            <artifactId>hystrix-demo</artifactId>
            <version>1.0-SNAPSHOT</version>
        </parent>
    
        <!-- 项目依赖 -->
        <dependencies>
            <!-- netflix eureka client 依赖 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
            <!-- spring boot web 依赖 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!-- lombok 依赖 -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <scope>provided</scope>
            </dependency>
    
            <!-- spring boot test 依赖 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
        </dependencies>
      
    </project>
    server:
      port: 8080 # 端口
    
    spring:
      application:
        name: order-service-rest # 应用名称
    
    # 配置 Eureka Server 注册中心
    eureka:
      instance:
        prefer-ip-address: true       # 是否使用 ip 地址注册
        instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
      client:
        service-url:                  # 设置服务注册中心地址
          defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/
    package com.example.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.io.Serializable;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Product implements Serializable {
    
        private Integer id;
        private String productName;
        private Integer productNum;
        private Double productPrice;
    
    }

    order

    package com.example.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.io.Serializable;
    import java.util.List;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Order implements Serializable {
    
        private Integer id;
        private String orderNo;
        private String orderAddress;
        private Double totalPrice;
        private List<Product> productList;
    
    }
    package com.example.service;
    
    import com.example.pojo.Product;
    
    import java.util.List;
    
    /**
     * 商品管理
     */
    public interface ProductService {
    
        /**
         * 查询商品列表
         *
         * @return
         */
        List<Product> selectProductList();
    
        /**
         * 根据多个主键查询商品
         *
         * @param ids
         * @return
         */
        List<Product> selectProductListByIds(List<Integer> ids);
    
        /**
         * 根据主键查询商品
         *
         * @param id
         * @return
         */
        Product selectProductById(Integer id);
    
    }

    我们先使用 Ribbon 并通过 RestTemplate 来实现远程服务的调用product服务。先讲解 RestTemplate 方式的服务容错处理。

    ProductServiceImpl.java

    package com.example.service.impl;
    
    import com.example.pojo.Product;
    import com.example.service.ProductService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.ParameterizedTypeReference;
    import org.springframework.http.HttpMethod;
    import org.springframework.stereotype.Service;
    import org.springframework.web.client.RestTemplate;
    
    import java.util.List;
    
    /**
     * 商品管理
     */
    @Service
    public class ProductServiceImpl implements ProductService {
    
        @Autowired
        private RestTemplate restTemplate;
    
        /**
         * 查询商品列表
         *
         * @return
         */
        @Override
        public List<Product> selectProductList() {
            // ResponseEntity: 封装了返回数据
            return restTemplate.exchange(
                    "http://product-service/product/list",
                    HttpMethod.GET,
                    null,
                    new ParameterizedTypeReference<List<Product>>() {
                    }).getBody();
        }
    
        /**
         * 根据多个主键查询商品
         *
         * @param ids
         * @return
         */
        @Override
        public List<Product> selectProductListByIds(List<Integer> ids) {
            StringBuffer sb = new StringBuffer();
            ids.forEach(id -> sb.append("id=" + id + "&"));
            return restTemplate.getForObject("http://product-service/product/listByIds?" + sb.toString(), List.class);
        }
    
        /**
         * 根据主键查询商品
         *
         * @param id
         * @return
         */
        @Override
        public Product selectProductById(Integer id) {
            return restTemplate.getForObject("http://product-service/product/" + id, Product.class);
        }
    
    }

    OrderService.java

    package com.example.service;
    
    import com.example.pojo.Order;
    
    public interface OrderService {
    
        /**
         * 根据主键查询订单
         *
         * @param id
         * @return
         */
        Order selectOrderById(Integer id);
    
        /**
         * 根据主键查询订单
         *
         * @param id
         * @return
         */
        Order queryOrderById(Integer id);
    
        /**
         * 根据主键查询订单
         *
         * @param id
         * @return
         */
        Order searchOrderById(Integer id);
    
    }

    OrderServiceImpl.java

    package com.example.service.impl;
    
    import com.example.pojo.Order;
    import com.example.service.OrderService;
    import com.example.service.ProductService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.Arrays;
    
    @Service
    public class OrderServiceImpl implements OrderService {
    
        @Autowired
        private ProductService productService;
    
        /**
         * 根据主键查询订单
         *
         * @param id
         * @return
         */
        @Override
        public Order selectOrderById(Integer id) {
            return new Order(id, "order-001", "中国", 22788D,
                    productService.selectProductList());
        }
    
        /**
         * 根据主键查询订单
         *
         * @param id
         * @return
         */
        @Override
        public Order queryOrderById(Integer id) {
            return new Order(id, "order-002", "中国", 11600D,
                    productService.selectProductListByIds(Arrays.asList(1, 2)));
        }
    
        /**
         * 根据主键查询订单
         *
         * @param id
         * @return
         */
        @Override
        public Order searchOrderById(Integer id) {
            return new Order(id, "order-003", "中国", 2666D,
                    Arrays.asList(productService.selectProductById(5)));
        }
    
    }
    package com.example.controller;
    
    import com.example.pojo.Order;
    import com.example.service.OrderService;
    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;
    
    @RestController
    @RequestMapping("/order")
    public class OrderController {
    
        @Autowired
        private OrderService orderService;
    
        /**
         * 根据主键查询订单-调用商品服务 /product/list
         *
         * @param id
         * @return
         */
        @GetMapping("/{id}/product/list")
        public Order selectOrderById(@PathVariable("id") Integer id) {
            return orderService.selectOrderById(id);
        }
    
        /**
         * 根据主键查询订单-调用商品服务 /product/listByIds
         *
         * @param id
         * @return
         */
        @GetMapping("/{id}/product/listByIds")
        public Order queryOrderById(@PathVariable("id") Integer id) {
            return orderService.queryOrderById(id);
        }
    
        /**
         * 根据主键查询订单-调用商品服务 /product/{id}
         *
         * @param id
         * @return
         */
        @GetMapping("/{id}/product")
        public Order searchOrderById(@PathVariable("id") Integer id) {
            return orderService.searchOrderById(id);
        }
    
    }
    package com.example;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.client.RestTemplate;
    
    @SpringBootApplication
    public class OrderServiceRestApplication {
    
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    
        public static void main(String[] args) {
            SpringApplication.run(OrderServiceRestApplication.class, args);
        }
    
    }

    我们先来解释一下降级,降级是当我们的某个微服务响应时间过长,或者不可用了,讲白了也就是那个微服务调用不了了,我们不能把错误信息返回出来,或者让他一直卡在那里,所以要在准备一个对应的策略(一个方法)当发生 这种问题的时候我们直接调用这个方法来快速返回这个请求,不让他一直卡在那 。

    讲了这么多,我们来看看具体怎么操作: 我们刚刚说了某个微服务调用不了了要做降级,也就是说,要在调用方做降级(不然那个微服务都down掉了再做 降级也没什么意义了) 比如说我们 order调用product那么就在order做降级

    服务消费者 pom.xml 添加 hystrix 依赖。

    <!-- spring-cloud netflix hystrix 依赖 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>

    服务消费者业务层代码添加服务降级规则。

    然后在我们的需要做服务降级方法上面加入注解@HystrixCommand(fallbackMethod就是我们刚刚说的方法的名字)

    import com.example.pojo.Product;
    import com.example.service.ProductService;
    import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.web.client.RestTemplate;
    
    import java.time.LocalDateTime;
    import java.time.format.DateTimeFormatter;
    
    /**
     * 商品管理
     */
    @Service
    public class ProductServiceImpl implements ProductService {
    
        @Autowired
        private RestTemplate restTemplate;
    
        /**
         * 根据主键查询商品
         *
         * @param id
         * @return
         */
        // 声明需要服务容错的方法
        // 服务降级
        @HystrixCommand(fallbackMethod = "selectProductByIdFallback")
        @Override
        public Product selectProductById(Integer id) {
            return restTemplate.getForObject("http://product-service/product/" + id, Product.class);
        }
    
        // 托底数据
        private Product selectProductByIdFallback(Integer id) {
            return new Product(id, "托底数据", 1, 2666D);
        }
    
    }

    服务消费者启动类开启熔断器注解。

    package com.example;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.client.RestTemplate;
    
    // 开启熔断器注解 2 选 1,@EnableHystrix 封装了 @EnableCircuitBreaker
    // @EnableHystrix
    @EnableCircuitBreaker
    @SpringBootApplication
    public class OrderServiceRestApplication {
    
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    
        public static void main(String[] args) {
            SpringApplication.run(OrderServiceRestApplication.class, args);
        }
    
    }

    @EnableHystrix 或者@EnableCircuitBreaker(他们之间是一个继承关系,2个注解所描述的内容是 完全一样的) 

    访问:http://localhost:9090/order/3/product 结果如下:

     关闭服务提供者,再次访问:http://localhost:9090/order/3/product 结果如下:

     通过结果可以看到,服务降级已经启用。当 Provider 不可用时返回托底数据,直到服务可用快速恢复。

    我们在服务提供者的该方法上添加2秒延时,

    @GetMapping("/{id}")
        public Product selectProductById(@PathVariable("id") Integer id) throws InterruptedException {
            Thread.sleep(2000);
            return productService.selectProductById(id);
        }

    再重启服务提供者product-service测试一次

     可能有些同学有疑问, 我这里什么都没干, 就让他休眠了一下 , 怎么就知道我这里超时了呢? 因为hystrix他有默认的超时监听,当你这个请求默认超过了1秒钟就会超时

    当然,这个可以配置的,至于怎么配 置,待会儿我会把一些配置统一列出来

    讲了这么多, 这个降级到底有什么用呢?

    第一, 他可以监听你的请求有没有超时,

    第二,报错了他这里直接截断了没有让请求一直卡在这里

    其实降级还有一个好处, 就是当你的系统马上迎来大量的并发(双十一秒杀这种 或者促销活动) 这时候如果发现系 统马上承载不了这么大的并发时, 可以考虑先关闭一些不重要的微服务(在降级方法里面返回一个比较友好的信 息),

    把资源让给主微服务,总结一下就是 整体资源快不够了,忍痛将某些服务先关掉,待渡过难关,再开启回来。

    服务熔断一般是指软件系统中,由于某些原因使得服务出现了过载现象,为防止造成整个系统故障,从而采用的一种保护措施,所以很多地方把熔断亦称为过载保护。

     其实熔断,就好像我们生活中的跳闸一样, 比如说你的电路出故障了,为了防止出现 大型事故 这里直接切断了你的电源以免意外继续发生, 把这个概念放在我们程序上也是如此, 当一个微服务调用多 次出现问题时(默认是10秒内20次当然 这个也能配置),hystrix就会采取熔断机制,不再继续调用你的方法(会 在默认5秒钟内和电器短路一样,5秒钟后会试探性的先关闭熔断机制,但是如果这时候再失败一次{之前是20次} 那么又会重新进行熔断) 而是直接调用降级方法,这样就一定程度上避免了服务雪崩的问题

    服务消费者业务层代码添加服务熔断规则。

    package com.example.service.impl;
    
    import com.example.pojo.Product;
    import com.example.service.ProductService;
    import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
    import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
    import com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.web.client.RestTemplate;
    
    import java.time.LocalDateTime;
    import java.time.format.DateTimeFormatter;
    
    /**
     * 商品管理
     */
    @Service
    public class ProductServiceImpl implements ProductService {
    
        @Autowired
        private RestTemplate restTemplate;
    
        /**
         * 根据主键查询商品
         *
         * @param id
         * @return
         */
        // 声明需要服务容错的方法
        // 服务熔断
        @HystrixCommand(commandProperties = {
                // 当请求符合熔断条件触发 fallbackMethod 默认 20 个
                @HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD,
                        value = "10"),
                // 请求错误率大于 50% 就启动熔断器,然后 for 循环发起重试请求,当请求符合熔断条件触发 fallbackMethod
                @HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE,
                        value = "50"),
                // 熔断多少秒后去重试请求,默认 5s
                @HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS,
                        value = "5000"),
        }, fallbackMethod = "selectProductByIdFallback")
        @Override
        public Product selectProductById(Integer id) {
            System.out.println("-----selectProductById-----"
                    + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_TIME));
            // 模拟查询主键为 1 的商品信息会导致异常
            if (1 == id)
                throw new RuntimeException("查询主键为 1 的商品信息导致异常");
            return restTemplate.getForObject("http://product-service/product/" + id, Product.class);
        }
    
        // 托底数据
        private Product selectProductByIdFallback(Integer id) {
            return new Product(id, "托底数据", 1, 2666D);
        }
    
    }

    服务消费者启动类开启熔断器注解。

    这个东西光笔记不太好测试,只能你们自己去测试了

    我们在父工程下再创建一个 Consumer 项目这次是基于 Feign 实现声明式服务调用。

    服务提供者添加 openfeign 依赖,openfeign 默认集成了 hystrix 依赖。

    <?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">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.example</groupId>
        <artifactId>order-service-feign</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <!-- 继承父依赖 -->
        <parent>
            <groupId>com.example</groupId>
            <artifactId>hystrix-demo</artifactId>
            <version>1.0-SNAPSHOT</version>
        </parent>
    
        <!-- 项目依赖 -->
        <dependencies>
            <!-- netflix eureka client 依赖 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
            <!-- spring cloud openfeign 依赖 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
            <!-- spring boot web 依赖 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!-- lombok 依赖 -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <scope>provided</scope>
            </dependency>
    
            <!-- spring boot test 依赖 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
        </dependencies>
    
    </project>

    服务提供者需要开启 Feign 对于 Hystrix 的支持。

    server:
      port: 9091 # 端口
    
    spring:
      application:
        name: order-service-feign # 应用名称
    
    # 配置 Eureka Server 注册中心
    eureka:
      instance:
        prefer-ip-address: true       # 是否使用 ip 地址注册
        instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
      client:
        service-url:                  # 设置服务注册中心地址
          defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/
    
    # Feign 开启 Hystrix 支持
    feign:
      hystrix:
        enabled: true
    package com.example.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.io.Serializable;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Product implements Serializable {
    
        private Integer id;
        private String productName;
        private Integer productNum;
        private Double productPrice;
    
    }

    Order.java

    package com.example.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.io.Serializable;
    import java.util.List;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Order implements Serializable {
    
        private Integer id;
        private String orderNo;
        private String orderAddress;
        private Double totalPrice;
        private List<Product> productList;
    
    }
    package com.example.service;
    
    import com.example.fallback.ProductServiceFallback;
    import com.example.pojo.Product;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestParam;
    
    import java.util.List;
    
    // 声明需要调用的服务和服务熔断处理类
    @FeignClient(value = "product-service", fallback = ProductServiceFallback.class)
    public interface ProductService {
    
        /**
         * 查询商品列表
         *
         * @return
         */
        @GetMapping("/product/list")
        List<Product> selectProductList();
    
        /**
         * 根据多个主键查询商品
         *
         * @param ids
         * @return
         */
        @GetMapping("/product/listByIds")
        List<Product> selectProductListByIds(@RequestParam("id") List<Integer> ids);
    
        /**
         * 根据主键查询商品
         *
         * @param id
         * @return
         */
        @GetMapping("/product/{id}")
        Product selectProductById(@PathVariable("id") Integer id);
    
    }

    OrderService.java

    package com.example.service;
    
    import com.example.pojo.Order;
    
    public interface OrderService {
    
        /**
         * 根据主键查询订单
         *
         * @param id
         * @return
         */
        Order selectOrderById(Integer id);
    
        /**
         * 根据主键查询订单
         *
         * @param id
         * @return
         */
        Order queryOrderById(Integer id);
    
        /**
         * 根据主键查询订单
         *
         * @param id
         * @return
         */
        Order searchOrderById(Integer id);
    
    }

    OrderServiceImpl.java

    package com.example.service.impl;
    
    import com.example.pojo.Order;
    import com.example.service.OrderService;
    import com.example.service.ProductService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.Arrays;
    
    @Service
    public class OrderServiceImpl implements OrderService {
    
        @Autowired
        private ProductService productService;
    
        /**
         * 根据主键查询订单
         *
         * @param id
         * @return
         */
        @Override
        public Order selectOrderById(Integer id) {
            return new Order(id, "order-001", "中国", 22788D,
                    productService.selectProductList());
        }
    
        /**
         * 根据主键查询订单
         *
         * @param id
         * @return
         */
        @Override
        public Order queryOrderById(Integer id) {
            return new Order(id, "order-002", "中国", 11600D,
                    productService.selectProductListByIds(Arrays.asList(1, 2)));
        }
    
        /**
         * 根据主键查询订单
         *
         * @param id
         * @return
         */
        @Override
        public Order searchOrderById(Integer id) {
            return new Order(id, "order-003", "中国", 2666D,
                    Arrays.asList(productService.selectProductById(5)));
        }
    
    }

    ProductServiceFallback.java

    package com.example.fallback;
    
    import com.example.pojo.Product;
    import com.example.service.ProductService;
    import org.springframework.stereotype.Component;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    
    /**
     * 服务熔断降级处理
     */
    @Component
    public class ProductServiceFallback implements ProductService {
    
        // 查询商品列表接口的托底数据
        @Override
        public List<Product> selectProductList() {
            return Arrays.asList(
                    new Product(1, "托底数据-华为手机", 1, 5800D),
                    new Product(2, "托底数据-联想笔记本", 1, 6888D),
                    new Product(3, "托底数据-小米平板", 5, 2020D)
            );
        }
    
        // 根据多个主键查询商品接口的托底数据
        @Override
        public List<Product> selectProductListByIds(List<Integer> ids) {
            List<Product> products = new ArrayList<>();
            ids.forEach(id -> products.add(new Product(id, "托底数据-电视机" + id, 1, 5800D)));
            return products;
        }
    
        // 根据主键查询商品接口的托底数据
        @Override
        public Product selectProductById(Integer id) {
            return new Product(id, "托底数据", 1, 2666D);
        }
    
    }
    package com.example.controller;
    
    import com.example.pojo.Order;
    import com.example.service.OrderService;
    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;
    
    @RestController
    @RequestMapping("/order")
    public class OrderController {
    
        @Autowired
        private OrderService orderService;
    
        /**
         * 根据主键查询订单-调用商品服务 /product/list
         *
         * @param id
         * @return
         */
        @GetMapping("/{id}/product/list")
        public Order selectOrderById(@PathVariable("id") Integer id) {
            return orderService.selectOrderById(id);
        }
    
        /**
         * 根据主键查询订单-调用商品服务 /product/listByIds
         *
         * @param id
         * @return
         */
        @GetMapping("/{id}/product/listByIds")
        public Order queryOrderById(@PathVariable("id") Integer id) {
            return orderService.queryOrderById(id);
        }
    
        /**
         * 根据主键查询订单-调用商品服务 /product/{id}
         *
         * @param id
         * @return
         */
        @GetMapping("/{id}/product")
        public Order searchOrderById(@PathVariable("id") Integer id) {
            return orderService.searchOrderById(id);
        }
    
    }

    服务消费者启动类开启 @EnableFeignClients 注解。

    package com.example;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    @SpringBootApplication
    // 开启 FeignClients 注解
    @EnableFeignClients
    public class OrderServiceFeignApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(OrderServiceFeignApplication.class, args);
        }
    
    }
    http://localhost:9091/order/5/product

    正常显示页面

     添加2秒睡眠时间,再次请求

     我们已经可以通过 Feign 实现服务降级处理,

    但是服务不可用时如果我们想要捕获异常信息该如何实现?接下来一起学习一下

    通过 fallbackFactory 属性声明服务熔断降级处理类。

    package com.example.service;
    
    import com.example.fallback.ProductServiceFallbackFactory;
    import com.example.pojo.Product;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestParam;
    
    import java.util.List;
    
    // 声明需要调用的服务和服务熔断处理类
    @FeignClient(value = "product-service", fallbackFactory = ProductServiceFallbackFactory.class)
    public interface ProductService {
    
        /**
         * 查询商品列表
         *
         * @return
         */
        @GetMapping("/product/list")
        List<Product> selectProductList();
    
        /**
         * 根据多个主键查询商品
         *
         * @param ids
         * @return
         */
        @GetMapping("/product/listByIds")
        List<Product> selectProductListByIds(@RequestParam("id") List<Integer> ids);
    
        /**
         * 根据主键查询商品
         *
         * @param id
         * @return
         */
        @GetMapping("/product/{id}")
        Product selectProductById(@PathVariable("id") Integer id);
    
    }

    实现 FallbackFactory 接口。

    package com.example.fallback;
    
    import com.example.pojo.Product;
    import com.example.service.ProductService;
    import feign.hystrix.FallbackFactory;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    
    /**
     * 服务熔断降级处理可以捕获异常
     */
    @Component
    public class ProductServiceFallbackFactory implements FallbackFactory<ProductService> {
    
        // 获取日志,在需要捕获异常的方法中进行处理
        Logger logger = LoggerFactory.getLogger(ProductServiceFallbackFactory.class);
    
        @Override
        public ProductService create(Throwable throwable) {
            return new ProductService() {
                // 查询商品列表接口的托底数据
                @Override
                public List<Product> selectProductList() {
                    logger.error("product-service 服务的 selectProductList 方法出现异常,异常信息如下:"
                            + throwable);
                    return Arrays.asList(
                            new Product(1, "托底数据-华为手机", 1, 5800D),
                            new Product(2, "托底数据-联想笔记本", 1, 6888D),
                            new Product(3, "托底数据-小米平板", 5, 2020D)
                    );
                }
    
                // 根据多个主键查询商品接口的托底数据
                @Override
                public List<Product> selectProductListByIds(List<Integer> ids) {
                    logger.error("product-service 服务的 selectProductListByIds 方法出现异常,异常信息如下:"
                            + throwable);
                    List<Product> products = new ArrayList<>();
                    ids.forEach(id -> products.add(new Product(id, "托底数据-电视机" + id, 1, 5800D)));
                    return products;
                }
    
                // 根据主键查询商品接口的托底数据
                @Override
                public Product selectProductById(Integer id) {
                    logger.error("product-service 服务的 selectProductById 方法出现异常,异常信息如下:"
                            + throwable);
                    return new Product(id, "托底数据", 1, 2666D);
                }
            };
        }
    
    }

    访问:http://localhost:9091/order/1/product/list 结果如下:

     控制台打印结果:

     hystrix相关配置:

    hystrix.command.default和hystrix.threadpool.default中的default为默认CommandKey

    Command Properties
    Execution相关的属性的配置:
    hystrix.command.default.execution.isolation.strategy 隔离策略,默认是Thread, 可选Thread|Semaphore

    hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 命令执行超时时间,默认1000ms

    hystrix.command.default.execution.timeout.enabled 执行是否启用超时,默认启用true
    hystrix.command.default.execution.isolation.thread.interruptOnTimeout 发生超时是是否中断,默认true
    hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests 最大并发请求数,默认10,该参数当使用ExecutionIsolationStrategy.SEMAPHORE策略时才有效。如果达到最大并发请求数,请求会被拒绝。理论上选择semaphore size的原则和选择thread size一致,但选用semaphore时每次执行的单元要比较小且执行速度快(ms级别),否则的话应该用thread。
    semaphore应该占整个容器(tomcat)的线程池的一小部分。
    Fallback相关的属性
    这些参数可以应用于Hystrix的THREAD和SEMAPHORE策略

    hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests 如果并发数达到该设置值,请求会被拒绝和抛出异常并且fallback不会被调用。默认10
    hystrix.command.default.fallback.enabled 当执行失败或者请求被拒绝,是否会尝试调用hystrixCommand.getFallback() 。默认true
    Circuit Breaker相关的属性
    hystrix.command.default.circuitBreaker.enabled 用来跟踪circuit的健康性,如果未达标则让request短路。默认true
    hystrix.command.default.circuitBreaker.requestVolumeThreshold 一个rolling window内最小的请求数。如果设为20,那么当一个rolling window的时间内(比如说1个rolling window是10秒)收到19个请求,即使19个请求都失败,也不会触发circuit break。默认20
    hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds 触发短路的时间值,当该值设为5000时,则当触发circuit break后的5000毫秒内都会拒绝request,也就是5000毫秒后才会关闭circuit。默认5000
    hystrix.command.default.circuitBreaker.errorThresholdPercentage错误比率阀值,如果错误率>=该值,circuit会被打开,并短路所有请求触发fallback。默认50
    hystrix.command.default.circuitBreaker.forceOpen 强制打开熔断器,如果打开这个开关,那么拒绝所有request,默认false
    hystrix.command.default.circuitBreaker.forceClosed 强制关闭熔断器 如果这个开关打开,circuit将一直关闭且忽略circuitBreaker.errorThresholdPercentage
    Metrics相关参数
    hystrix.command.default.metrics.rollingStats.timeInMilliseconds 设置统计的时间窗口值的,毫秒值,circuit break 的打开会根据1个rolling window的统计来计算。若rolling window被设为10000毫秒,则rolling window会被分成n个buckets,每个bucket包含success,failure,timeout,rejection的次数的统计信息。默认10000
    hystrix.command.default.metrics.rollingStats.numBuckets 设置一个rolling window被划分的数量,若numBuckets=10,rolling window=10000,那么一个bucket的时间即1秒。必须符合rolling window % numberBuckets == 0。默认10
    hystrix.command.default.metrics.rollingPercentile.enabled 执行时是否enable指标的计算和跟踪,默认true
    hystrix.command.default.metrics.rollingPercentile.timeInMilliseconds 设置rolling percentile window的时间,默认60000
    hystrix.command.default.metrics.rollingPercentile.numBuckets 设置rolling percentile window的numberBuckets。逻辑同上。默认6
    hystrix.command.default.metrics.rollingPercentile.bucketSize 如果bucket size=100,window=10s,若这10s里有500次执行,只有最后100次执行会被统计到bucket里去。增加该值会增加内存开销以及排序的开销。默认100
    hystrix.command.default.metrics.healthSnapshot.intervalInMilliseconds 记录health 快照(用来统计成功和错误绿)的间隔,默认500ms
    Request Context 相关参数
    hystrix.command.default.requestCache.enabled 默认true,需要重载getCacheKey(),返回null时不缓存
    hystrix.command.default.requestLog.enabled 记录日志到HystrixRequestLog,默认true

    Collapser Properties 相关参数
    hystrix.collapser.default.maxRequestsInBatch 单次批处理的最大请求数,达到该数量触发批处理,默认Integer.MAX_VALUE
    hystrix.collapser.default.timerDelayInMilliseconds 触发批处理的延迟,也可以为创建批处理的时间+该值,默认10
    hystrix.collapser.default.requestCache.enabled 是否对HystrixCollapser.execute() and HystrixCollapser.queue()的cache,默认true

    ThreadPool 相关参数
    线程数默认值10适用于大部分情况(有时可以设置得更小),如果需要设置得更大,那有个基本得公式可以follow:
    requests per second at peak when healthy × 99th percentile latency in seconds + some breathing room
    每秒最大支撑的请求数 (99%平均响应时间 + 缓存值)
    比如:每秒能处理1000个请求,99%的请求响应时间是60ms,那么公式是:
    (0.060+0.012)

    基本得原则时保持线程池尽可能小,他主要是为了释放压力,防止资源被阻塞。
    当一切都是正常的时候,线程池一般仅会有1到2个线程激活来提供服务

    hystrix.threadpool.default.coreSize 并发执行的最大线程数,默认10
    hystrix.threadpool.default.maxQueueSize BlockingQueue的最大队列数,当设为-1,会使用SynchronousQueue,值为正时使用LinkedBlcokingQueue。该设置只会在初始化时有效,之后不能修改threadpool的queue size,除非reinitialising thread executor。默认-1。
    hystrix.threadpool.default.queueSizeRejectionThreshold 即使maxQueueSize没有达到,达到queueSizeRejectionThreshold该值后,请求也会被拒绝。因为maxQueueSize不能被动态修改,这个参数将允许我们动态设置该值。if maxQueueSize == -1,该字段将不起作用
    hystrix.threadpool.default.keepAliveTimeMinutes 如果corePoolSize和maxPoolSize设成一样(默认实现)该设置无效。如果通过plugin(https://github.com/Netflix/Hystrix/wiki/Plugins)使用自定义实现,该设置才有用,默认1.
    hystrix.threadpool.default.metrics.rollingStats.timeInMilliseconds 线程池统计指标的时间,默认10000
    hystrix.threadpool.default.metrics.rollingStats.numBuckets 将rolling window划分为n个buckets,默认10

    至此 Hystrix 服务容错知识点就讲解结束了。

  • 相关阅读:
    Life -1b The secrets of long life? Teacher: GABRIELE
    Life -1a How well do you sleep? Teacher: GABRIELE
    Kubernetes(K8s) 学习笔记 updating...
    English trip EM3-PE1B Teacher:Taylor
    V3 -A Hard Bargain Teacher: GABRIELE
    802.11k/v/r WiFi无缝漫游
    V3 -Who Am I? Teacher: GABRIELE
    Dell OMSA(updating...)
    (转)红蓝对抗之Windows内网渗透
    WiFi6(整理网上资料进行汇总查看,相关内容摘取与互联网)
  • 原文地址:https://www.cnblogs.com/lusaisai/p/13339746.html
Copyright © 2011-2022 走看看