Spring Cloud Alibaba之服务容错组件 – Sentinel扩展(十九)

  • 2019 年 11 月 2 日
  • 笔记

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文链接:https://blog.csdn.net/pyycsd/article/details/102803281

扩展 – 错误信息优化

Sentinel默认在当前服务触发限流或降级时仅返回简单的异常信息,如下:

Spring Cloud Alibaba之服务容错组件 – Sentinel [代码篇]

并且限流和降级返回的异常信息是一样的,导致无法根据异常信息区分是触发了限流还是降级。

所以我们需要对错误信息进行相应优化,以便可以细致区分触发的是什么规则。Sentinel提供了一个UrlBlockHandler接口,实现该接口即可自定义异常处理逻辑。具体如下示例:

import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlBlockHandler;  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.flow.param.ParamFlowException;  import com.alibaba.csp.sentinel.slots.system.SystemBlockException;  import lombok.Builder;  import lombok.Data;  import lombok.extern.slf4j.Slf4j;  import org.codehaus.jackson.map.ObjectMapper;  import org.springframework.http.MediaType;  import org.springframework.stereotype.Component;    import javax.servlet.http.HttpServletRequest;  import javax.servlet.http.HttpServletResponse;  import java.io.IOException;    /**   * 自定义流控异常处理   **/  @Slf4j  @Component  public class MyUrlBlockHandler implements UrlBlockHandler {        @Override      public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException e) throws IOException {          MyResponse errorResponse = null;          // 不同的异常返回不同的提示语          if (e instanceof FlowException) {              errorResponse = MyResponse.builder()                      .status(100).msg("接口限流了")                      .build();          } else if (e instanceof DegradeException) {              errorResponse = MyResponse.builder()                      .status(101).msg("服务降级了")                      .build();          } else if (e instanceof ParamFlowException) {              errorResponse = MyResponse.builder()                      .status(102).msg("热点参数限流了")                      .build();          } else if (e instanceof SystemBlockException) {              errorResponse = MyResponse.builder()                      .status(103).msg("触发系统保护规则")                      .build();          } else if (e instanceof AuthorityException) {              errorResponse = MyResponse.builder()                      .status(104).msg("授权规则不通过")                      .build();          }            response.setStatus(500);          response.setCharacterEncoding("utf-8");          response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);          new ObjectMapper().writeValue(response.getWriter(), errorResponse);      }  }    /**   * 简单的响应结构体   */  @Data  @Builder  class MyResponse {      private Integer status;      private String msg;  }

此时再触发流控规则就可以响应代码中自定义的提示信息了:

扩展 – 实现区分来源

当配置流控规则或授权规则时,若需要针对调用来源进行限流,得先实现来源的区分,Sentinel提供了RequestOriginParser接口来处理来源。只要Sentinel保护的接口资源被访问,Sentinel就会调用RequestOriginParser的实现类去解析访问来源。

写代码:首先,服务消费者需要具备有一个来源标识,这里假定为服务消费者在调用接口的时候都会传递一个origin的header参数标识来源。具体如下示例:

import com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser;  import com.alibaba.nacos.client.utils.StringUtils;  import lombok.extern.slf4j.Slf4j;  import org.springframework.stereotype.Component;    import javax.servlet.http.HttpServletRequest;    /**   * 实现区分来源   **/  @Slf4j  @Component  public class MyRequestOriginParser implements RequestOriginParser {        @Override      public String parseOrigin(HttpServletRequest request) {          // 从header中获取名为 origin 的参数并返回          String origin = request.getHeader("origin");          if (StringUtils.isBlank(origin)) {              // 如果获取不到,则抛异常              String err = "origin param must not be blank!";              log.error("parse origin failed: {}", err);              throw new IllegalArgumentException(err);          }            return origin;      }  }

编写完以上代码并重启项目后,此时header中不包含origin参数就会报错了:

image.png

扩展 – RESTful URL支持

了解过RESTful URL的都知道这类URL路径可以动态变化,而Sentinel默认是无法识别这种变化的,所以每个路径都会被当成一个资源,如下图:

这显然是有问题的,好在Sentinel提供了UrlCleaner接口解决这个问题。实现该接口可以让我们对来源url进行编辑并返回,这样就可以将RESTful URL里动态的路径转换为占位符之类的字符串。具体实现代码如下:

import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlCleaner;  import lombok.extern.slf4j.Slf4j;  import org.apache.commons.lang3.math.NumberUtils;  import org.springframework.stereotype.Component;    import java.util.Arrays;    /**   * RESTful URL支持   **/  @Slf4j  @Component  public class MyUrlCleaner implements UrlCleaner {        @Override      public String clean(String originUrl) {          String[] split = originUrl.split("/");            // 将数字转换为特定的占位标识符          return Arrays.stream(split)                  .map(s -> NumberUtils.isNumber(s) ? "{number}" : s)                  .reduce((a, b) -> a + "/" + b)                  .orElse("");      }  }

此时该RESTful接口就不会像之前那样一个数字就注册一个资源了: