【SpringCloud】Sentinel 组件:blockHandler vs fallback


在Sentinel的异常处理机制中,blockHandler与fallback是两个高频使用,但极易混淆的核心配置,二者虽均用于异常兜底、避免服务崩溃或返回不友好响应,但在触发场景、处理范围、语法规范、使用优先级上截然不同,若使用不当会导致兜底失效、异常无法正常拦截等问题。

复制代码
======= 🌟 青柠来相伴,代码更简单。🌟 =======
📚 本文所有内容,我都整理在了 青柠合集 里。👇
🎯 搜索关注【青柠代码录】,即可查看所有合集文章 ~
======= 🌟 ================ 🌟 =======

一、Sentinel异常处理核心场景与执行流程

在讲解二者区别前,先明确Sentinel的两类核心异常场景及整体执行流程,这是理解blockHandler与fallback的基础:

流量控制/熔断降级异常(平台级拦截)

由Sentinel的五大核心规则(限流、熔断、热点、系统保护、授权规则)触发,抛出BlockException及其子类,具体包括: 此类异常属于"平台级拦截",触发时机早于业务逻辑执行,即Sentinel先拦截请求,再判断是否执行业务代码,因此业务逻辑未执行或未执行完毕。

  1. FlowException:限流异常(请求QPS/线程数超出阈值);
  2. DegradeException:熔断异常(服务调用失败率、响应时间超出阈值,触发熔断);
  3. ParamFlowException:热点参数限流异常(指定热点参数的请求超出阈值);
  4. SystemException:系统保护异常(系统负载、CPU、内存超出阈值,触发系统保护);
  5. AuthorityException:授权异常(请求来源未通过授权校验)。

业务逻辑异常(业务级错误)

业务代码执行过程中抛出的各类异常,包括但不限于: 此类异常属于"业务级错误",此时Sentinel规则未触发,业务逻辑已执行但出现异常,需通过兜底逻辑避免异常扩散。

  1. 基础异常: NullPointerException(空指针)、 IllegalArgumentException(参数非法);
  2. 业务异常:自定义业务异常(如 OrderNotFoundException订单不存在、 StockInsufficientException库存不足);
  3. 外部依赖异常: SQLException(数据库异常)、 FeignException(远程调用异常)、 TimeoutException(超时异常)。

Sentinel异常处理执行流程

请求进入Sentinel保护的资源 → Sentinel执行规则校验 → 若触发规则(限流/熔断等)→ 抛出BlockException → 优先执行blockHandler(若配置)→ 若未配置blockHandler → 执行fallback(若配置)→ 若均未配置 → 抛出原始BlockException

若未触发Sentinel规则 → 执行业务逻辑 → 业务逻辑抛出异常 → 执行fallback(若配置)→ 若未配置fallback → 抛出原始业务异常;

blockHandler与fallback的核心区别,本质就是对这两类异常的处理分工不同------前者专注处理平台级拦截异常,后者专注处理业务级错误,二者协同可构建完善的微服务容错体系,覆盖从请求拦截到业务执行的全链路异常兜底。

二、blockHandler 详解

2.1 核心定义

blockHandler是Sentinel提供的「平台级异常处理机制」,专门用于处理因Sentinel规则触发的BlockException及其子类异常,核心作用是当请求被限流、熔断、热点拦截、系统保护或授权拦截时,提供统一的兜底响应(如友好提示、默认数据),避免直接抛出原始异常给前端。

核心特点:仅响应Sentinel规则触发的异常,不处理业务逻辑本身的异常;触发时机早于业务逻辑执行(或业务逻辑未执行);优先级高于fallback;兜底方法需严格遵循语法规范,否则无法生效。

2.2 语法规范

