Spring Boot2.x 集成 OpenFeign 实现 Hystrix 熔断降级与 Ribbon 负载均衡配置

发布时间 2023-12-31 11:38:07作者: 夏秋初

参考

踩坑与注意

  1. OpenFeign 默认超时为 1s,如果使用 Hystrix 的时候,报以下错误,可以尝试修改 OpenFeign 超时时间。
    • Read timed out executing GET
    • 配置 Hystrix HystrixTimeoutException 超时 不生效
    • Hystrix circuit short-circuited and is OPEN (因为失败过多,自动触发短路)
  2. 找不到 @HystrixCommand 相关注解,因为 spring-cloud-starter-openfeign 依赖虽然集成了 Hystrix ,但是并没有相关注解,需要引入 spring-cloud-starter-netflix-hystrix 依赖。
  3. 测试 Ribbon 超时配置并未生效(环境:消费者和注册中心在线,提供者下线;Feign 超时设置100s,Ribbon 设置2s)。
  4. 参考 jeecg-boot 服务降级是将所有远程调用接口封装到一个类,然后使用 fallbackFactory 模式进行处理。
  5. Ribbon 已被 spring-cloud-starter-openfeign 集成。
  6. Hystrix 可以有很多方式指定降级处理函数。
  7. 可以通过 Hystrix Dashboard 进行监控和调试。

环境

环境 版本 说明
windows 10
vs code 1.85.1
Spring Boot Extension Pack v0.2.1 vscode插件
Extension Pack for Java v0.25.15 vscode插件
JDK 11
Springboot 2.3.12.RELEASE
spring-cloud-dependencies Hoxton.SR12 mvn依赖
spring-cloud-starter-netflix-eureka-client 未设置 mvn依赖
spring-cloud-starter-netflix-eureka-server 未设置 mvn依赖
spring-cloud-starter-openfeign 未设置 mvn依赖
spring-cloud-starter-netflix-hystrix 未设置 mvn依赖,如果不需要 hystrix 相关注解可以不引入,可以通过 @FeignClient 注解 fallbackFactory、fallback 进行指定降级处理类。
Apache Maven 3.8.6

正文

项目分为多模块:注册中心(registry)、消费者(consumer)、提供者(provider)、common (公共模块)。

准备工作

准备工作请参考该文章实现基础项目的搭建 《Spring Boot2.x 集成 Eureka 与 OpenFeign》,本文代码可通过文章顶部 参考 中下载。

  1. application.properties 修改默认超时与开启 hystrix。
# feign开启hystrix
feign.circuitbreaker.enabled=true
# 修改超时时间
feign.client.config.default.connectTimeout=100000
feign.client.config.default.readTimeout=600000
# # 配置所有服务连接超时
# feign.client.config.default.connect-timeout=5000
# #配置所有服务等待超时
# feign.client.config.default.read-timeout=5000
# 日志
feign.client.config.default.loggerLevel=full
# gzip压缩
feign.compression.request.enabled=true
feign.compression.response.enabled=true
# hystrix
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=100000
# hystrix.command.default.metrics.rollingStats.timeInMilliseconds=5000
# hystrix.command.default.circuitBreaker.requestVolumeThreshold=4
# hystrix.command.default.circuitBreaker.errorThresholdPercentage=50
# hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds=3000
# hystrix.command.commandKeyTest.execution.isolation.thread.timeoutInMilliseconds=10000

Hystrix 熔断降级 (消费者(consumer)子模块)

由于本文没有使用到 hystrix 的相关注解,直接使用 @FeignClient 注解 fallbackFactory、fallback 进行指定降级处理类进行演示。如果需要 hystrix 的相关注解,请 pom.xml 引入 spring-cloud-starter-netflix-hystrix 依赖。

