一、关于
Gateway、Nacos以及Sentinel相关的介绍以及使用参考之前的文章,本文主要讲这三者的集成使用。
本文代码仓库地址:https://github.com/lazyrabb1t/rabb-springcloud-demo
二、Gateway集成Nacos实现动态路由
2.1 创建工程
2.2 添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.54</version>
<scope>compile</scope>
</dependency>
2.3 配置
bootstrap.yml添加nacos配置:
spring:
cloud:
# 设置Nacos服务端配置
nacos:
server-addr: localhost:8848
username: nacos
password: nacos
config:
group: DEFAULT_GROUP
route-data-id: rabb-gateway-routes
application.yml添加gateway配置:
server:
port: 20400
spring:
application:
name: rabb-gateway-alibaba
cloud:
gateway:
routes:
- id: test-route
uri: http://lazyrabbit.xyz
predicates:
- Path=/rabb/**
filters:
- RewritePath=/rabb/?(?<segment>.*), /$\{segment}
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能
lower-case-service-id: true #使用小写服务名,默认是大写
添加配置spring.cloud.gateway.discovery.locator.enabled=true
后,Spring Cloud Gateway可以使用服务发现客户端接口DiscoveryClient,从服务注意中心获取服务注册信息,然后配置相应的路由。
默认情况下,通过服务发现客户端DiscoveryClient自动配置的路由信息中,只包括一个默认的Predicate和Filter:
- 默认的Predicate是一个Path Predicate,模式是
/serviceId/**
,serviceId就是从服务发现客户端中获取的服务ID。 - 默认的Filter是一个重写路径过滤器,它的正则表达式为:
/serviceId/(?<remaining>.*)
,它的作用是将请求路径中的serviceId去掉,变为/(?<remaining>.*)
另外也支持自定义的Predicate和Filters。
2.5 测试
启动nacos、rabb-gateway-alibaba模块(本模块)以及一个服务提供者模块rabb-nacos-provider(nacos篇里的模块)。
可以通过网关加服务名直接访问服务提供者的接口,如:http://localhost:20400/rabb-nacos-provider/。
2.6 实现动态路由
利用Spring的事件机制,动态获取配置并发布路由更新事件:
@Component
@Slf4j
public class NacosDynamicRouteService implements ApplicationEventPublisherAware {
@Value("${spring.cloud.nacos.config.route-data-id}")
private String dataId;
@Value("${spring.cloud.nacos.config.group}")
private String group;
@Value("${spring.cloud.nacos.server-addr}")
private String serverAddr;
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher applicationEventPublisher;
private static final List<String> ROUTE_LIST = new ArrayList<>();
@PostConstruct
public void dynamicRouteByNacosListener() {
try {
ConfigService configService = NacosFactory.createConfigService(serverAddr);
String config = configService.getConfig(dataId, group, 5000);
this.refreshRoute(config);
configService.addListener(dataId, group, new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
clearRoute();
refreshRoute(config);
}
@Override
public Executor getExecutor() {
return null;
}
});
} catch (NacosException e) {
log.error("路由监听配置异常:{}", e.getMessage(), e);
}
}
private void refreshRoute(String routeStr) {
try {
List<RouteDefinition> gatewayRouteDefinitions = JSONObject.parseArray(routeStr, RouteDefinition.class);
for (RouteDefinition routeDefinition : gatewayRouteDefinitions) {
addRoute(routeDefinition);
}
publish();
log.info("路由更新:{}", routeStr);
} catch (Exception e) {
log.error("路由更新异常:{}", e.getMessage(), e);
}
}
private void clearRoute() {
for (String id : ROUTE_LIST) {
this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
}
ROUTE_LIST.clear();
}
private void addRoute(RouteDefinition definition) {
try {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
ROUTE_LIST.add(definition.getId());
} catch (Exception e) {
log.error("路由更新异常:{}", e.getMessage(), e);
}
}
private void publish() {
this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this.routeDefinitionWriter));
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
}
在nacos中添加名为rabb-gateway-routes
的配置,值如下:
[
{
"id": "rabb-nacos-provider",
"order": 0,
"predicates": [
{
"args": {
"pattern": "/rabb-nacos-provider/**"
},
"name": "Path"
}
],
"filters": [
{
"name": "RewritePath",
"args": {
"regexp": "/rabb-nacos-provider/?(?<segment>.*)",
"replacement": "/${segment}"
}
}
],
"uri": "lb://rabb-nacos-provider"
},
{
"filters": [
{
"name": "RewritePath",
"args": {
"regexp": "/rabb/?(?<segment>.*)",
"replacement": "/${segment}"
}
}
],
"id": "rabb_route",
"order": 1,
"predicates": [
{
"args": {
"pattern": "/rabb/**"
},
"name": "Path"
}
],
"uri": "http://www.lazyrabbit.xyz"
}
]
同时修改配置spring.cloud.gateway.discovery.locator.enabled=false
后,关闭自动映射服务的路由。
2.7 测试动态路由
修改nacos中路由配置,可以看到网关的日志中有更新路由的输出,同时更改后的路由生效。
三、Gateway集成Sentinel实现流量控制
从 1.6.0 版本开始,Sentinel 提供了 Spring Cloud Gateway 的适配模块,可以提供两种资源维度的限流:
- route 维度:即在 Spring 配置文件中配置的路由条目,资源名为对应的 routeId
- 自定义 API 维度:用户可以利用 Sentinel 提供的 API 来自定义一些 API 分组
3.1 添加依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- sentinel 集成gateway -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
3.2 配置
spring:
cloud:
sentinel:
eager: true
transport:
dashboard: localhost:8880
3.3 代码配置异常处理器以及API分组和规则
@Configuration
public class SentinelConfig {
@PostConstruct
public void doInit() {
// 代码方式添加api分组以及规则
initCustomizedApis();
initGatewayRules();
// 自定义异常处理
initBlockHandler();
}
/**
* 自定义限流异常处理器
*/
private void initBlockHandler() {
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(ResponseUtils.failure("限流")));
}
};
// 加载自定义限流异常处理器
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
/**
* API分组,对不组可以进行不同限流规则
*/
private void initCustomizedApis() {
Set<ApiDefinition> definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("some_customized_api")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/rabb-nacos-provider/openfeign/user/**"));
}});
ApiDefinition api2 = new ApiDefinition("another_customized_api")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/rabb-nacos-provider/openfeign/test/**"));
}});
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);
}
}
3.4 启动配置
添加启动参数-Dcsp.sentinel.app.type=1
启动该模块,告诉sentinel此模块为一个网关工程。
或者添加以下代码:
@SpringBootApplication
public class GatewayNacosSentinelApplication {
public static void main(String[] args) {
System.setProperty("csp.sentinel.app.type","1");
SpringApplication.run(GatewayNacosSentinelApplication.class, args);
}
}
3.5 测试
重启sentinel以及本模块,进行测试
四、Sentinel集成Naocs动态数据源
4.1 添加依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
4.2 配置
添加sentinel.datasource
配置:
spring:
application:
name: rabb-sentinel
cloud:
nacos:
server-addr: localhost:8848
username: nacos
password: nacos
sentinel:
transport:
dashboard: localhost:8880
port: 8719
datasource:
ds:
nacos:
server-addr: 127.0.0.1:8848
data-id: rabb-gateway-sentinel-rule
group-id: DEFAULT_GROUP
username: nacos
password: nacos
ruleType: gw-flow
eager: true
ds 是 ReadableDataSource 的名字,可随意编写。后面的 file ,zk ,nacos , apollo 就是对应具体的数据源,它们后面的配置就是这些具体数据源各自的配置。注意数据源的依赖要单独引入(比如 sentinel-datasource-nacos)。
每种数据源都有两个共同的配置项: data-type、 converter-class 以及 rule-type。
- data-type 配置项表示 Converter 类型,Spring Cloud Alibaba Sentinel 默认提供两种内置的值,分别是 json 和 xml (不填默认是json)。
- 如果不想使用内置的 json 或 xml 这两种 Converter,可以填写 custom 表示自定义 Converter,然后再配置 converter-class 配置项,该配置项需要写类的全路径名(比如 spring.cloud.sentinel.datasource.ds1.file.converter-class=com.alibaba.cloud.examples.JsonFlowRuleListConverter)。
- rule-type 配置表示该数据源中的规则属于哪种类型的规则(flow, degrade, authority, system, param-flow, gw-flow, gw-api-group)。注意网关流控规则 (GatewayFlowRule) 对应 gw-flow。
4.3 在nacos添加流控规则
在nacos中添加一个名称rabb-gateway-sentinel-rule
的配置,其值如下:
[
{
"resource": "rabb-nacos-provider",
"limitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
对应属性:
- resource:资源名,即限流规则的作用对象
- limitApp:流控针对的调用来源,若为 default 则不区分调用来源
- grade:限流阈值类型(QPS 或并发线程数);0代表根据并发数量来限流,1代表根据QPS来进行流量控制
- count:限流阈值
- strategy:调用关系限流策略
- controlBehavior:流量控制效果(直接拒绝、Warm Up、匀速排队)
- clusterMode:是否为集群模式
4.4 测试
重新启动各个工程,登录sentinel控制台,可以在流控规则页面中发现4.3中的配置规则。
修改nacos中配置,sentinel能及时同步过来。sentinel中的配置暂时无法同步到nacos中。