blockHandler的配置依赖@SentinelResource注解(Sentinel核心注解,用于定义受保护的资源),需严格遵循以下规范:

  1. 注解配置 :在需要保护的资源(接口方法、业务方法)上添加 @SentinelResource注解,通过 blockHandler属性指定兜底方法名(必须与兜底方法名完全一致,区分大小写);若兜底方法在其他类中,需配合 blockHandlerClass属性指定类对象(如 blockHandlerClass = SentinelBlockHandler.class)。
  2. 方法访问权限 :兜底方法必须是 public修饰(Sentinel底层通过反射调用,private/protected会导致反射失败);若配合 blockHandlerClass使用,兜底方法必须额外添加 static修饰(否则无法被Sentinel解析和调用)。
  3. 参数列表 :兜底方法的参数列表必须与原资源方法完全一致,且在最后额外添加一个 BlockException类型的参数,用于接收触发的具体异常(可通过该参数判断是限流、熔断还是其他平台级异常,从而返回更精准的提示)。
  4. 返回值 :兜底方法的返回值,必须与原资源方法的返回值完全一致(包括泛型类型),确保前后端响应格式统一,避免前端解析异常(如原方法返回 Result<OrderDTO>,兜底方法也必须返回 Result<OrderDTO>)。
  5. 方法位置 :默认情况下,兜底方法需与原资源方法在同一个类中;若指定 blockHandlerClass,则兜底方法需在该类中,且为静态方法;兜底方法名不可与原资源方法名重复。
  6. 异常捕获范围 :仅能捕获 BlockException及其子类,无法捕获业务逻辑异常(如NullPointerException),即使在兜底方法中捕获了业务异常,也无法生效。

2.3 实战案例

场景1:订单提交接口(高并发场景),配置Sentinel限流规则(QPS上限100)、熔断规则(失败率上限50%,熔断时长10秒),当请求超出限流阈值或触发熔断时,通过blockHandler返回统一的兜底提示,同时区分不同类型的BlockException,返回更精准的响应。

第一步:引入Sentinel依赖(SpringBoot+微服务项目)

复制代码
<!-- Sentinel 核心依赖(核心功能,必引) -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.8.6</version> <!-- 常用稳定版,兼容SpringBoot 2.6.x+ -->
</dependency>
<!-- Sentinel 注解支持(必需,用于@SentinelResource注解解析) -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-annotation-aspectj</artifactId>
    <version>1.8.6</version>
</dependency>
<!-- SpringBoot 整合 Sentinel(自动配置,简化集成) -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    <version>2021.0.5.0</version> <!-- 兼容Spring Cloud Alibaba 2021版本 -->
</dependency>
<!-- Sentinel 控制台依赖(可选,用于可视化配置规则、监控流量,开发常用) -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-transport-simple-http</artifactId>
    <version>1.8.6</version>
</dependency>

第二步:配置Sentinel控制台(可选,可视化管理规则)

复制代码
# application.yml 配置
spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080 # 控制台地址(需先启动Sentinel控制台)
        port: 8719 # 客户端与控制台通信端口,默认8719,若冲突可修改
      web-context-unify: false # 关闭web上下文统一,避免资源标识重复

第三步:编写资源方法与blockHandler兜底方法(同包同类,基础用法)

复制代码
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.system.SystemException;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * 订单接口 - Sentinel保护示例(blockHandler实战,基础用法)
 */
@RestController
public class OrderController {

    /**
     * 订单提交接口(高并发核心接口)
     * @SentinelResource:定义Sentinel资源,value为资源唯一标识(必需,不可重复)
     * blockHandler:指定限流/熔断等平台级异常的兜底方法名
     */
    @PostMapping("/order/submit")
    @SentinelResource(value = "orderSubmitResource", blockHandler = "orderSubmitBlockHandler")
    public Result<String> submitOrder(@RequestParam String orderId, @RequestParam String userId) {
        // 模拟业务逻辑:订单校验、库存扣减、支付回调、日志记录
        System.out.println("订单[" + orderId + "]提交中,用户ID:" + userId);
        // 模拟业务异常(用于测试:blockHandler不会处理该异常)
        // if ("TEST_ERROR".equals(orderId)) {
        //     throw new NullPointerException("订单ID为空异常");
        // }
        return Result.success("订单提交成功", orderId);
    }

    /**
     * blockHandler兜底方法(处理Sentinel规则触发的BlockException)
     * 规范:public、参数与原方法一致+BlockException、返回值与原方法一致
     * @param orderId 原方法参数1
     * @param userId 原方法参数2
     * @param e BlockException异常(用于区分异常类型,精准返回提示)
     * @return 统一响应结果(符合前后端分离格式)
     */
    public Result<String> orderSubmitBlockHandler(String orderId, String userId, BlockException e) {
        // 区分不同类型的BlockException,返回更精准的提示(开发常用场景)
        String message = "系统繁忙,请稍后再试(平台保护)";
        if (e instanceof FlowException) {
            message = "订单提交过于频繁,请10秒后重试(限流保护,QPS已达上限)";
        } else if (e instanceof DegradeException) {
            message = "订单服务暂时降级,请10秒后重试(熔断保护,服务调用失败率过高)";
        } else if (e instanceof ParamFlowException) {
            message = "热点参数请求过于频繁,请稍后再试(热点限流保护)";
        } else if (e instanceof SystemException) {
            message = "系统负载过高,请稍后再试(系统保护)";
        } else if (e instanceof AuthorityException) {
            message = "请求来源未授权,无法提交订单(授权保护)";
        }
        // 开发优化:打印异常日志,便于排查问题(不影响前端响应)
        System.err.println("订单提交触发平台级保护,订单ID:" + orderId + ",异常类型:" + e.getClass().getSimpleName());
        // 返回统一响应格式,状态码503(服务不可用,符合平台级异常规范)
        return Result.fail(503, message);
    }
}

// 统一响应结果类(开发通用,可直接复用)
class Result<T> {
    private int code;
    private String message;
    private T data;

    // 成功响应(带数据)
    public static <T> Result<T> success(String message, T data) {
        Result<T> result = new Result<>();
        result.setCode(200);
        result.setMessage(message);
        result.setData(data);
        return result;
    }

    // 成功响应(不带数据)
    public static <T> Result<T> success(String message) {
        return success(message, null);
    }

    // 失败响应(带状态码和提示)
    public static <T> Result<T> fail(int code, String message) {
        Result<T> result = new Result<>();
        result.setCode(code);
        result.setMessage(message);
        return result;
    }

    // getter/setter 省略(开发中需完善)
    public int getCode() { return code; }
    public void setCode(int code) { this.code = code; }
    public String getMessage() { return message; }
    public void setMessage(String message) { this.message = message; }
    public T getData() { return data; }
    public void setData(T data) { this.data = data; }
}

第四步:配置Sentinel限流+熔断规则(代码方式,常用,支持动态配置,无需依赖控制台)

复制代码
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;

/**
 * Sentinel 规则配置类(实战:代码配置,支持动态更新,无需控制台)
 */
@Configuration
public class SentinelRuleConfig {

    /**
     * 初始化限流规则+熔断规则(@PostConstruct:容器启动后自动执行)
     */
    @PostConstruct
    public void initRules() {
        // 1. 初始化限流规则
        initFlowRules();
        // 2. 初始化熔断规则
        initDegradeRules();
    }

    /**
     * 初始化限流规则:订单提交接口QPS上限100
     */
    private void initFlowRules() {
        List<FlowRule> rules = new ArrayList<>();
        // 创建限流规则对象
        FlowRule flowRule = new FlowRule();
        // 指定需要限流的资源(与@SentinelResource的value完全一致)
        flowRule.setResource("orderSubmitResource");
        // 限流类型:QPS(每秒请求数),可选:0=线程数限流,1=QPS限流
        flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        // 限流阈值:100 QPS(根据实际业务调整)
        flowRule.setCount(100);
        // 限流策略:默认直接拒绝(可选:WARM_UP(预热)、RATE_LIMITER(匀速排队))
        flowRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
        // 添加规则到规则管理器
        rules.add(flowRule);
        FlowRuleManager.loadRules(rules);
    }

    /**
     * 初始化熔断规则:订单提交接口失败率上限50%,熔断时长10秒
     */
    private void initDegradeRules() {
        List<DegradeRule> rules = new ArrayList<>();
        // 创建熔断规则对象
        DegradeRule degradeRule = new DegradeRule();
        // 指定需要熔断的资源(与@SentinelResource的value完全一致)
        degradeRule.setResource("orderSubmitResource");
        // 熔断策略:失败率(可选:失败率、响应时间)
        degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
        // 失败率阈值:50%(请求失败率超过50%触发熔断)
        degradeRule.setCount(0.5);
        // 熔断时长:10秒(熔断后,10秒内不再调用该资源,之后尝试恢复)
        degradeRule.setTimeWindow(10);
        // 最小请求数:10(只有当请求数达到10时,才会计算失败率,避免少量请求误触发熔断)
        degradeRule.setMinRequestAmount(10);
        // 统计时长:1000毫秒(统计最近1秒的请求失败率)
        degradeRule.setStatIntervalMs(1000);
        // 添加规则到规则管理器
        rules.add(degradeRule);
        DegradeRuleManager.loadRules(rules);
    }
}

