1、前言
在我们开发微服务系统时,Gateway是必不可少的一个组件,我们可以通过Gateway来转发用户进行的请求,这样就可以隐藏具体系统的信息。
在微服务系统开发中,常常是以团队的方式进行开发的,所以就需要在Gateway中进行路由的配置,Gateway为开发者提供了三种路由配置的方式:
- 自定义
RouteLocator
对象,通过硬编码的方式实现路由配置。 - 通过
YAML
文件配置routes
属性。 - 实现
ApplicationEventPublisherAware
接口结合Nacos,监听Naocs中的路由文件,完成动态路由配置。
其中第二种和第三种都可以结合Nacos实现动态路由,主要看开发者怎么选择。第二种就是结合Nacos的配置中心,将routes参数下的数据放入到nacos中,我这里使用第三种进行展示,用JSON的格式配置路由信息。
2、前置知识
我们知道Gateway有两个重要的组件:Predicates、Filters。
2.1、Predicates
Predicates的主要功能是进行请求的拦截和筛选。当一个请求来到Gateway时,Predicates会根据配置的规则对请求进行评估,如果请求满足某种条件,那么这个请求就会被分发到相应的路由。
2.1.1、Path
Path表示请求当前服务的路径的匹配格式。
YAML格式:
yaml
spring:
cloud:
gateway:
routes:
- id: path_route
uri: http://127.0.0.1:8080
predicates:
- Path=/path/**,/path-server/**
JSON格式:
json
{
"id": "path_route",
"uri": "http://127.0.0.1:8082",
"predicates":[
{
"name": "Path",
"args": {
"_genkey_0": "/path/**",
"_genkey_1": "/path-server/**"
}
}
]
}
_genkey_0、_genkey_1可以自定义,没有限制,表示的是Path这个参数下的值。
2.1.2、Method
进入当前服务的请求方式。
YAML格式:
yaml
spring:
cloud:
gateway:
routes:
- id: path_route
uri: http://127.0.0.1:8082
predicates:
- Method=GET,POST
JSON格式:
json
{
"id": "path_route",
"uri": "http://127.0.0.1:8082",
"predicates":[
{
"name": "Method",
"args": {
"_genkey_0": "GET",
"_genkey_1": "POST"
}
]
}
2.1.3、After
路由在指定时间之后生效。
YAML格式:
yaml
spring:
cloud:
gateway:
routes:
- id: path_route
uri: http://127.0.0.1:8082
predicates:
- After=2021-08-16T07:36:00.000+08:00[Asia/Shanghai]
JSON格式:
yaml
{
"id": "path_route",
"uri": "http://127.0.0.1:8082",
"predicates":[
{
"name": "After",
"args": {
"datetime": "2023-09-23T07:36:00.000+08:00[Asia/Shanghai]"
}
}
]
}
有关时间的参数,只能为datetime,此处可在源码AfterRouterPredicateFactory类中找到
2.1.4、Before
路由在指定时间之前有效。
YAML格式:
yaml
spring:
cloud:
gateway:
routes:
- id: path_route
uri: http://127.0.0.1:8082
predicates:
- Before=2023-09-23T07:36:00.000+08:00[Asia/Shanghai]
JSON格式:
json
{
"id": "path_route",
"uri": "http://127.0.0.1:8082",
"predicates":[
{
"name": "Before",
"args": {
"datetime": "2023-09-23T07:36:00.000+08:00[Asia/Shanghai]"
}
}
]
}
2.1.5、Between
路由在指定时间之间有效。
YAML格式:
yaml
spring:
cloud:
gateway:
routes:
- id: path_route
uri: http://127.0.0.1:8082
predicates:
- Between=2023-09-23T07:36:00.000+08:00[Asia/Shanghai], 2023-09-23T08:15:00.000+08:00[Asia/Shanghai]
JSON格式:
yaml
{
"id": "path_route",
"uri": "http://127.0.0.1:8082",
"predicates":[
{
"name": "Between",
"args": {
"datetime1": "2023-09-23T07:36:00.000+08:00[Asia/Shanghai]",
"datetime2": "2023-09-23T08:18:00.000+08:00[Asia/Shanghai]"
}
}
]
}
args对应值的key只能为datetime1和datetime2,此处可在源码BetweenRouterPredicateFactory类中找到。
2.1.6、Cookie
表示cookie中存在指定名称,并且对应的值符合指定正则表达式,才算匹配成功
YAML格式:
yaml
spring:
cloud:
gateway:
routes:
- id: path_route
uri: https://127.0.0.1:8080
predicates:
- Cookie=session, fly
session是cookie的名称,fly是cookie的值。
JSON格式:
json
{
"id": "path_route",
"uri": "http://127.0.0.1:8082",
"predicates":[
{
"name": "Cookie",
"args": {
"name": "session",
"regexp": "fly"
}
}
]
}
regexp表示正则的意思。
2.1.7、Header
表示header中存在指定名称,并且对应的值符合指定正则表达式,才算匹配成功。
YAML格式:
yaml
spring:
cloud:
gateway:
routes:
- id: path_route
uri: https://example.org
predicates:
- Header=X-Request-Id, \d+
JSON格式:
yaml
{
"id": "path_route",
"uri": "http://127.0.0.1:8082",
"predicates":[
{
"name": "Header",
"args": {
"header": "X-Request-Id",
"regexp": "\\d+"
}
}
]
}
2.1.8、Host
表示请求的host要和指定的字符串匹配,并且对应的值符合指定正则表达式,才算匹配成功。
YAML格式:
yaml
spring:
cloud:
gateway:
routes:
- id: path_route
uri: http://127.0.0.1:8082
predicates:
- Host=localhost:8080,localhost:8081
JSON格式:
yaml
{
"id": "path_route",
"uri": "http://127.0.0.1:8082",
"predicates":[
{
"name": "Host",
"args": {
"_genkey_0": "localhost:8080",
"_genkey_0": "localhost:8081"
}
}
]
}
2.1.9、Query
在请求中要带有指定的参数,且该参数的值需等于配置的值或匹配正则表达式,才算匹配成功。
YAML格式:
yaml
spring:
cloud:
gateway:
routes:
- id: path_route
uri: http://127.0.0.1:8082
predicates:
- Query=name, fly
有一个叫做name的参数,值为fly。
JSON格式:
json
{
"id": "path_route",
"uri": "http://127.0.0.1:8082",
"predicates":[
{
"name": "Query",
"args": {
"param": "name",
"regexp": "fly"
}
}
]
}
2.1.10、RemoteAddr
匹配指定来源的请求。
YAML格式:
yaml
spring:
cloud:
gateway:
routes:
- id: path_route
uri: http://127.0.0.1:8082
predicates:
- RemoteAddr=192.168.0.1
JSON格式:
json
{
"id": "remoteaddr_route",
"uri": "http://127.0.0.1:8082",
"predicates":[
{
"name": "RemoteAddr",
"args": {
"_genkey_0": "192.168.0.1"
}
}
]
}
2.1.11、Weight
按照权重将请求分发到不同的位置。
YAML格式:
yaml
spring:
cloud:
gateway:
routes:
- id: path_route
uri: http://127.0.0.1:8082
predicates:
- Weight=group1, 8
JSON格式:
yaml
{
"id": "path_route",
"uri": "http://127.0.0.1:8082",
"predicates":[
{
"name": "Weight",
"args": {
"weight.group": "group1",
"weight.weight": "8"
}
}
]
}
权重这个一般不用咯,因为在微服务中一般有loadblance负载均衡器在,所以请求的分发一般由它来负责。
2.2、Filters
与Predicates相比,Filters的功能更加全面。它不仅可以在请求到达目标之前进行拦截,还可以对响应进行修改和修饰。具体来说,Filters可以用于修改响应报文,增加或修改Header或Cookie,甚至可以修改响应的主体内容。这些功能是Predicates所不具备的。
2.2.1、AddRequestHeader
添加请求头信息。
YAML格式:
yaml
spring:
cloud:
gateway:
filters:
- AddRequestHeader = X-Request-Foo,Bar
添加一个名为X-Request-Foo的请求头参数,值为Bar。
JSON格式:
json
{
"name":"AddRequestHeader",
"args":{
"_genkey_0":"X-Request-Foo",
"_genkey_1":"Bar"
}
}
2.2.2、RewritePath
路径重写。
YAML格式:
yaml
spring:
cloud:
gateway:
filters:
- RewritePath = /path/(?<segment>.*), /$\{segment}
JSON格式:
json
{
"name":"RewritePath",
"args":{
"_genkey_0":"/foo/(?<segment>.*)",
"_genkey_1":"/$\\{segment}"
}
}
2.2.3、AddRequestParameter
添加请求参数。
YAML格式:
yaml
spring:
cloud:
gateway:
filters:
- AddRequestParameter = foo,bar
JSON格式:
json
{
"name":"AddRequestParameter",
"args":{
"_genkey_0":"foo",
"_genkey_1":"bar"
}
}
2.2.4、AddResponseHeader
添加响应参数。
YAML格式:
yaml
spring:
cloud:
gateway:
filters:
- AddResponseHeader = X-Request-Foo,Bar
JSON格式:
json
{
"name":"AddResponseHeader",
"args":{
"_genkey_0":"X-Request-Foo",
"_genkey_1":"Bar"
}
}
2.2.5、PrefixPath
路径前缀增强(添加路径)。
YAML格式:
yaml
spring:
cloud:
gateway:
filters:
- PrefixPath = /mypath
JSON格式:
json
{
"name":"PrefixPath",
"args":{
"_genkey_0":"/mypath"
}
}
2.2.6、StripPrefix
路径前缀删除。
YAML格式:
yaml
spring:
cloud:
gateway:
filters:
- StripPrefix = 2
删除前面两个前缀。
JSON格式:
json
{
"name":"StripPrefix",
"args":{
"_genkey_0":"2"
}
}
2.2.7、RedirectTo
重定向。要指定响应码和重定向路径。
YAML格式:
yaml
spring:
cloud:
gateway:
filters:
- RedirectTo = 302,https://www.baidu.com
JSON格式:
json
{
"name":"RedirectTo",
"args":{
"_genkey_0":"302",
"_genkey_1":"https://www.baidu.com"
}
}
2.2.8、RemoveRequestHeader
删除请求头属性。
YAML 格式:
yaml
spring:
cloud:
gateway:
filters:
- RemoveRequestHeader = X-Request-Foo
JSON格式:
json
{
"name":"RemoveRequestHeader",
"args":{
"_genkey_0":"X-Request-Foo"
}
}
2.2.9、RemoveResponseHeader
删除响应头属性。
YAML格式:
yaml
spring:
cloud:
gateway:
filters:
- RemoveResponseHeader = X-Request-Foo
JSON格式:
json
{
"name":"RemoveResponseHeader",
"args":{
"_genkey_0":"X-Request-Foo"
}
}
3、动态路由的实现
动态路由的原理:将路由信息存放在某个中间层,然后在项目启动后对这个文件进行加载和监听,这个中间层可以是数据库或者文件,重要的是能在这个文件被修改后监听到这个文件的变化并重新加载。
这里的中间层使用的是nacos的配置心中,Gateway整合了Naocs配置中心后,可以很好的监听指定的配置文件。
3.1、Gateway中的YAML文件
yaml
server:
port: 8000
nacos_server: 127.0.0.1:8848
spring:
application:
name: gateway-server
cloud:
nacos:
discovery:
server-addr: ${nacos_server}
config:
file-extension: yaml
server-addr: ${nacos_server}
因为在Gateway中整合了Nacos配置中心,所以默认会监听gateway-server.yaml或者gateway-server这两个文件。在项目启动的时候可以看到的。故此,我就想gateway的基本配置放在配置中心中了。
配置如下:
yaml
spring:
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
sentinel:
transport:
dashboard: http://localhost:8080
datasource:
flow-control:
nacos:
server-addr: ${nacos_server}
data-id: gateway_flux-control_config.json
rule-type: gw_flow
degrade-control:
nacos:
data-id: gateway_degrade-control_config.json
server-addr: ${nacos_server}
rule-type: degrade
eager: true
## 自定义的配置文件信息
dynamic-routes:
nacos-addr: ${nacos_server}
data-id: gateway-dynamic-routes.json
group: DEFAULT_GROUP
3.1、路由文件
我们需要结合前置知识来编写路由规则,并将文件存放在Nacos配置中心中。
yaml
[
{
"id": "feign-test",
"uri": "http://localhost:8082",
"predicates":[{
"name": "Path",
"args":{
"pattern": "/feign/**"
}
}],
"filters":[{
"name": "StripPrefix",
"args":{
"value1": 1
}
}]
},
## 下面还可以写其他的路由配置。
]
3.2、实现ApplicationEventPublisherAware接口
我们可以使用ApplicationEventPublisherAware的实现类来监听路由文件的变化并重新加载。
java
@Component
public class NacosDynamicRouteService implements ApplicationEventPublisherAware, ApplicationRunner {
@Value("${dynamic-routes.data-id}")
private String dataId;
@Value("${dynamic-routes.group}")
private String group;
@Value("${dynamic-routes.nacos-addr}")
private String serverAddr;
private final RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher applicationEventPublisher;
private static final List<String> ROUTE_LIST = new ArrayList<>();
public NacosDynamicRouteService(RouteDefinitionWriter routeDefinitionWriter){
this.routeDefinitionWriter = routeDefinitionWriter;
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
@Override
public void run(ApplicationArguments args) {
try {
// 获取nacosService对象
ConfigService configService = NacosFactory.createConfigService(serverAddr);
// 获取指定的路由配置文件
String config = configService.getConfig(dataId, group, 5000);
// 添加对路由文件的监听器
configService.addListener(dataId, group, new NacosDynamicRouteListener(config));
} catch (NacosException e) {
e.printStackTrace();
}
}
// 清除路由信息
private void clearRoute() {
for (String id : ROUTE_LIST) {
this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
}
ROUTE_LIST.clear();
}
// 路由文件监听器
private class NacosDynamicRouteListener extends AbstractListener{
public NacosDynamicRouteListener(String config){
receiveConfigInfo(config);
}
@Override
public void receiveConfigInfo(String s) { // S就是路由文件
clearRoute();
List<RouteDefinition> gatewayRouteDefinitions = JSONObject.parseArray(s, RouteDefinition.class);
for (RouteDefinition routeDefinition : gatewayRouteDefinitions) {
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
ROUTE_LIST.add(routeDefinition.getId());
}
applicationEventPublisher.publishEvent(new RefreshRoutesEvent(routeDefinitionWriter));
}
}
}