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

SpringCloud快速入门(11)---- Sentinel(异常处理)

1.Web接口异常处理

1.1 问题场景

当我们对web接口进行了保护,例如流量控制时,访问量过多时sentinel会直接把错误信息返回:

这是因为sentinel默认是使用一个拦截器来实现的:

public abstract class AbstractSentinelInterceptor implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String resourceName = ""; try { resourceName = this.getResourceName(request); if (StringUtil.isEmpty(resourceName)) { return true; } else if (this.increaseReference(request, this.baseWebMvcConfig.getRequestRefName(), 1) != 1) { return true; } else { String origin = this.parseOrigin(request); String contextName = this.getContextName(request); ContextUtil.enter(contextName, origin); //资源保护流程 Entry entry = SphU.entry(resourceName, 1, EntryType.IN); request.setAttribute(this.baseWebMvcConfig.getRequestAttributeName(), entry); //没有违反规则返回true,违法规则抛出BlockException异常 return true; } } catch (BlockException var12) { BlockException e = var12; try { //调用这个方法处理 this.handleBlockException(request, response, resourceName, e); } finally { ContextUtil.exit(); } return false; } } }

handleBlockException()最后会调用下面这个handle进行处理:

public class DefaultBlockExceptionHandler implements BlockExceptionHandler { public DefaultBlockExceptionHandler() { } public void handle(HttpServletRequest request, HttpServletResponse response, String resourceName, BlockException ex) throws Exception { response.setStatus(429); PrintWriter out = response.getWriter(); out.print("Blocked by Sentinel (flow limiting)"); out.flush(); out.close(); } }

也就输出了页面里的内容。这样的方式不适合前后端分离项目,我们需要自定义异常处理器统一返回 JSON。

1.2 自定义异常

定义一个统一返回对象:

package com.ting.common; import lombok.Data; @Data public class R { private Integer code; private String msg; private Object data; public static R ok() { R r = new R(); r.setCode(200); return r; } public static R ok(String msg, Object data) { R r = new R(); r.setCode(200); r.setMsg(msg); r.setData(data); return r; } public static R error() { R r = new R(); r.setCode(500); return r; } public static R error(Integer code, String msg) { R r = new R(); r.setCode(code); r.setMsg(msg); return r; } }

实现BlockExceptionHandler接口编写自定义返回内容:

@Component public class MyBlockExceptionHandler implements BlockExceptionHandler { private ObjectMapper objectMapper = new ObjectMapper(); @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, String s, BlockException e) throws Exception { PrintWriter writer = httpServletResponse.getWriter(); R error = R.error(500, s + "被Sentinel限制了,原因:" + e.getMessage()); writer.write(objectMapper.writeValueAsString(error)); } }

再次触发保护时就会返回我们设定好的内容

为什么我们实现了BlockExceptionHandler 就不会走DefaultBlockExceptionHandler 了:

看这段源码:

public SentinelWebMvcConfig sentinelWebMvcConfig() { SentinelWebMvcConfig sentinelWebMvcConfig = new SentinelWebMvcConfig(); sentinelWebMvcConfig.setHttpMethodSpecify(this.properties.getHttpMethodSpecify()); sentinelWebMvcConfig.setWebContextUnify(this.properties.getWebContextUnify()); Optional var10000; //Optional<BlockExceptionHandler> blockExceptionHandlerOptional; //isPresent()表示判断Spring容器是否有BlockExceptionHandler的bean if (this.blockExceptionHandlerOptional.isPresent()) { //有,直接用 var10000 = this.blockExceptionHandlerOptional; Objects.requireNonNull(sentinelWebMvcConfig); var10000.ifPresent(sentinelWebMvcConfig::setBlockExceptionHandler); } else if (StringUtils.hasText(this.properties.getBlockPage())) { //如果配置了自定义的限流跳转页面,则使用跳转方式处理异常 sentinelWebMvcConfig.setBlockExceptionHandler((request, response, resourceName, e) -> { response.sendRedirect(this.properties.getBlockPage()); }); } else { //使用默认的DefaultBlockExceptionHandler sentinelWebMvcConfig.setBlockExceptionHandler(new DefaultBlockExceptionHandler()); } var10000 = this.urlCleanerOptional; Objects.requireNonNull(sentinelWebMvcConfig); var10000.ifPresent(sentinelWebMvcConfig::setUrlCleaner); var10000 = this.requestOriginParserOptional; Objects.requireNonNull(sentinelWebMvcConfig); var10000.ifPresent(sentinelWebMvcConfig::setOriginParser); return sentinelWebMvcConfig; }

注意:只有会被自动识别的资源(SpringMVC 接口,OpenFeign 远程调用接口,Gateway 网关路由接口)才会使用BlockExceptionHandler处理,@SentinelResource 定义的资源不会走BlockExceptionHandler

2. @SentinelResource添加的资源

2.1 源码解析

这里我们修改一下项目代码;

@RestController public class OrderController { @Autowired OrderService orderService; @GetMapping("/order") public Order createOrder( @RequestParam("userId") Long userId, @RequestParam("productId") Long productId) { return orderService.createOrder(userId, productId); } }
@Slf4j @Service public class OrderServiceImpl implements OrderService { @Autowired ProductFeignClient productFeignClient; @SentinelResource(value = "createOrder") @Override public Order createOrder(Long userId, Long productId) { log.info("调用了OrderServiceImpl.createOrder(Long userId, Long productId)"); Product product = productFeignClient.getProductById(productId); Order order = new Order(); order.setId(productId); order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum()))); order.setUserId(userId); order.setNickName("Ting"); order.setAddress("北京"); order.setProductList(Arrays.asList(product)); return order; } }