测试效果(3种核心场景):

  1. 限流触发:当请求QPS超过100时,请求被Sentinel拦截,触发 orderSubmitBlockHandler方法,返回"订单提交过于频繁,请10秒后重试(限流保护,QPS已达上限)",避免直接抛出 FlowException异常;
  2. 熔断触发:当订单提交接口失败率超过50%(且请求数≥10),触发熔断,请求被拦截,返回"订单服务暂时降级,请10秒后重试(熔断保护,服务调用失败率过高)";
  3. 业务异常测试:若放开代码中"模拟业务异常"的注释,当orderId为"TEST_ERROR"时,抛出NullPointerException,此时blockHandler不生效(因不处理业务异常),若未配置fallback,会直接抛出该异常。

2.4 进阶用法:blockHandlerClass(解耦兜底逻辑,推荐)

当多个资源(接口)需要共用相同的blockHandler逻辑时(如多个接口的限流、熔断兜底提示一致),可将兜底方法抽取到独立的处理类中,通过blockHandlerClass指定,实现业务代码与兜底逻辑的解耦,提升代码可维护性(开发推荐做法,避免代码冗余)。

复制代码
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.system.SystemException;

/**
 * Sentinel 统一BlockException处理类(抽取通用兜底逻辑,供多个资源复用)
 */
public class SentinelBlockHandler {

    /**
     * 通用限流/熔断兜底方法(必须是static,否则无法被Sentinel解析)
     * 适用于所有返回Result<String>类型的资源方法(可根据实际返回值重载)
     * @param orderId 原方法参数1(与原资源方法参数一致)
     * @param userId 原方法参数2(与原资源方法参数一致)
     * @param e BlockException异常(用于区分异常类型)
     * @return 统一响应结果
     */
    public static Result<String> commonBlockHandler(String orderId, String userId, BlockException e) {
        String message = "系统繁忙,请稍后再试(平台保护)";
        if (e instanceof FlowException) {
            message = "请求过于频繁,请10秒后重试(限流保护)";
        } else if (e instanceof DegradeException) {
            message = "服务暂时降级,请10秒后重试(熔断保护)";
        } else if (e instanceof ParamFlowException) {
            message = "热点参数请求过于频繁,请稍后再试";
        } else if (e instanceof SystemException) {
            message = "系统负载过高,请稍后再试";
        } else if (e instanceof AuthorityException) {
            message = "请求来源未授权,无法操作";
        }
        // 打印异常日志,便于排查
        System.err.println("触发平台级保护,参数:orderId=" + orderId + ", userId=" + userId + ",异常:" + e.getMessage());
        return Result.fail(503, message);
    }

    /**
     * 重载兜底方法(适用于返回Result<OrderDTO>类型的资源方法)
     * 解决不同返回值类型的资源共用兜底逻辑的问题
     */
    public static Result<OrderDTO> commonBlockHandlerForOrder(String orderId, BlockException e) {
        String message = "系统繁忙,请稍后再试(平台保护)";
        if (e instanceof FlowException) {
            message = "请求过于频繁,请10秒后重试(限流保护)";
        }
        // 兜底返回默认订单信息,避免返回null
        OrderDTO defaultOrder = new OrderDTO();
        defaultOrder.setOrderId(orderId);
        defaultOrder.setOrderStatus("请求被拦截,暂无法处理");
        return Result.success(message, defaultOrder);
    }
}

// 改造OrderController中的@SentinelResource注解(复用通用兜底方法)
@PostMapping("/order/submit")
@SentinelResource(
        value = "orderSubmitResource",
        blockHandler = "commonBlockHandler", // 指定独立类中的兜底方法名
        blockHandlerClass = SentinelBlockHandler.class // 指定兜底方法所在类
)
public Result<String> submitOrder(@RequestParam String orderId, @RequestParam String userId) {
    // 业务逻辑不变
    System.out.println("订单[" + orderId + "]提交中,用户ID:" + userId);
    return Result.success("订单提交成功", orderId);
}

// 新增订单查询接口(复用通用兜底方法,不同返回值类型)
@GetMapping("/order/query")
@SentinelResource(
        value = "orderQueryResource",
        blockHandler = "commonBlockHandlerForOrder",
        blockHandlerClass = SentinelBlockHandler.class
)
public Result<OrderDTO> queryOrder(@RequestParam String orderId) {
    // 业务逻辑
    OrderDTO orderDTO = new OrderDTO();
    orderDTO.setOrderId(orderId);
    orderDTO.setUserId("10086");
    orderDTO.setOrderStatus("已支付");
    return Result.success("查询成功", orderDTO);
}

三、fallback 详解

3.1 核心定义

