SpringCloud Sentinel 组件中,blockHandler 和 fallback 的区别是什么?

摘要:在Sentinel的异常处理机制中,blockHandler与fallback是两个高频使用,但极易
在Sentinel的异常处理机制中,blockHandler与fallback是两个高频使用,但极易混淆的核心配置,二者虽均用于异常兜底、避免服务崩溃或返回不友好响应,但在触发场景、处理范围、语法规范、使用优先级上截然不同,若使用不当会导致兜底失效、异常无法正常拦截等问题。 ======= 🌟 青柠来相伴,代码更简单。🌟 ======= 📚 本文所有内容,我都整理在了 青柠合集 里。👇 🎯 搜索关注【青柠代码录】,即可查看所有合集文章 ~ ======= 🌟 ================ 🌟 ======= 一、Sentinel异常处理核心场景与执行流程 在讲解二者区别前,先明确Sentinel的两类核心异常场景及整体执行流程,这是理解blockHandler与fallback的基础: 流量控制/熔断降级异常(平台级拦截): 由Sentinel的五大核心规则(限流、熔断、热点、系统保护、授权规则)触发,抛出BlockException及其子类,具体包括: 此类异常属于“平台级拦截”,触发时机早于业务逻辑执行,即Sentinel先拦截请求,再判断是否执行业务代码,因此业务逻辑未执行或未执行完毕。 FlowException:限流异常(请求QPS/线程数超出阈值); DegradeException:熔断异常(服务调用失败率、响应时间超出阈值,触发熔断); ParamFlowException:热点参数限流异常(指定热点参数的请求超出阈值); SystemException:系统保护异常(系统负载、CPU、内存超出阈值,触发系统保护); AuthorityException:授权异常(请求来源未通过授权校验)。 业务逻辑异常(业务级错误): 业务代码执行过程中抛出的各类异常,包括但不限于: 此类异常属于“业务级错误”,此时Sentinel规则未触发,业务逻辑已执行但出现异常,需通过兜底逻辑避免异常扩散。 基础异常:NullPointerException(空指针)、IllegalArgumentException(参数非法); 业务异常:自定义业务异常(如OrderNotFoundException订单不存在、StockInsufficientException库存不足); 外部依赖异常: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核心注解,用于定义受保护的资源),需严格遵循以下规范: 注解配置:在需要保护的资源(接口方法、业务方法)上添加@SentinelResource注解,通过blockHandler属性指定兜底方法名(必须与兜底方法名完全一致,区分大小写);若兜底方法在其他类中,需配合blockHandlerClass属性指定类对象(如blockHandlerClass = SentinelBlockHandler.class)。 方法访问权限:兜底方法必须是public修饰(Sentinel底层通过反射调用,private/protected会导致反射失败);若配合blockHandlerClass使用,兜底方法必须额外添加static修饰(否则无法被Sentinel解析和调用)。 参数列表:兜底方法的参数列表必须与原资源方法完全一致,且在最后额外添加一个BlockException类型的参数,用于接收触发的具体异常(可通过该参数判断是限流、熔断还是其他平台级异常,从而返回更精准的提示)。 返回值:兜底方法的返回值,必须与原资源方法的返回值完全一致(包括泛型类型),确保前后端响应格式统一,避免前端解析异常(如原方法返回Result<OrderDTO>,兜底方法也必须返回Result<OrderDTO>)。 方法位置:默认情况下,兜底方法需与原资源方法在同一个类中;若指定blockHandlerClass,则兜底方法需在该类中,且为静态方法;兜底方法名不可与原资源方法名重复。 异常捕获范围:仅能捕获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种核心场景): 限流触发:当请求QPS超过100时,请求被Sentinel拦截,触发orderSubmitBlockHandler方法,返回“订单提交过于频繁,请10秒后重试(限流保护,QPS已达上限)”,避免直接抛出FlowException异常; 熔断触发:当订单提交接口失败率超过50%(且请求数≥10),触发熔断,请求被拦截,返回“订单服务暂时降级,请10秒后重试(熔断保护,服务调用失败率过高)”; 业务异常测试:若放开代码中“模拟业务异常”的注释,当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有相似之处,但核心差异明显,结合实战总结如下(重点区分二者差异): 注解配置:通过@SentinelResource的fallback属性指定兜底方法名(区分大小写,与兜底方法名完全一致);若兜底方法在其他类中,需配合fallbackClass属性指定类对象(如fallbackClass = SentinelFallback.class);1.8.0+版本可通过defaultFallback配置通用兜底方法。 方法访问权限:兜底方法无需强制是public修饰(private、protected均可,Sentinel反射可调用);若配合fallbackClass使用,兜底方法必须是static修饰(与blockHandlerClass要求一致)。 参数列表:兜底方法的参数列表与原资源方法完全一致,或在最后额外添加一个Throwable类型的参数(用于接收业务异常信息,便于打印日志、排查问题),无需强制添加异常参数(这是与blockHandler的核心差异之一)。 返回值:与原资源方法的返回值完全一致(包括泛型类型),确保前后端响应格式统一,避免前端解析异常。 异常范围: 默认处理:所有业务异常(如NullPointerException、SQLException、自定义异常),以及BlockException(若未配置blockHandler); 排除异常:可通过exceptionsToIgnore属性排除指定异常(如参数校验异常,无需兜底,直接抛出); 注意:部分Sentinel版本(1.6.0-1.7.0)无法排除BlockException,即使配置了exceptionsToIgnore = {BlockException.class},也无法生效。 方法位置:默认与原资源方法在同一个类中;若指定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种核心场景): 数据库异常:当orderId为“DB_ERROR”时,抛出RuntimeException,触发fallback方法,返回默认订单信息,日志打印异常堆栈; 自定义业务异常:当orderId为“NOT_FOUND”时,抛出OrderNotFoundException,触发fallback方法,返回默认订单信息; 远程调用异常:当userService返回null时,抛出FeignException,触发fallback方法,返回默认订单信息; 参数校验异常:当orderId为空时,抛出IllegalArgumentException,该异常被exceptionsToIgnore排除,不触发fallback,直接抛出异常给前端; 限流触发(未配置blockHandler):若给该接口配置限流规则,触发限流时,会抛出FlowException,因未配置blockHandler,会触发fallback方法,返回默认订单信息。 3.4 进阶用法:fallbackClass + defaultFallback fallbackClass:与blockHandlerClass类似,用于将fallback兜底方法抽取到独立的类中,实现业务代码与兜底逻辑的解耦,兜底方法需为static修饰,适用于多个资源共用同一兜底逻辑的场景; 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&gt;的资源) 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&gt;的资源) 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引用,实现业务代码与兜底逻辑的解耦,提升代码可维护性,避免重复开发。 核心规范: 通用blockHandler处理类:存放所有接口的平台级异常兜底方法,方法为static、public,根据返回值类型重载; 通用fallback处理类:存放所有接口的业务级异常兜底方法,方法为static,可根据返回值类型重载,支持defaultFallback通用兜底; 资源标识(@SentinelResource的value):建议采用“模块名+接口名”的格式(如order:submit、user:query),便于规则配置和监控排查。 本文由mdnice多平台发布