添加Service层,把业务逻辑移动到service层,并且把createOrder方法标注为createOrder资源,调用一次后我们就可以在sentinel控制台看见这个资源:

我们对其进行流量控制,快速点击触发保护:

可以发现并没有走BlockExceptionHandler进行处理,这是因为BlockExceptionHandler是基于web拦截器进行实现的,只能对于SpringMVC 接口,OpenFeign 远程调用接口,Gateway 网关路由接口,这种涉及到请求的资源生效。@SentinelResource 手动资源保护是基于SpringAOP实现的:

在SentinelResurceAspect这个类里我们可以看到到,定义了一个切点即@SentinelResource注解,添加了这个注解的方法就会使用下面的invokeResourceWithSentinel方法进行增强,可以看到在执行pjp.proceed()之前调用了SphU.entry()即检查是否违法了规则,如果正常则继续执行,异常则会抛出BlockException异常被下面的catch捕获进而调用handleBlockException()方法进行处理。

handleBlockException()方法中我们可以看到,检查了@SentinelResource注解中是否设置了blockHandler,如果设置了由invoke()方法处理,没有则由handleFallback()方法处理。在我们刚才的情况中我们没有设置任何东西,所有代码在这里会进入handleFallback()方法:

这里可以看到handleFallback()方法调用了他自己的一个重载方法,其中传入了两个关键参数:

annotation.fallback()和annotation.defaultFallback()

在下面方法中,首先通过fallback参数尝试获取了fallback方法,如果有则通过这个方法处理,但是我们并没有设置,所以这里获取的结果是null,会直接进入else中,调用handleDefaultFallback()方法通过默认的fallback(annotation.defaultFallback())进行处理。

在handleDefaultFallback()方法中先通过DefaultFallback参数尝试获取了默认fallback方法,但是我们什么都没有在@SentinelResource注解中设置,所以获取到的同样是null,这里直接进入了else也就是直接把异常抛出。

2.2 blockHandle

通过这个属性指定一个兜底回调,方法必须和原方法参数、返回值完全一致可以额外添加BlockException属性:

@SentinelResource(value = "createOrder", blockHandler = "createOrderFallback") @Override public Order createOrder(Long userId, Long productId) { log.info("调用了OrderServiceImpl.createOrder(Long userId, Long productId)"); Product product = productFeignClient.getProductById(productId); Order order = new Order(); order.setId(productId); order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum()))); order.setUserId(userId); order.setNickName("Ting"); order.setAddress("北京"); order.setProductList(Arrays.asList(product)); return order; } //兜底回调 public Order createOrderFallback(Long userId, Long productId, BlockException e) { Order order = new Order(); order.setId(0L); order.setTotalAmount(new BigDecimal("0")); order.setUserId(0L); order.setNickName("出现异常:" + e.getClass()); order.setAddress(""); return order; }

当再次触发限流时就会触发我们的兜底回调:

注意:blockHandler只处理限流 / 熔断(BlockException)导致的异常

2.3 fallback

fallback 只处理业务异常(运行时异常),不会处理BlockException异常,方法必须和原方法参数、返回值完全一致,可以额外加 Throwable 参数。

注意:Sentinel 默认不会捕获业务异常,运行时异常会直接抛出去,不走 fallback,我们需要在配置文件中设置:

spring.cloud.sentinel.enabled=true
@SentinelResource(value = "createOrder", blockHandler = "createOrderFallback", fallback = "createOrderRuntimeExceptionFallback") @Override public Order createOrder(Long userId, Long productId) { log.info("调用了OrderServiceImpl.createOrder(Long userId, Long productId)"); Order test = null; test.getAddress(); Product product = productFeignClient.getProductById(productId); Order order = new Order(); order.setId(productId); order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum()))); order.setUserId(userId); order.setNickName("Ting"); order.setAddress("北京"); order.setProductList(Arrays.asList(product)); return order; } //兜底回调 public Order createOrderRuntimeExceptionFallback(Long userId, Long productId, Throwable e) { Order order = new Order(); order.setId(0L); order.setTotalAmount(new BigDecimal("0")); order.setUserId(0L); order.setNickName("出现运行时异常异常:" + e.getClass()); order.setAddress(""); return order; }

这我模拟了一个空指针的场景:

2.4 defaultFallback

和fallback类似,只处理业务异常(运行时异常),不会处理BlockException异常,通常用于对当前类多个业务方法做兜底返回,返回值必须和业务方法一致,支持无参或Throwable参数,当未指定fallback或者指定了未实现时会使用defaultFallback处理运行时异常:

@SentinelResource(value = "createOrder", blockHandler = "createOrderFallback", defaultFallback = "OrderRuntimeExceptionDefaultFallback" ) @Override public Order createOrder(Long userId, Long productId) { log.info("调用了OrderServiceImpl.createOrder(Long userId, Long productId)"); Order test = null; test.getAddress(); Product product = productFeignClient.getProductById(productId); Order order = new Order(); order.setId(productId); order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum()))); order.setUserId(userId); order.setNickName("Ting"); order.setAddress("北京"); order.setProductList(Arrays.asList(product)); return order; } public Order OrderRuntimeExceptionDefaultFallback(Throwable e) { Order order = new Order(); order.setId(0L); order.setTotalAmount(new BigDecimal("0")); order.setUserId(0L); order.setNickName("出现运行时异常异常:" + e.getClass()); order.setAddress("OrderDefaultFallback"); return order; }

3. Feign远程调用资源

3.1 使用示例

在前面OpenFeign章节,我们已经写过示例:

@FeignClient(value = "service-product", fallback = ProductFeignClientFallback.class) public interface ProductFeignClient { @GetMapping("/product/{id}") Product getProductById(@PathVariable("id") Long id); }
@Component public class ProductFeignClientFallback implements ProductFeignClient { @Override public Product getProductById(Long id) { Product product = new Product(); product.setId(666L); product.setPrice(new BigDecimal("636")); product.setProductName("xiaomi666"); product.setNum(777); return product; } }

编写好fallback,在@FeignClient注解中指定fallback方法所在类即可

3.2 源码解析

在SentinelFeignAutoConfiguration,这个Sentinel和OpenFeign整合配置类中,这里可以看到注册了Feign.Builder到spring容器中,这里面就包含了所有的Feign客户端:

在这个类中内部构建方法可以看到,这里获取并判断了fallback是否存在,最后整合进了SentinelInvocationHandler

在这个类的invoke方法中就可以看到我们熟悉的逻辑,先判断是否违法规则,如果违法抛出异常,再判断是否有fallback,没有直接把异常抛出

4.SphU硬编码控制

我们可以通过SphU的entry方法对任意一段代码进行保护,这个方法了解即可

public Order createOrder(Long userId, Long productId) { log.info("调用了OrderServiceImpl.createOrder(Long userId, Long productId)"); // Order test = null; // test.getAddress(); Product product = productFeignClient.getProductById(productId); Order order = new Order(); try { SphU.entry("resourceName"); order.setId(productId); order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum()))); order.setUserId(userId); order.setNickName("Ting"); order.setAddress("北京"); order.setProductList(Arrays.asList(product)); } catch (BlockException e) { //编码处理 } return order; }
http://www.jsqmd.com/news/829646/

相关文章:

  • 汕头祥龙再生资源回收:澄海可靠的办公室拆除公司 - LYL仔仔
  • 从零入门 WinDbg:手把手分析 C++ 崩溃 Dump(超详细实战版)
  • 3分钟拯救你的B站视频:m4s-converter零转码转换完全指南
  • 当高原对话AI:拉萨本地GEO优化公司推荐指南 - 品牌评测官
  • 别再手动绑骨了!用Mixamo+Unity 2022,5分钟搞定二次元角色动画(附材质修复全流程)
  • 植物导水率测量仪产品介绍和厂家推荐 - 品牌推荐大师
  • 《世毫九本原论》导读版研究报告(科普教育)
  • 利用taotoken多模型能力为内容创作平台提供ai增强服务
  • AI 开发狂飙!.NET 11 Preview 4 原生集成向量搜索 + MCP 模板,EF Core 直接对标 RAG 应用
  • PDF怎么转换格式?2026在线转换工具实测对比与方法详解 - 软件小管家
  • 在Ubuntu 18.04上为ARM板搭建Qt Creator交叉编译环境(Qt5.12.9 + tslib)
  • 内容做了一大堆,流量就是起不来?初创公司低成本获流的真实解法
  • Windows安卓应用安装器终极指南:3种方法实现跨平台无缝体验
  • 植物生理生态监测系统产品介绍和厂家推荐 - 品牌推荐大师
  • 2026年免费PDF转换软件怎么选?热门工具优缺点对比与推荐指南 - 软件小管家
  • 性比价很高的ai中转站
  • 手把手教你用hashcat和rar2john破解RAR5压缩包密码(保姆级图文教程)
  • 知名水果礼盒厂商对接方式amp;专属定制水果礼盒全套落地方案,海棠果礼盒/香妃果礼盒/小苹果礼盒,水果礼盒厂商哪家可靠 - 品牌推荐师
  • 宪意(山东)建筑拆除:济南拆门窗源头厂家 - LYL仔仔
  • 基于Magisk挂载机制的Android HTTPS流量监控完整技术方案
  • Ubuntu20.04安装Isaac Sim 4.5 + Isaac Lab 2.1
  • PDF怎么转Word?如何免费转换?2026年最新软件推荐对比 - 软件小管家
  • 北京金发钹祥金属材料贸易:专业的北京不锈钢焊接公司 - LYL仔仔
  • 82、【Agent】【OpenCode】bash 工具提示词(amend 风险)
  • PDF怎么转PPT?2026年免费转换方法和软件推荐 - 软件小管家
  • 1987年6月28日下午15-17点出生性格、运势和命运
  • 《拓扑不变量系统刻画》导读版研究报告(科普教育)
  • VTube Studio插件开发实战指南:如何快速构建虚拟主播互动系统
  • PDF怎么转Word?2026免费PDF转Word软件推荐与实测对比 - 软件小管家
  • Heightmapper完全指南:5步将全球地形数据变成3D模型