fallback是Sentinel提供的「业务级异常处理机制」,专门用于处理业务逻辑执行过程中,抛出的异常(不包括BlockException,若未配置blockHandler,才会处理BlockException)。

核心作用是当业务代码出现错误(如数据库异常、远程调用失败、自定义业务异常)时,提供兜底逻辑(如返回默认数据、友好提示),避免服务崩溃或返回不友好的异常信息,保证业务链路的稳定性。

核心特点:仅响应业务逻辑异常,不响应Sentinel规则触发的异常(除非未配置blockHandler);触发时机晚于业务逻辑执行(业务逻辑已执行但出错);优先级低于blockHandler;兜底方法语法规范相对灵活,无需强制添加异常参数。

3.2 语法规范

fallback同样依赖@SentinelResource注解,语法规范与blockHandler有相似之处,但核心差异明显,结合实战总结如下(重点区分二者差异):

  1. 注解配置 :通过@SentinelResourcefallback属性指定兜底方法名(区分大小写,与兜底方法名完全一致);若兜底方法在其他类中,需配合fallbackClass属性指定类对象(如fallbackClass = SentinelFallback.class);1.8.0+版本可通过defaultFallback配置通用兜底方法。
  2. 方法访问权限 :兜底方法无需强制是public修饰(private、protected均可,Sentinel反射可调用);若配合fallbackClass使用,兜底方法必须是static修饰(与blockHandlerClass要求一致)。
  3. 参数列表 :兜底方法的参数列表与原资源方法完全一致,或在最后额外添加一个Throwable类型的参数(用于接收业务异常信息,便于打印日志、排查问题),无需强制添加异常参数(这是与blockHandler的核心差异之一)。
  4. 返回值:与原资源方法的返回值完全一致(包括泛型类型),确保前后端响应格式统一,避免前端解析异常。
  5. 异常范围
  6. 默认处理:所有业务异常(如NullPointerException、SQLException、自定义异常),以及BlockException(若未配置blockHandler);
  7. 排除异常:可通过exceptionsToIgnore属性排除指定异常(如参数校验异常,无需兜底,直接抛出);
  8. 注意:部分Sentinel版本(1.6.0-1.7.0)无法排除BlockException,即使配置了exceptionsToIgnore = {BlockException.class},也无法生效。
  9. 方法位置 :默认与原资源方法在同一个类中;若指定fallbackClass,则兜底方法需在该类中,且为静态方法;可与原资源方法名重复(但不推荐,避免混淆)。

3.3 实战案例

场景2:订单查询接口,业务逻辑中需查询数据库(可能出现数据库连接超时、订单不存在等异常),同时调用远程用户服务(可能出现远程调用失败、超时异常),通过fallback提供兜底响应(返回默认数据或友好提示),同时排除参数校验异常(无需兜底)。

复制代码
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * 订单接口 - Sentinel保护示例(fallback实战,多异常场景)
 */
@RestController
public class OrderQueryController {

    // 模拟远程用户服务(开发中通常是Feign调用)
    @Autowired
    private UserService userService;

    /**
     * 订单查询接口
     * fallback:指定业务异常兜底方法名
     * exceptionsToIgnore:排除不需要处理的异常(此处排除参数校验异常)
     * 注:未配置blockHandler,若触发Sentinel规则,会触发fallback
     */
    @GetMapping("/order/query")
    @SentinelResource(
            value = "orderQueryResource",
            fallback = "orderQueryFallback",
            exceptionsToIgnore = {IllegalArgumentException.class}
    )
    public Result<OrderDTO> queryOrder(@RequestParam String orderId) {
        // 1. 参数校验(异常被exceptionsToIgnore排除,不进入fallback)
        if (orderId == null || orderId.isEmpty()) {
            throw new IllegalArgumentException("订单ID不能为空(参数校验失败)");
        }
        // 2. 模拟数据库查询异常(如连接超时、订单不存在)
        if ("DB_ERROR".equals(orderId)) {
            throw new RuntimeException("数据库连接超时,无法查询订单");
        }
        if ("NOT_FOUND".equals(orderId)) {
            throw new OrderNotFoundException("订单不存在,订单ID:" + orderId); // 自定义业务异常
        }
        // 3. 模拟远程调用异常(调用用户服务失败)
        String userName = userService.getUserName("10086"); // 远程调用
        if (userName == null) {
            throw new FeignException("远程调用用户服务失败,无法获取用户名");
        }
        // 4. 正常业务逻辑:返回订单信息
        OrderDTO orderDTO = new OrderDTO();
        orderDTO.setOrderId(orderId);
        orderDTO.setUserId("10086");
        orderDTO.setUserName(userName);
        orderDTO.setOrderStatus("已支付");
        return Result.success("查询成功", orderDTO);
    }

    /**
     * fallback兜底方法(处理业务逻辑异常)
     * 规范:参数与原方法一致,返回值与原方法一致,可添加Throwable参数接收异常
     * @param orderId 原方法参数
     * @param e 业务异常(可选,用于打印异常日志、排查问题)
     * @return 兜底响应(返回默认订单信息,避免返回异常)
     */
    private Result<OrderDTO> orderQueryFallback(String orderId, Throwable e) {
        // 开发常用:打印异常日志(包含异常堆栈,便于排查)
        System.err.println("订单查询业务异常,订单ID:" + orderId);
        e.printStackTrace();
        // 兜底逻辑:返回默认订单信息(根据业务需求调整,避免返回null)
        OrderDTO defaultOrder = new OrderDTO();
        defaultOrder.setOrderId(orderId);
        defaultOrder.setUserId("unknown");
        defaultOrder.setUserName("未知用户");
        defaultOrder.setOrderStatus("查询异常,暂无法获取订单信息");
        return Result.success("查询异常,已返回默认信息", defaultOrder);
    }

    // 自定义业务异常(开发常用,区分不同业务错误)
    static class OrderNotFoundException extends RuntimeException {
        public OrderNotFoundException(String message) {
            super(message);
        }
    }

    // 模拟远程调用异常(简化,对应Feign调用异常)
    static class FeignException extends RuntimeException {
        public FeignException(String message) {
            super(message);
        }
    }

    // 模拟远程用户服务(简化)
    static class UserService {
        public String getUserName(String userId) {
            // 模拟远程调用失败
            if ("10086".equals(userId)) {
                return "青柠代码录";
            }
            return null;
        }
    }
}

// 订单DTO(开发常用实体类,完善getter/setter)
class OrderDTO {
    private String orderId;
    private String userId;
    private String userName;
    private String orderStatus;

    // getter/setter 完善
    public String getOrderId() { return orderId; }
    public void setOrderId(String orderId) { this.orderId = orderId; }
    public String getUserId() { return userId; }
    public void setUserId(String userId) { this.userId = userId; }
    public String getUserName() { return userName; }
    public void setUserName(String userName) { this.userName = userName; }
    public String getOrderStatus() { return orderStatus; }
    public void setOrderStatus(String orderStatus) { this.orderStatus = orderStatus; }
}

测试效果(4种核心场景):

  1. 数据库异常:当orderId为"DB_ERROR"时,抛出RuntimeException,触发fallback方法,返回默认订单信息,日志打印异常堆栈;
  2. 自定义业务异常:当orderId为"NOT_FOUND"时,抛出OrderNotFoundException,触发fallback方法,返回默认订单信息;
  3. 远程调用异常:当userService返回null时,抛出FeignException,触发fallback方法,返回默认订单信息;
  4. 参数校验异常:当orderId为空时,抛出IllegalArgumentException,该异常被 exceptionsToIgnore排除,不触发fallback,直接抛出异常给前端;
  5. 限流触发(未配置blockHandler):若给该接口配置限流规则,触发限流时,会抛出FlowException,因未配置blockHandler,会触发fallback方法,返回默认订单信息。

