spring:
cloud:
sentinel:
eager: true
transport:
dashboard: 192.168.1.22:8858
port: 8719
datasource:
ds1:
nacos:
server-addr: 52.83.239.30:8848
dataId: gateway-server-flow
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
限流规则
[
{
"resource": "/swagger-ui.html", // 资源名称
"limitApp": "192.168.1.25", // 来源应用 default 不区分调用者
"grade": 1, // 阀值类型,0表示线程数,1表示QPS
"count": 1, // 单机阀值
"strategy": 0, // 流控模式,0表示直接,1表示关联,2表示链路
"controlBehavior": 0, // 流控效果,0表示快速失败,1表示Warm UP,2表示排队等待
"clusterMode": false // 是否集群
}
]
// 192.168.1.25来源的请求 qps 为 1(每秒的查询数1次)
降级规则
[
{
"resource": "/swagger-ui.html", // 资源名称
"count": 3, // 最大Rt(ms)(平均响应时间 ) 比例阀值 异常数
"grade": 0, // 熔断策略 0 RT 1 异常比例 2 异常数
"timeWindow": 10, // 熔断时长S 10秒后回复
}
]
// 一秒内进来n个请求,平均响应时长均超过rt。对此接口进行熔断降级。10秒后恢复
热点规则
黑白名单
[
{
"resource": "/swagger-ui.html", // 资源名称
"limitApp": "192.168.1.25,127.0.0.1", // 对应的黑名单/白名单,不同 origin 用 , 分隔,如 appA,appB
"strategy": 1 // 限流模式 0 白名单 1 黑名单
}
]
// 192.168.1.25 和 127.0.0.1 的来源禁止请求
关于 limitApp 及自定义异常处理
WebMVC 设置来源 limitApp
开发包
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
重写RequestOriginParser 设置来源
import com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser;
import com.cloud.common.web.utils.IpUtils;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
* @author 刘志强
* @date 2020/9/24 16:51
*/
@Component
public class MyRequestOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest request) {
// 从header中获取名为 origin 的参数并返回
String ip = IpUtils.getIpAddr(request);
return ip;
}
}
覆盖 DefaultUrlBlockHandler 异常处理器处理器
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.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.cloud.common.core.result.AjaxResult;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyUrlBlockHandler implements UrlBlockHandler {
public MyUrlBlockHandler() {
}
@Override
public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws IOException {
if (AuthorityException.isBlockException(ex)) {
writer(request,response,AjaxResult.error("被sentinel拦截"));
}
}
public static boolean writer(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AjaxResult ajaxResult) {
PrintWriter writer = null;
String originHeader = httpServletRequest.getHeader("Origin");
httpServletResponse.setHeader("Access-Control-Allow-Origin", originHeader);
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.addHeader("Access-Control-Allow-Credentials", "true");
httpServletResponse.addHeader("Vary", "Origin");
httpServletResponse.setContentType("application/json; charset=utf-8");
try {
JSONObject jsonObject = (JSONObject) JSON.toJSON(ajaxResult);
writer = httpServletResponse.getWriter();
writer.append(jsonObject.toString());
} catch (IOException e) {
System.out.println("response error" + e);
} finally {
if (writer != null) {
writer.close();
}
}
return false;
}
}
将异常处理器添加至WebCallbackManager中
import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SentinelConfig {
@Bean
public MyUrlBlockHandler webCallbackManager() {
MyUrlBlockHandler myUrlBlockHandler = new MyUrlBlockHandler();
WebCallbackManager.setUrlBlockHandler(myUrlBlockHandler);
return myUrlBlockHandler;
}
}
WebFlux 设置来源 limitApp (Gateway 也可以这样写)
开发包
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
取消SentinelWebFluxFilter 及异常处理器 自定过滤器,重写SentinelWebFluxFilter过滤器 及异常处理器
取消默认的过滤器
spring:
cloud:
sentinel:
filter:
enabled: false
重写SentinelWebFluxFilter过滤器
import com.alibaba.csp.sentinel.adapter.spring.webflux.SentinelWebFluxFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
@Configuration
public class SentinelConfig {
@Bean
@Order(-2)
public WebFilter sentinelWebFluxFilter()
{
return new MySentinelWebFluxFilter();
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public WebExceptionHandler sentinelBlockExceptionHandler() {
return new MySentinelBlockExceptionHandler();
}
}
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.adapter.reactor.ContextConfig;
import com.alibaba.csp.sentinel.adapter.reactor.EntryConfig;
import com.alibaba.csp.sentinel.adapter.reactor.SentinelReactorTransformer;
import com.alibaba.csp.sentinel.adapter.spring.webflux.SentinelWebFluxFilter;
import com.alibaba.csp.sentinel.adapter.spring.webflux.callback.WebFluxCallbackManager;
import java.util.Optional;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
public class MySentinelWebFluxFilter extends SentinelWebFluxFilter {
private static final String EMPTY_ORIGIN = "";
public MySentinelWebFluxFilter() {
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange).transform(this.buildSentinelTransformer(exchange));
}
private SentinelReactorTransformer<Void> buildSentinelTransformer(ServerWebExchange exchange) {
ServerHttpRequest serverHttpRequest = exchange.getRequest();
String path = exchange.getRequest().getPath().value();
String finalPath = (String)Optional.ofNullable(WebFluxCallbackManager.getUrlCleaner()).map((f) -> {
return (String)f.apply(exchange, path);
}).orElse(path);
String origin = getIpAddress(serverHttpRequest);
return new SentinelReactorTransformer(new EntryConfig(finalPath, EntryType.IN, new ContextConfig(finalPath, origin)));
}
public static String getIpAddress(ServerHttpRequest request) {
HttpHeaders headers = request.getHeaders();
String ip = headers.getFirst("x-forwarded-for");
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
if (ip.indexOf(",") != -1) {
ip = ip.split(",")[0];
}
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddress().getAddress().getHostAddress();
}
return ip;
}
}
自定义异常处理器
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import java.nio.charset.StandardCharsets;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import reactor.core.publisher.Mono;
/**
* @author admin
*/
public class MySentinelBlockExceptionHandler implements WebExceptionHandler {
private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange) {
ServerHttpResponse serverHttpResponse = exchange.getResponse();
serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
byte[] datas = "{\"status\":429,\"message\":\"请求超过最大数,请稍后再试\"}".getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(datas);
return serverHttpResponse.writeWith(Mono.just(buffer));
}
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
if (exchange.getResponse().isCommitted()) {
return Mono.error(ex);
}
if (!BlockException.isBlockException(ex)) {
return Mono.error(ex);
}
return handleBlockedRequest(exchange, ex).flatMap(response -> writeResponse(response, exchange));
}
private Mono<ServerResponse> handleBlockedRequest(ServerWebExchange exchange, Throwable throwable) {
return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable);
}
}