线上503了?聊聊Feign熔断降级这点事

线上503了?聊聊Feign熔断降级这点事

事故现场

bash 复制代码
[ERROR] feign.RetryableException: adp-dws-data-service executing POST http://...
Caused by: java.net.UnknownHostException

DNS解析失败,异常直接503给用户。

问题在哪:

java 复制代码
// 就这么简单粗暴地调
@FeignClient(name = "dws-service", url = "${dws.url}")
public interface DWSFeign {
    EntityResult<List<Data>> queryActiveInfo(Object param);
}

// 没有任何异常处理
public void queryClueActive(String phone) {
    dwsFeign.queryActiveInfo(phone);  // 💥 炸了
}

熔断降级到底是啥

说白了就是止损

就像保险丝一样。电路出问题了,保险丝自动熔断,防止烧坏其他电器。

微服务也一样。下游挂了,熔断器自动切断,别让它拖累整个系统。

熔断 :下游挂了,别再调了,直接返回
降级:返回个兜底数据,缓存也行,默认值也行

举个例子:调推荐服务,连续3次超时。熔断器开启,后续请求直接返回空列表。别让用户傻等。


技术方案选啥

市面上主流的有4种方案。别急着选,先看看各自的优缺点。

方案1:Hystrix

Netflix开源,24k+ star。Spring Cloud官方集成的就是这个。

java 复制代码
@FeignClient(name = "dws-service", fallback = DWSFallback.class)
public interface DWSFeign {
    EntityResult<List<Data>> queryActiveInfo(Object param);
}

@Component
public class DWSFallback implements DWSFeign {
    @Override
    public EntityResult<List<Data>> queryActiveInfo(Object param) {
        log.error("DWS服务降级");
        return EntityResult.empty();
    }
}

优点

  • Spring Cloud原生支持,配置简单
  • 线程池隔离,服务雪崩时有效
  • 社区资料多,遇到问题好查

缺点

  • 2018年就停止维护了,官方推荐用Resilience4j
  • 依赖重,引入一堆jar包
  • 线程池模式性能损耗大

适用场景:老项目已经在用的,别轻易换。新项目别选这个了。


方案2:Sentinel

阿里开源,22k+ star。淘宝、天猫都在用。

java 复制代码
@FeignClient(name = "dws-service")
public interface DWSFeign {
    @SentinelResource(value = "queryActiveInfo", 
                      fallback = "queryFallback",
                      blockHandler = "queryBlockHandler")
    EntityResult<List<Data>> queryActiveInfo(Object param);
}

public EntityResult<List<Data>> queryFallback(Object param, Throwable e) {
    log.error("DWS服务异常降级: {}", e.getMessage());
    return EntityResult.empty();
}

public EntityResult<List<Data>> queryBlockHandler(Object param, BlockException e) {
    log.warn("DWS服务被限流: {}", e.getMessage());
    return EntityResult.empty();
}

优点

  • 功能最强大:熔断、限流、热点参数、系统负载保护都有
  • 可视化控制台,实时修改规则,不用重启
  • 国内大厂在用,文档友好

缺点

  • 学习曲线陡,概念多(资源、规则、槽链...)
  • 控制台要单独部署
  • 配置复杂,新手容易懵

适用场景:大型项目,需要精细化流量控制的。比如电商大促、秒杀活动。


方案3:Resilience4j

轻量级,9k+ star。Spring官方现在推荐用这个。

java 复制代码
@FeignClient(name = "dws-service")
public interface DWSFeign {
    @CircuitBreaker(name = "dws", fallbackMethod = "queryFallback")
    EntityResult<List<Data>> queryActiveInfo(Object param);
}

public EntityResult<List<Data>> queryFallback(Object param, Exception e) {
    log.error("DWS服务降级: {}", e.getMessage());
    return EntityResult.empty();
}

配置文件:

yaml 复制代码
resilience4j:
  circuitbreaker:
    instances:
      dws:
        failureRateThreshold: 50        # 失败率50%触发熔断
        slowCallRateThreshold: 100      # 慢调用率100%触发
        slowCallDurationThreshold: 2s   # 超过2s算慢调用
        waitDurationInOpenState: 10s    # 熔断持续10秒
        slidingWindowSize: 10           # 滑动窗口10次调用

优点

  • 轻量级,只引入需要的模块
  • 函数式编程风格,代码优雅
  • Spring Boot官方推荐
  • 文档详细,社区活跃

缺点

  • 没有可视化控制台(有第三方的)
  • 配置都在yaml,动态调整要重启
  • 国内案例少,踩坑要自己摸索

适用场景:新项目首选。追求轻量、代码优雅的团队。


方案4:手动try-catch

最原始的方式。