3.4 进阶用法:fallbackClass + defaultFallback

  1. fallbackClass:与blockHandlerClass类似,用于将fallback兜底方法抽取到独立的类中,实现业务代码与兜底逻辑的解耦,兜底方法需为static修饰,适用于多个资源共用同一兜底逻辑的场景;
  2. defaultFallback:Sentinel 1.8.0及以上版本新增,用于配置通用的fallback逻辑(适用于多个资源共用同一兜底逻辑,且无需区分参数),语法规范如下:
  • 返回值与原资源方法一致;

  • 参数列表可为空,或仅添加一个 Throwable参数(用于接收异常信息);

  • 若同时配置fallback和defaultFallback,仅fallback生效(fallback优先级高于defaultFallback);

  • 若配合fallbackClass使用,defaultFallback方法也需为static修饰。

    import com.alibaba.csp.sentinel.annotation.SentinelResource;

    /**

    • 通用fallback处理类(抽取通用兜底逻辑,供多个资源复用)
      */
      public class SentinelFallback {
      // 通用fallback方法(static,用于fallbackClass,适用于返回Result<OrderDTO>的资源)
      public static Result<OrderDTO> commonOrderFallback(String orderId, Throwable e) {
      System.err.println("订单业务异常,订单ID:" + orderId + ",异常信息:" + e.getMessage());
      e.printStackTrace();
      OrderDTO defaultOrder = new OrderDTO();
      defaultOrder.setOrderId(orderId);
      defaultOrder.setOrderStatus("查询异常,已兜底");
      return Result.success("业务异常,已返回默认信息", defaultOrder);
      }

      // 通用fallback方法(重载,适用于返回Result<String>的资源)
      public static Result<String> commonStringFallback(String orderId, String userId, Throwable e) {
      System.err.println("业务异常,orderId:" + orderId + ",userId:" + userId);
      return Result.success("业务异常,已兜底", "兜底成功");
      }

      // 默认fallback方法(通用,适用于所有返回Result<OrderDTO>的资源,无需区分参数)
      public static Result<OrderDTO> defaultFallback(Throwable e) {
      System.err.println("通用兜底:业务异常,异常信息:" + e.getMessage());
      OrderDTO defaultOrder = new OrderDTO();
      defaultOrder.setOrderId("unknown");
      defaultOrder.setOrderStatus("查询异常,通用兜底");
      return Result.success("业务异常,已兜底", defaultOrder);
      }
      }

    // 改造OrderQueryController中的注解(复用通用兜底方法)
    @GetMapping("/order/query")
    @SentinelResource(
    value = "orderQueryResource",
    fallbackClass = SentinelFallback.class, // 指定fallback所在类
    fallback = "commonOrderFallback", // 指定具体兜底方法(优先级高于defaultFallback)
    defaultFallback = "defaultFallback", // 通用兜底(当前配置下不会生效)
    exceptionsToIgnore = {IllegalArgumentException.class}
    )
    public Result<OrderDTO> queryOrder(@RequestParam String orderId) {
    // 业务逻辑不变
    }

    // 新增订单取消接口(复用通用兜底方法,不同返回值类型)
    @PostMapping("/order/cancel")
    @SentinelResource(
    value = "orderCancelResource",
    fallbackClass = SentinelFallback.class,
    fallback = "commonStringFallback"
    )
    public Result<String> cancelOrder(@RequestParam String orderId, @RequestParam String userId) {
    // 模拟业务异常
    if ("ERROR".equals(orderId)) {
    throw new RuntimeException("订单取消失败,订单已支付");
    }
    return Result.success("订单取消成功", orderId);
    }

四、核心对比:blockHandler vs fallback

为了方便大家快速区分和记忆,整理了以下对比表格,覆盖核心维度:

对比维度 blockHandler fallback 面试考点补充
核心作用 处理Sentinel规则触发的平台级异常(BlockException及其子类) 处理业务逻辑执行过程中的业务级异常(含自定义异常) 二者分工:平台级拦截用blockHandler,业务级错误用fallback
触发时机 请求被Sentinel拦截,业务逻辑未执行或未执行完毕 业务逻辑已执行,执行过程中抛出异常 触发顺序:blockHandler先于fallback
异常类型 仅处理BlockException及其子类(限流、熔断、热点等) 处理所有业务异常;未配置blockHandler时,也处理BlockException fallback不处理BlockException(有blockHandler时)
语法规范(参数) 必须包含BlockException参数(最后一位) 可选包含Throwable参数(最后一位),非必需 blockHandler参数必须包含BlockException
访问权限 必须是public;配合blockHandlerClass时需为static 无强制要求(private也可);配合fallbackClass时需为static blockHandler必须是public,fallback无要求
优先级 高于fallback(同时触发BlockException时,仅执行blockHandler) 低于blockHandler(未触发BlockException或未配置blockHandler时执行) 核心考点:优先级对比
应用场景 秒杀、大促等高频并发场景,处理限流、熔断兜底 数据库查询、远程调用等业务场景,处理业务异常兜底 结合场景说明二者用法
版本依赖 所有Sentinel版本均支持,无版本限制 1.6.0+支持处理所有业务异常;1.8.0+支持defaultFallback 版本差异是面试高频考点
兜底逻辑要求 轻量,仅返回提示/默认数据,不执行复杂逻辑 轻量,避免二次异常,可打印日志排查问题

补充说明:兜底逻辑的核心原则是"轻量无依赖",无论blockHandler还是fallback,都应避免在兜底方法中执行数据库操作、远程调用等复杂逻辑,否则会导致兜底方法自身抛出异常,反而破坏接口稳定性,这也是开发中兜底逻辑的核心规范。

五、最佳实践

结合前文的语法规范、实战案例及踩坑点,总结开发中blockHandler与fallback的最佳使用方式,覆盖单接口、多接口、复杂场景的适配,确保兜底逻辑生效。

5.1 单接口兜底配置

对于单个核心接口(如订单提交、支付回调),需同时配置blockHandler(处理平台级异常)和fallback(处理业务级异常),明确分工、双重兜底,确保接口在高并发和业务异常场景下均能稳定响应,示例如下:

复制代码
@PostMapping("/order/pay")
@SentinelResource(
        value = "orderPayResource", // 资源唯一标识,建议与接口路径保持一致,便于排查
        blockHandler = "orderPayBlockHandler", // 平台级异常兜底
        fallback = "orderPayFallback", // 业务级异常兜底
        exceptionsToIgnore = {IllegalArgumentException.class} // 排除参数校验异常
)
public Result<String> payOrder(@RequestParam String orderId, @RequestParam String payType) {
    // 1. 参数校验(异常被排除,不进入fallback)
    if (payType == null || !Arrays.asList("WECHAT", "ALIPAY").contains(payType)) {
        throw new IllegalArgumentException("支付方式非法,仅支持微信、支付宝");
    }
    // 2. 业务逻辑:调用支付接口、更新订单状态、记录支付日志
    boolean paySuccess = payService.doPay(orderId, payType);
    if (!paySuccess) {
        throw new PayFailedException("支付失败,请重新尝试"); // 自定义业务异常
    }
    return Result.success("支付成功", orderId);
}

// blockHandler兜底(平台级异常:限流、熔断等)
public Result<String> orderPayBlockHandler(String orderId, String payType, BlockException e) {
    String message = "系统繁忙,请稍后再试";
    if (e instanceof FlowException) {
        message = "支付请求过于频繁,请10秒后重试";
    } else if (e instanceof DegradeException) {
        message = "支付服务暂时降级,请稍后再试";
    }
    System.err.println("支付触发平台级保护,orderId:" + orderId + ",异常:" + e.getClass().getSimpleName());
    return Result.fail(503, message);
}

// fallback兜底(业务级异常:支付失败、自定义异常等)
private Result<String> orderPayFallback(String orderId, String payType, Throwable e) {
    System.err.println("支付业务异常,orderId:" + orderId + ",异常信息:" + e.getMessage());
    e.printStackTrace();
    // 兜底逻辑:返回支付失败提示,引导用户重新支付
    return Result.success("支付异常,请重新尝试支付", orderId);
}

5.2 多接口通用兜底配置

当多个接口(如订单相关、用户相关接口)的兜底逻辑一致时,推荐将blockHandler和fallback兜底方法分别抽取到独立的处理类中,通过blockHandlerClass和fallbackClass引用,实现业务代码与兜底逻辑的解耦,提升代码可维护性,避免重复开发。

核心规范:

  1. 通用blockHandler处理类:存放所有接口的平台级异常兜底方法,方法为static、public,根据返回值类型重载;
  2. 通用fallback处理类:存放所有接口的业务级异常兜底方法,方法为static,可根据返回值类型重载,支持defaultFallback通用兜底;
  3. 资源标识(@SentinelResource的value):建议采用"模块名+接口名"的格式(如order:submit、user:query),便于规则配置和监控排查。

本文由mdnice多平台发布

相关推荐
AI袋鼠帝2 小时前
耗时2天,搓了个Agent🦀 App,语音唤醒,还能视频通话,已免费开源~
后端
遇见你...8 小时前
A01-Spring概述
java·后端·spring
代码匠心8 小时前
从零开始学Flink:TopN 榜单
大数据·后端·flink·flink sql·大数据处理
lizhongxuan10 小时前
Claude Code 防上下文爆炸:源码级深度解析
前端·后端
Warson_L11 小时前
Python 流程控制与逻辑
后端·python
糖炒栗子032611 小时前
架构笔记:应用配置无状态化 (Statelessness)
后端
Warson_L11 小时前
Python 四大组合数据类型 (Collection Types)
后端·python
查古穆11 小时前
大白话讲ReAct:大模型的“边想边干”
后端
于先生吖11 小时前
SpringBoot+MQTT 无人健身房智能管控系统源码实战
java·spring boot·后端