扩展 - 错误信息优化
Sentinel默认在当前服务触发限流或降级时仅返回简单的异常信息,如下:
image并且限流和降级返回的异常信息是一样的,导致无法根据异常信息区分是触发了限流还是降级。
所以我们需要对错误信息进行相应优化,以便可以细致区分触发的是什么规则。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;
}
此时再触发流控规则就可以响应代码中自定义的提示信息了:
image扩展 - 实现区分来源
当配置流控规则或授权规则时,若需要针对调用来源进行限流,得先实现来源的区分,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扩展 - RESTful URL支持
了解过RESTful URL的都知道这类URL路径可以动态变化,而Sentinel默认是无法识别这种变化的,所以每个路径都会被当成一个资源,如下图:
image这显然是有问题的,好在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接口就不会像之前那样一个数字就注册一个资源了:
image.png
网友评论