Nacos实现Spring Cloud Gateway的动态路由

前言

方案参考:https://www.cnblogs.com/zlt2000/p/11712943.html

网关的核心概念就是路由配置和路由规则,而作为所有请求流量的入口,在实际生产环境中为了保证高可靠和高可用,是尽量要避免重启的,所以实现动态路由是非常有必要的;本文主要介绍Spring Cloud Gateway实现的思路,并且以Nacos为数据源来讲解

实现要点

要实现动态路由只需关注下面4个点

  1. 网关启动时,动态路由的数据怎样加载进来
  2. 静态路由动态路由以哪个为准,ps:静态路由指的是配置文件里写死的路由配置
  3. 监听动态路由的数据源变化
  4. 数据有变化时怎样通知gateway刷新路由

具体实现

Spring Cloud Gateway中加载路由信息分别由以下几个类负责

  • PropertiesRouteDefinitionLocator:从配置文件中读取路由信息(如YML、Properties等)

  • RouteDefinitionRepository:从存储器中读取路由信息(如内存、配置中心、Redis、MySQL等)

  • DiscoveryClientRouteDefinitionLocator:从注册中心中读取路由信息(如Nacos、Eurka、Zookeeper等)

    我们可以通过自定义RouteDefinitionRepository的实现类来实现动态路由的目的

添加依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<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-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

静态路由示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spring:
cloud:
gateway:
discovery:
locator:
lowerCaseServiceId: true
enabled: true
routes:
- id: auth
uri: lb://uaa-server
predicates:
- Path=/api-uaa/**
filters:
- StripPrefix=1
- PreserveHostHeader

实现动态路由的数据加载

创建一个NacosRouteDefinitionRepository实现类

  • 重写getRouteDefinitions方法实现路由信息的读取
  • 配置Nacos监听器,监听路由配置信息的变化
  • 路由变化只需要往ApplicationEventPublisher推送一个RefreshRoutesEvent事件即刻,Gateway会自动监听该事件并调用getRouteDefinitions方法更新路由信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.context.ApplicationEventPublisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;

/**
* nacos路由数据源
*/
@Slf4j
public class NacosRouteDefinitionRepository implements RouteDefinitionRepository {
private static final String SCG_DATA_ID = "scg-routes";
private static final String SCG_GROUP_ID = "SCG_GATEWAY";

private ApplicationEventPublisher publisher;

private NacosConfigProperties nacosConfigProperties;

public NacosRouteDefinitionRepository(ApplicationEventPublisher publisher, NacosConfigProperties nacosConfigProperties) {
this.publisher = publisher;
this.nacosConfigProperties = nacosConfigProperties;
addListener();
}

@Override
public Flux<RouteDefinition> getRouteDefinitions() {
try {
String content = nacosConfigProperties.configServiceInstance().getConfig(SCG_DATA_ID, SCG_GROUP_ID,5000);
List<RouteDefinition> routeDefinitions = getListByStr(content);
return Flux.fromIterable(routeDefinitions);
} catch (NacosException e) {
log.error("getRouteDefinitions by nacos error", e);
}
return Flux.fromIterable(CollUtil.newArrayList());
}

/**
* 添加Nacos监听
*/
private void addListener() {
try {
nacosConfigProperties.configServiceInstance().addListener(SCG_DATA_ID, SCG_GROUP_ID, new Listener() {
@Override
public Executor getExecutor() {
return null;
}

@Override
public void receiveConfigInfo(String configInfo) {
publisher.publishEvent(new RefreshRoutesEvent(this));
}
});
} catch (NacosException e) {
log.error("nacos-addListener-error", e);
}
}

@Override
public Mono<Void> save(Mono<RouteDefinition> route) {
return null;
}

@Override
public Mono<Void> delete(Mono<String> routeId) {
return null;
}

private List<RouteDefinition> getListByStr(String content) {
if (StrUtil.isNotEmpty(content)) {
return JSONObject.parseArray(content, RouteDefinition.class);
}
return new ArrayList<>(0);
}
}

创建配置类

创建配置类DynamicRouteConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.central.gateway.route.NacosRouteDefinitionRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* 动态路由配置
*/
@Configuration
@ConditionalOnProperty(prefix = "appblog.gateway.dynamicRoute", name = "enabled", havingValue = "true")
public class DynamicRouteConfig {
@Autowired
private ApplicationEventPublisher publisher;

/**
* Nacos实现方式
*/
@Configuration
@ConditionalOnProperty(prefix = "appblog.gateway.dynamicRoute", name = "dataType", havingValue = "nacos", matchIfMissing = true)
public class NacosDynRoute {
@Autowired
private NacosConfigProperties nacosConfigProperties;

@Bean
public NacosRouteDefinitionRepository nacosRouteDefinitionRepository() {
return new NacosRouteDefinitionRepository(publisher, nacosConfigProperties);
}
}
}

添加Nacos路由配置

新增配置项:

  • Data Idscg-routes
  • GroupSCG_GATEWAY

添加两条路由数据,配置内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[
{
"id": "appblog",
"predicates": [{
"name": "Path",
"args": {
"pattern": "/appblog/**"
}
}],
"uri": "http://www.appblog.cn/",
"filters": []
},
{
"id": "github",
"predicates": [{
"name": "Path",
"args": {
"pattern": "/github/**"
}
}],
"uri": "https://github.com/",
"filters": []
}
]

测试

启动网关通过/actuator/gateway/routes端点查看当前路由信息

可以看到Nacos里配置的两条路由信息

Powered by AppBlog.CN     浙ICP备14037229号

Copyright © 2012 - 2021 APP开发技术博客 All Rights Reserved.

访客数 : | 访问量 :