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接口就不会像之前那样一个数字就注册一个资源了:
