线上503了?聊聊Feign熔断降级这点事
事故现场
            
            
              bash
              
              
            
          
          [ERROR] feign.RetryableException: adp-dws-data-service executing POST http://...
Caused by: java.net.UnknownHostExceptionDNS解析失败,异常直接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秒内返回,别让用户等
- 监控先行 - 降级了要能知道
- 有损服务 - 保核心功能,非核心该降就降
参考资料