当前位置: 首页 > news >正文

OpenFein统一重试和统一降级,且原生Fein重试失效

openFein重试分为多种,讲俩种:

1、Fein原生(Retryer)重试,需要关闭熔断,如果你自身用的Fein原生失效,配置这个就好了,为什么关这个就好了,请继续往下看

feign.circuitbreaker.enabled=false

2、结合Resilience4j进行重试,随着 Hystrix 停更,Spring Cloud 官方目前力推Resilience4j作为熔断、限流、重试的标准方案。

好了方案讲完,我来说实现吧,第一种比较简单,我不太喜欢用原生的重试代码,这样我不好调试,也不好定制日志输出。

fein重试也不是什么错误都重试的,他只会捕获到feign.RetryableException时,才会去重试,一般网络异常,超时连接这种fein会自动包装成RetryableException,向业务500/400这些是不会重试的

那么问题来了,业务都抛500了,你为什么还不重试,那我要你有啥用,所以这就是我不喜欢原生重试的一点,想解决,没问题,就要谈谈fein的ErrorDecoder接口了,这个接口是干嘛的

ErrorDecoder:相当于安全员,分析返回的状态码,可以定制对应状态码报错

方案一:

第一步先注册重试器

/** * Feign配置类(用于 @FeignClient 的 configuration 属性) * * 注意:此类不能有 @Configuration 注解! * 否则会被 Spring 全局扫描,导致配置冲突 * * 使用方式: * @FeignClient(url = "...", name = "xxx", configuration = FeignClientConfig.class) */ @Configuration public class FeignClientConfig { private static final org.slf4j.Logger log = LoggerFactory.getLogger(FeignClientConfig.class); @Value("${feign.errordecpder.initTime}") private long initTime; @Value("${feign.errordecpder.maxTime}") private long maxTime; @Value("${feign.errordecpder.maxAttempts}") private int maxAttempts; /** * 重试策略 * * 配置说明: * - period: 初始重试间隔 100ms * - maxPeriod: 最大重试间隔 1000ms(指数退避) * - maxAttempts: 最大尝试次数 4次(1次原始调用 + 3次重试) */ @Bean public Retryer feignRetryer() { log.info("【Feign配置】初始化重试策略: 初始间隔={}ms, 最大间隔={}ms, 最大重试次数={}", initTime, maxTime, maxAttempts); return new Retryer.Default(initTime, maxTime, maxAttempts); } }

第二步实现ErrorDecoder:

/** * Feign 全局错误解码器 * * 将 5xx 服务端错误转换为 RetryableException,触发重试机制 * * 使用方式: * 在 bootstrap.yml 中配置: * feign: * client: * config: * default: * errorDecoder: 自定义的ErrorDecoder 地址 */ public class FeignGlobalErrorDecoder implements ErrorDecoder { private static final Logger log = LoggerFactory.getLogger(FeignGlobalErrorDecoder.class); @Override public Exception decode(String methodKey, Response response) { FeignException exception = FeignException.errorStatus(methodKey, response); int status = response.status(); // 5xx 服务端错误 - 触发重试 if (status >= 500 && status < 600) { log.warn("【Feign重试】服务端返回{}错误,方法:{},触发重试", status, methodKey); return new RetryableException( status, "服务端" + status + "错误,触发重试", response.request().httpMethod(), exception, new Date(), response.request() ); } // 4xx 客户端错误 - 不重试,直接返回 if (status >= 400 && status < 500) { log.warn("【Feign错误】客户端错误{},方法:{},不重试", status, methodKey); return exception; } // 其他错误 return exception; } }

yml配置我也给一下吧:

# Feign配置 feign: circuitbreaker: # 禁用断路器,让 Feign 原生重试生效 enabled: false errordecpder: # 初始时间 initTime: 100 # 重试间隔 ms maxTime: 1000 # 重试次数 maxAttempts: 3 client: config: default: # 连接超时时间 connectTimeout: 5000 # 响应超时时间 readTimeout: 10000 loggerLevel: full # 全局错误解码器(将5xx转为可重试异常) errorDecoder: xxx.config.FeignGlobalErrorDecoder

方案二:

Resilience4j是yml配置,但是也不支持状态码报错,只支持配置异常类型报错

feign: circuitbreaker: enabled: true # 必须开启,否则 Resilience4j 不会介入 resilience4j: retry: configs: default: # 全局默认配置 maxAttempts: 3 # 最大重试次数(含第一次) waitDuration: 2s # 重试间隔 enableExponentialBackoff: true # 开启指数退避 exponentialBackoffMultiplier: 2 # 间隔翻倍 (2s, 4s...) retryExceptions: # 哪些异常触发重试 - java.net.ConnectException # 连接拒绝 - feign.RetryableException # Feign 包装的可重试异常 - io.github.resilience4j.retry.MaxRetriesExceededException instances: default: baseConfig: default

想要判断状态码重试,也可以把上面的errordecode实现复制过来,通用的。

有点多了,全局降级方法就不赘述了,openfein不支持全局,所以自己通过aop方式来实现,有的不走降级和重试,要看看是不是自己框架有没有全局异常捕获哦,自定义降级>全局降级哦

import feign.FeignException; import feign.RetryableException; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.openfeign.FallbackFactory; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.context.ApplicationContext; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.Map; /** * Feign全局降级切面 * * 优先级: * 1. 自定义 fallbackFactory(如果配置了)→ 手动调用 * 2. 全局降级切面(如果没有配置)→ 此切面处理 * 工作流程: * Feign原生重试 → 重试耗尽 → 抛出异常 → * AOP切面捕获 → 调用自定义fallbackFactory 或 全局降级 */ @Aspect @Component public class FeignGlobalFallbackAspect { private static final Logger log = LoggerFactory.getLogger(FeignGlobalFallbackAspect.class); private final ApplicationContext applicationContext; public FeignGlobalFallbackAspect(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } @Pointcut("@within(org.springframework.cloud.openfeign.FeignClient)") public void feignClientMethods() {} @Around("feignClientMethods()") public Object aroundFeignCall(ProceedingJoinPoint joinPoint) throws Throwable { Class<?> targetClass = joinPoint.getTarget().getClass(); FeignClient feignClient = getFeignClientAnnotation(targetClass); try { return joinPoint.proceed(); } catch (Exception e) { // 检查是否配置了自定义 fallbackFactory if (feignClient != null) { Class<?> fallbackFactoryClass = feignClient.fallbackFactory(); if (fallbackFactoryClass != void.class && fallbackFactoryClass != Void.class) { // 尝试调用自定义 fallbackFactory Object fallbackResult = invokeCustomFallbackFactory(fallbackFactoryClass, e, joinPoint); if (fallbackResult != null) { return fallbackResult; } } } // 没有配置自定义降级或调用失败,执行全局降级 log.warn("【Feign全局降级】未检测到自定义降级配置或调用失败,使用全局降级"); return handleFallback(joinPoint, e, feignClient); } } /** * 调用自定义 fallbackFactory */ @SuppressWarnings("unchecked") private Object invokeCustomFallbackFactory(Class<?> fallbackFactoryClass, Throwable cause, ProceedingJoinPoint joinPoint) { try { // 从 Spring 容器获取 fallbackFactory 实例 Object factoryBean = applicationContext.getBean(fallbackFactoryClass); if (factoryBean instanceof FallbackFactory) { FallbackFactory<Object> fallbackFactory = (FallbackFactory<Object>) factoryBean; // 调用 create 方法获取降级实例 Object fallbackInstance = fallbackFactory.create(cause); if (fallbackInstance != null) { // 获取方法名和参数 String methodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); // 通过反射调用降级方法 Method[] methods = fallbackInstance.getClass().getMethods(); for (Method method : methods) { if (method.getName().equals(methodName) && method.getParameterCount() == args.length) { log.info("【Feign自定义降级】调用自定义 fallbackFactory: {}", fallbackFactoryClass.getSimpleName()); // 设置可访问,解决 Lambda 表达式权限问题 method.setAccessible(true); return method.invoke(fallbackInstance, args); } } } } } catch (Exception ex) { log.error("【Feign自定义降级】调用 fallbackFactory 失败: {}", ex.getMessage()); } return null; } private FeignClient getFeignClientAnnotation(Class<?> targetClass) { FeignClient annotation = AnnotationUtils.findAnnotation(targetClass, FeignClient.class); if (annotation != null) { return annotation; } Class<?>[] interfaces = targetClass.getInterfaces(); for (Class<?> iface : interfaces) { annotation = AnnotationUtils.findAnnotation(iface, FeignClient.class); if (annotation != null) { return annotation; } } return null; } private Object handleFallback(ProceedingJoinPoint joinPoint, Exception e, FeignClient feignClient) { String methodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); String serviceName = "unknown"; if (feignClient != null) { serviceName = feignClient.name(); if (serviceName == null || serviceName.isEmpty()) { serviceName = feignClient.value(); } } String errorMsg = buildErrorMsg(e); log.error("【Feign全局降级】服务:{},方法:{},异常:{}", serviceName, methodName, errorMsg); try { Class<?> returnType = getReturnType(joinPoint, methodName, args.length); if (returnType != null) { return buildDefaultResult(returnType, serviceName, methodName, errorMsg); } } catch (Exception ex) { log.error("【Feign全局降级】构建兜底结果失败", ex); } throw new RuntimeException("Feign调用失败: " + errorMsg, e); } private Class<?> getReturnType(ProceedingJoinPoint joinPoint, String methodName, int argCount) { Method[] methods = joinPoint.getTarget().getClass().getMethods(); for (Method method : methods) { if (method.getName().equals(methodName) && method.getParameterCount() == argCount) { return method.getReturnType(); } } return null; } private String buildErrorMsg(Throwable cause) { if (cause instanceof FeignException.FeignServerException) { return "服务端异常[" + ((FeignException.FeignServerException) cause).status() + "]"; } else if (cause instanceof FeignException.FeignClientException) { return "客户端异常[" + ((FeignException.FeignClientException) cause).status() + "]"; } else if (cause instanceof RetryableException) { return "重试耗尽"; } else if (cause instanceof java.net.ConnectException) { return "连接失败"; } else if (cause instanceof java.net.SocketTimeoutException) { return "调用超时"; } return cause.getClass().getSimpleName() + ": " + cause.getMessage(); } private Object buildDefaultResult(Class<?> returnType, String serviceName, String methodName, String errorMsg) { if (returnType == void.class || returnType == Void.class) { return null; } if (returnType.isPrimitive()) { if (returnType == boolean.class) return false; if (returnType == int.class) return 0; if (returnType == long.class) return 0L; if (returnType == double.class) return 0.0; if (returnType == float.class) return 0.0f; if (returnType == short.class) return (short) 0; if (returnType == byte.class) return (byte) 0; if (returnType == char.class) return '\0'; } if (returnType == String.class) { return "服务[" + serviceName + "]调用失败: " + errorMsg; } if (Map.class.isAssignableFrom(returnType)) { return Map.of( "code", 503, "msg", "服务[" + serviceName + "]调用失败", "data", null, "success", false ); } log.warn("【Feign全局降级】返回类型{}未明确处理,返回null", returnType.getName()); return null; } }
http://www.jsqmd.com/news/511974/

相关文章:

  • 1%的预测精度提升,在现货市场值多少钱?基于100MW电站的年度收益敏感性分析
  • ClearerVoice-Studio与SpringBoot集成:构建智能语音微服务
  • 避坑指南:PINN在常微分方程积分中的常见问题与解决方案
  • SparkFun I2C GPS库:寄存器级控制与多星座定位开发指南
  • 【高精度气象】2026别再只问“天气准不准”:真正拉开收益差距的,是把预报接进交易、调度和运维
  • 深入理解 C#.NET TaskScheduler:为什么大量使用 Work-Stealing
  • 智能排班系统:企业人力资源管理的数字化革新
  • SiameseAOE模型内网穿透测试指南:本地开发调试GPU模型服务
  • 户籍制度捆绑资源下留守儿童问题对人口结构的长效影响
  • COMSOL多极分解:分方向多级展开通用模型在电磁场与透射率光学BIC仿真中的应用及面上箭头展示
  • RAG系统深度解析
  • Qwen3-ASR语音识别应用:会议记录、字幕生成实战案例
  • Harbor镜像仓库对接OpenLDAP统一认证实操手册
  • 告别手动排班:智能排班系统助力HR实现高效管理
  • 巧用手机原生功能,零成本给重要文档加密防护
  • 企业数据安全体系建设指南:从风险识别到技术落地的全流程(2026版)
  • Retinaface+CurricularFace镜像教程:手把手教你搭建人脸识别环境,简单易用
  • 养老设计行业黑马崛起:揭秘深圳医博传人如何用3个月霸榜搜索引擎的“危险操作“
  • 从零基础到行业专家:50 步 AI 成长路线图,构建可落地的 AI 核心能力
  • 揭秘Open-Sora的‘数据炼金术‘:我是如何用GPT-4V+LLaVA打造百万级视频字幕的
  • Pixel Dimension Fissioner部署教程:Docker镜像开箱即用+Stable v1.0.0适配
  • 三菱电机编码器软件修改全攻略:J2、J2S、J3、J4系列大揭秘
  • PageAdmin平台版技术说明:站群集约化与应用平台化技术方案
  • 为什么头部科技公司已停用单Agent方案?Dify多角色协同工作流在风控、客服、BI三大场景的压测数据全公开
  • java微信小程序的中小型企业员工电子档案借阅管理系统的设计与实现
  • EPLAN使用小技巧
  • Harmonyos应用实例141:三角形内角和动态验证
  • 基于SSM框架的智能停车场管理系统设计与实现
  • 智能排班系统的技术实现与功能特点解析
  • 3.20爬虫基础速看