fallbackFactory 类重写 create ,返回的是当前接口的实例;fallback 实现的是当前接口;
参考 jeecg-boot 如果调用的 Feign 接口函数包含返回值,则返回 null,然后在调用处代码判断是否是 null。

  1. 入口文件

    package com.xiaqiuchu.consumer;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    @EnableFeignClients
    @EnableEurekaClient
    @SpringBootApplication
    public class ConsumerApplication {
    	public static void main(String[] args) {
    		SpringApplication.run(ConsumerApplication.class, args);
    	}
    
    }
    
  2. fallbackFactory 模式(推荐,可以获取报错信息,与 fallback 模式任选其一)。

    1. StudentService
      package com.xiaqiuchu.consumer.service;
      
      import org.springframework.cloud.openfeign.FeignClient;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RequestMethod;
      import org.springframework.web.bind.annotation.RequestParam;
      
      import com.xiaqiuchu.common.entity.Student;
      import com.xiaqiuchu.consumer.component.StudentServiceFallBackFactory;
      
      // 回退类正常模式(无法获取详细报错信息)
      // @FeignClient(value = "STUDENT-TEACHER-PROVIDER", fallback = StudentServiceFallBack.class)
      // 工厂模式(获取详细报错信息)
      @FeignClient(value = "STUDENT-TEACHER-PROVIDER", fallbackFactory = StudentServiceFallBackFactory.class)
      public interface StudentService {
      
      	@RequestMapping(method = RequestMethod.GET, value = "/student/findById")
      	public Student findById(@RequestParam(name = "id") Long id);
      
      	@RequestMapping(method = RequestMethod.GET, value = "/student/ping")
      	public String ping();
      }
      
      
    2. StudentServiceFallBackFactory
      	package com.xiaqiuchu.consumer.component;
      
      	import org.springframework.cloud.openfeign.FallbackFactory;
      	import org.springframework.stereotype.Component;
      	import com.xiaqiuchu.common.entity.Student;
      	import com.xiaqiuchu.consumer.service.StudentService;
      	import lombok.extern.slf4j.Slf4j;
      	// https://blog.csdn.net/weixin_42771651/article/details/121626431
      	// https://blog.csdn.net/qq_41125219/article/details/121346640
      	@Slf4j
      	@Component
      	public class StudentServiceFallBackFactory implements FallbackFactory<StudentService>{
      
      		@Override
      		public StudentService create(Throwable cause) {
      
      			log.error("远程调用出错,异常原因:{}", cause.getMessage(), cause);
      
      			return new StudentService(){
      
      				@Override
      				public Student findById(Long id) {
      					return new Student();
      				}
      
      				@Override
      				public String ping() {
      					return "err";
      				}
      
      			};
      		}
      
      	}
      
      
  3. fallback 模式(与 fallbackFactory 模式任选其一)。

    1. StudentService
      	package com.xiaqiuchu.consumer.service;
      
      	import org.springframework.cloud.openfeign.FeignClient;
      	import org.springframework.web.bind.annotation.RequestMapping;
      	import org.springframework.web.bind.annotation.RequestMethod;
      	import org.springframework.web.bind.annotation.RequestParam;
      
      	import com.xiaqiuchu.common.entity.Student;
      	import com.xiaqiuchu.consumer.component.StudentServiceFallBack;
      	import com.xiaqiuchu.consumer.component.StudentServiceFallBackFactory;
      
      	// 回退类正常模式(无法获取详细报错信息)
      	@FeignClient(value = "STUDENT-TEACHER-PROVIDER", fallback = StudentServiceFallBack.class)
      	// 工厂模式(获取详细报错信息)
      	// @FeignClient(value = "STUDENT-TEACHER-PROVIDER", fallbackFactory = StudentServiceFallBackFactory.class)
      	public interface StudentService {
      
      		@RequestMapping(method = RequestMethod.GET, value = "/student/findById")
      		public Student findById(@RequestParam(name = "id") Long id);
      
      		@RequestMapping(method = RequestMethod.GET, value = "/student/ping")
      		public String ping();
      	}
      
      
    2. StudentServiceFallBack
      	package com.xiaqiuchu.consumer.component;
      
      	import org.springframework.stereotype.Component;
      
      	import com.xiaqiuchu.common.entity.Student;
      	import com.xiaqiuchu.consumer.service.StudentService;
      
      	/**
      	 * 回退类实现
      	 */
      	@Component
      	public class StudentServiceFallBack implements StudentService {
      
      		// public StudentServiceFallBack(){
      
      		// }
      
      		@Override
      		public Student findById(Long id) {
      			return new Student();
      			// TODO Auto-generated method stub
      			// throw new UnsupportedOperationException("Unimplemented method 'findById'");
      		}
      
      		@Override
      		public String ping() {
      			// TODO Auto-generated method stub
      			// throw new UnsupportedOperationException("Unimplemented method 'ping'");
      			return "error";
      		}
      
      
      	}
      

Ribbon 负载均衡 (消费者(consumer)子模块)

负载均衡模式有很多种,轮询策略、随机策略、重试策略、最可用策略、可用过滤算法、自定义处理等。

  1. 新建 FeignConfiguration.java
    package com.xiaqiuchu.consumer.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import com.netflix.loadbalancer.IRule;
    import com.netflix.loadbalancer.RandomRule;
    
    /**
     * https://www.cnblogs.com/liconglong/p/15408858.html
     */
    @Configuration
    public class FeignConfiguration {
    	 /**
    	 * 配置随机的负载均衡策略
    	 * 特点:对所有的服务都生效
    	 */
    	@Bean
    	public IRule loadBalancedRule() {
    		return new RandomRule();
    	}
    }