spring-cloud-gateway是大家比较熟悉的网关了,不仅有路由,还有限流功能等。不过不能支持匀速限流,所以集成阿里sentinel来实现匀速限流功能,即超过qps请求进行排队,匀速转发到下游。
一、搭建spring-cloud-gateway环境
1.新建一个springboot项目,并引入相关的dependency。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
2.配置bootstrap.yml文件
server:
port: 8889
spring:
application:
name: app-gateway
profiles:
active: dev
cloud:
gateway:
routes:
- id: appPush-service
uri: http://localhost:8081/
predicates:
- Path=/appPush/**
此时启动好下游服务和网关服务,访问http://localhost:8889/appPush/test,就会路由到 http://localhost:8081/appPush/test,到此简单的spring-cloud-gateway就搭建完毕了。
二、集成阿里sentinel
1.引入相关的dependency。
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
<version>1.6.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>0.9.1.BUILD-SNAPSHOT</version>
</dependency>
2.编写异常处理类
package last.soul.gateway.handler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.util.function.Supplier;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class JsonSentinelGatewayBlockExceptionHandler implements WebExceptionHandler {
private List<ViewResolver> viewResolvers;
private List<HttpMessageWriter<?>> messageWriters;
private final Supplier<ServerResponse.Context> contextSupplier = () -> {
return new ServerResponse.Context() {
@Override
public List<HttpMessageWriter<?>> messageWriters() {
return JsonSentinelGatewayBlockExceptionHandler.this.messageWriters;
}
@Override
public List<ViewResolver> viewResolvers() {
return JsonSentinelGatewayBlockExceptionHandler.this.viewResolvers;
}
};
};
public JsonSentinelGatewayBlockExceptionHandler(List<ViewResolver> viewResolvers, ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolvers;
this.messageWriters = serverCodecConfigurer.getWriters();
}
private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange) {
ServerHttpResponse serverHttpResponse = exchange.getResponse();
serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
byte[] datas = "{\"code\":403,\"msg\":\"限流!!!\"}".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);
} else {
return !BlockException.isBlockException(ex) ? Mono.error(ex) : this.handleBlockedRequest(exchange, ex).flatMap((response) -> {
return this.writeResponse(response, exchange);
});
}
}
private Mono<ServerResponse> handleBlockedRequest(ServerWebExchange exchange, Throwable throwable) {
return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable);
}
}
3.配置类
package last.soul.gateway.config;
import last.soul.gateway.handler.JsonSentinelGatewayBlockExceptionHandler;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;
import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Configuration
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
/**
* 配置SentinelGatewayBlockExceptionHandler,限流后异常处理
*
* @return
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public JsonSentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new JsonSentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
/**
* 配置SentinelGatewayFilter
*
* @return
*/
@Bean
@Order(-1)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
@PostConstruct
public void doInit() {
initCustomizedApis();
initGatewayRules();
}
/**
* API分组,对不组可以进行不同限流规则
*/
private void initCustomizedApis() {
Set<ApiDefinition> definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("appPush-service")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/appPush/**")
);
}});
ApiDefinition api2 = new ApiDefinition("appPush-service2")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/appPush2/**")
);
}});
definitions.add(api1);
definitions.add(api2);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
/**
* 配置限流规则
*/
private void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(new GatewayFlowRule("appPush-service")
.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)//0 直接拒绝 ,1 Warm Up, 2 匀速排队
.setGrade(RuleConstant.FLOW_GRADE_QPS)//0 基于线程数,1 基于QPS
.setMaxQueueingTimeoutMs(5000)//和排队数量、单个任务处理时间成正比。可以设置大点防止丢弃请求。
.setCount(10)//qps限制为10
.setIntervalSec(1)//时间窗口为1s
);
GatewayRuleManager.loadRules(rules);
}
}
在initCustomizedApis方法中,将api分为appPush-service和appPush-service2两组,在initGatewayRules方法中对appPush-service组实行了匀速的策略。
此时用jemeter模拟100个请求,会发现第一次通过10个请求,剩下的90个每秒执行10次,一共大约10完成。
三、部署sentinel控制台并关联到网关
通过上面两步基本也完成了匀速的功能,如果想要实时监控并修改相关参数可以集成控制台,步骤如下:
1.部署控制台有两种方式,一种是下载源代码,编译运行。另外一种是下载相关jar包,通过命令运行,推荐这种。命令如下:
java -Dserver.port=9999 -Dcsp.sentinel.dashboard.server=localhost:9999 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.7.2.jar
控制台端口号9999,-Dcsp.sentinel.dashboard.server=localhost:9999表示把控制台本身加到监控中,该参数可以省略。
2.并关联到网关
有两种方式,方式一,在yml文件中cloud节点,gateway平级增加
sentinel:
eager: true
transport:
dashboard: localhost:9999
port: 9999
方式二,在app的启动参数中(Vm options)增加
-Dcsp.sentinel.app.type=1是告诉控制台,我是网关类型的APP
-Dcsp.sentinel.dashboard.server=localhost:9999 -Dproject.name=app-gateway
-Dcsp.sentinel.app.type=1是告诉控制台,我是网关类型的APP。
-Dcsp.sentinel.dashboard.server=localhost:9999是控制台地址。
无论哪种方式都要在网关中添加控制台dependency
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>1.6.3</version>
</dependency
3.访问控制台,并登录。地址为:http://localhost:9999/
默认的用户名密码:sentinel/sentinel
4.用jemeter模拟访问网关,并刷新控制台(因为实现监控不访问没有曲线,访问结束曲线也会消失),就可以看到qps的曲线图。
5.可以实时修改限流相关参数,会实现生效,但是网关重启后会重置为网关里代码写的值。
6.第5步中修改参数功能有时会有bug,时好时坏,可能后续版本会有改善。
四、其它
1.本文没有详细介绍gateway,sentinel知识,大家可以查阅相关文档,本文重点介绍二者如何集成。
2.规则在代码里,并且不能实时更新,使用控制台修改(上面说过不太好用)可以解决。即便实时更新了,gateway项目一重启,参数会重置。但是接入配置中心appollo或者nacos(这个对你项目springboot版本有要求,不能太低),能一次解决不能实时更新和参数会重置两个问题。至于如何集成相对简单,本文不详细介绍。
3.网关集群之后下游的qps是多少?比如两台网关集群,配置的qps都是10,那么下游接收到的请求是多少?答案是相加,20。所以建议每台网关配置的qps数应该是下游能接受的qps除以网关集群个数。
那么能不能让网关集群,让整个集群的qps数设置为下游的qps数呢,答案是可以,关于spring-cloud-gateway集成比较复杂,本文不做介绍。
至于是做集群,还是不做集群把每个网关设置成qps除以集群数,看实际情况权衡吧。
4.本文部分是参考网上好几个文章拼凑而成,就不一一列举原文了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。