java 复制代码
private Map<String, ClueActiveInfoRsp> queryDwsWithFallback(List<String> phones) {
    try {
        return dwsFeign.queryActiveInfo(buildRequest(phones))
                .getRows()
                .stream()
                .collect(Collectors.toMap(
                    ClueActiveInfoRsp::getLeadsId,
                    Function.identity(),
                    (v1, v2) -> v2
                ));
    } catch (Exception e) {
        log.error("DWS服务挂了,返回空Map,异常: {}", e.getMessage());
        return new HashMap<>();
    }
}

优点

  • 零依赖,不用引入任何框架
  • 逻辑清晰,新人一看就懂
  • 想怎么降级就怎么降级,自由度高
  • 出问题容易排查

缺点

  • 没有熔断功能,服务挂了还在一直调
  • 没有统计指标,降级了多少次不知道
  • 每个接口都要写,代码重复
  • 超时、重试要自己控制

适用场景

  • 项目小,调用方少
  • 临时应急方案
  • 框架依赖缺失时救急

怎么选?

场景 推荐方案 理由
新项目从零开始 Resilience4j 轻量、官方推荐
项目已有Sentinel Sentinel 可视化控制台,动态调整规则
老项目用Hystrix 别动 能跑就别改,风险大
线上故障应急 try-catch 简单直接,快速上线
高并发、需限流 Sentinel 热点参数、系统负载保护
追求轻量高性能 Resilience4j 信号量隔离,开销小

降级策略怎么设计

策略1:返回空值

java 复制代码
@Component
public class DWSFeignFallback implements DWSFeign {
    @Override
    public EntityResult<List<Data>> queryActiveInfo(Object param) {
        log.error("DWS挂了,返回空数据");
        EntityResult<List<Data>> result = new EntityResult<>();
        result.setRows(Collections.emptyList());
        return result;
    }
}

适用:推荐列表、活跃度信息等非核心功能。

策略2:返回缓存

java 复制代码
@Component  
public class UserServiceFallback implements UserService {
    @Autowired
    private RedisTemplate<String, User> redis;
    
    @Override
    public User getUserInfo(Long userId) {
        log.warn("用户服务挂了,读缓存");
        
        User cached = redis.opsForValue().get("user:" + userId);
        if (cached != null) return cached;
        
        return User.builder().id(userId).name("用户信息加载中...").build();
    }
}

适用:用户信息、商品详情等变化不频繁的数据。

策略3:写操作降级

java 复制代码
@Component
public class OrderServiceFallback implements OrderService {
    @Autowired  
    private OrderMQProducer mq;
    
    @Override
    public OrderResult createOrder(OrderDTO order) {
        log.error("订单服务挂了,扔MQ里异步处理");
        mq.send(order);
        
        return OrderResult.builder()
                .success(true)
                .orderId("PENDING_" + UUID.randomUUID())
                .message("订单处理中,请稍后查询")
                .build();
    }
}

适用:下单、支付等写操作,高峰期流量削峰。


配置注意事项

Hystrix超时配置

yaml 复制代码
# ❌ 错误配置
feign:
  client:
    config:
      default:
        readTimeout: 30000   # 30秒

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 20000  # 20秒

Hystrix 20秒就超时了,Feign的30秒根本没机会生效。

正确配置:

yaml 复制代码
# ✅ 正确配置  
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 35000  # 比Feign多5秒

记住这个公式:Hystrix超时 > Feign超时


写在最后

几个原则记一下:

  • 弱依赖设计 - 非核心功能要能降级
  • 快速失败 - 1秒内返回,别让用户等
  • 监控先行 - 降级了要能知道
  • 有损服务 - 保核心功能,非核心该降就降

参考资料

相关推荐
初级程序员Kyle6 小时前
开始改变第六天 MySQL(1)
后端
MeowRain6 小时前
JVM分代回收
后端
程序员蜗牛6 小时前
拒绝重复造轮子!SpringBoot 内置的20个高效官方工具类详解
后端
白衣鸽子6 小时前
ListUtils:Java列表操作的瑞士军刀
后端·开源·设计
bcbnb6 小时前
Charles vs Fiddler vs Wireshark,哪款抓包工具最适合开发者?
后端
L.EscaRC6 小时前
【Rust编程】深入解析 Rust gRPC 框架:Tonic
后端·rpc·rust
长存祈月心6 小时前
安装与切换Rust版本
开发语言·后端·rust
流星白龙6 小时前
双端迭代器:从 `next_back()` 到零拷贝“滑动窗口”——Rust DoubleEndedIterator 全景指南
开发语言·后端·rust
JaguarJack7 小时前
PHP 中的命名艺术 实用指南
后端·php