sentinel在1.6.0后就引入了Sentinel API Gateway Adapter Common,适配了Gateway。
以前sentinel自定义限流结果是通过实现BlockExceptionHandler接口进行自定义限流返回结果,但是如果是使用了spring-cloud-alibaba-sentinel-gateway的依赖进行限流的话,使用之前的接口是不起作用的。
首先是我本地项目的环境,适配的是springboot 2.3.12.RELEASE版本,cloud版本为Hoxton.SR12。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
通过SentinelSCGAutoConfiguration配置类,我们可以看到主要有两种方法可以实现自定义:配置文件配置和自定义代码配置。
1.配置文件配置
在application.yml加入以下配置:
spring:
cloud:
sentinel:
scg:
fallback:
mode: response
responseBody: "{\"code\":\"429\",\"msg\":\"请求太多了\"}"
对应的配置类为FallbackProperties,以下是详细的配置:
/**
* The fallback mode for sentinel spring-cloud-gateway. choose `redirect` or
* `response`.
*/
private String mode;
/**
* Redirect Url for `redirect` mode.
*/
private String redirect;
/**
* Response Body for `response` mode.
*/
private String responseBody;
/**
* Response Status for `response` mode.
*/
private Integer responseStatus = HttpStatus.TOO_MANY_REQUESTS.value();
/**
* Content-Type for `response` mode.
*/
private String contentType = MediaType.APPLICATION_JSON.toString();
可以通过配置看到根据mode的类型处理模式有两种:response和redirect。在SentinelSCGAutoConfiguration中对应的代码在 initFallback() 方法。
- response
如果设置mode为response则返回contentType 类型的responseBody,响应码为responseStatus 。 - redirect
如果设置mode为redirect,则限流后自动跳转某个页面,页面地址为redirect。
2. 自定义代码配置
通过Sentinel wiki 我们可以看到:
所以我们可以通过GatewayCallbackManager的 setBlockHandler() 方法来实现注册我们自定义的异常处理类,需要实现BlockRequestHandler接口,默认的异常处理类是:DefaultBlockRequestHandler。
首先自定义一个异常处理类:
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.InvalidMediaTypeException;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.List;
import static org.springframework.web.reactive.function.BodyInserters.fromValue;
/**
* @author TYH
* @Description: Sentinel异常处理类
* @date 2022/3/8 14:34
*/
public class SentinelBlockRequestHandler implements BlockRequestHandler {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable t) {
// 判断是否是html访问,如果是则转发url
if (acceptsHtml(exchange)) {
return htmlErrorResponse();
}
// 如果是接口访问,则返回提示
return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(fromValue(new ResponseData(HttpStatus.TOO_MANY_REQUESTS.value(),"请求太多了")));
}
private Mono<ServerResponse> htmlErrorResponse() {
String url="http://www.baidu.com";
URI uri =URI.create(url);
return ServerResponse.temporaryRedirect(uri).build();
}
private boolean acceptsHtml(ServerWebExchange exchange) {
try {
List<MediaType> acceptedMediaTypes = exchange.getRequest().getHeaders().getAccept();
acceptedMediaTypes.remove(MediaType.ALL);
MediaType.sortBySpecificityAndQuality(acceptedMediaTypes);
return acceptedMediaTypes.stream()
.anyMatch(MediaType.TEXT_HTML::isCompatibleWith);
} catch (InvalidMediaTypeException ex) {
return false;
}
}
/**
* 定义返回的实体类,字段根据需要添加
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
static class ResponseData {
private int code;
private String msg;
}
}
然后再调用GatewayCallbackManager的方法进行注册:
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Configuration;
/**
* @author TYH
* @Description:
* @date 2022/3/8 15:02
*/
@Slf4j
@Configuration
public class InitConfig implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
log.info("初始化限流handler");
GatewayCallbackManager.setBlockHandler(new SentinelBlockRequestHandler());
}
}
然后就实现了接口访问限流返回固定值,html访问限流跳转页面的效果。