1、前言

在我们开发微服务系统时,Gateway是必不可少的一个组件,我们可以通过Gateway来转发用户进行的请求,这样就可以隐藏具体系统的信息。
在微服务系统开发中,常常是以团队的方式进行开发的,所以就需要在Gateway中进行路由的配置,Gateway为开发者提供了三种路由配置的方式:

  1. 自定义RouteLocator 对象,通过硬编码的方式实现路由配置。
  2. 通过YAML文件配置routes属性。
  3. 实现ApplicationEventPublisherAware接口结合Nacos,监听Naocs中的路由文件,完成动态路由配置。

其中第二种和第三种都可以结合Nacos实现动态路由,主要看开发者怎么选择。第二种就是结合Nacos的配置中心,将routes参数下的数据放入到nacos中,我这里使用第三种进行展示,用JSON的格式配置路由信息。

2、前置知识

我们知道Gateway有两个重要的组件:PredicatesFilters

2.1、Predicates

Predicates的主要功能是进行请求的拦截和筛选。当一个请求来到Gateway时,Predicates会根据配置的规则对请求进行评估,如果请求满足某种条件,那么这个请求就会被分发到相应的路由。

2.1.1、Path

Path表示请求当前服务的路径的匹配格式。

YAML格式:

spring:  
  cloud:  
    gateway:  
      routes:  
      - id: path_route  
        uri: http://127.0.0.1:8080  
        predicates:  
        - Path=/path/**,/path-server/**

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格式:

spring:  
  cloud:  
    gateway:  
      routes:  
      - id: path_route  
        uri: http://127.0.0.1:8082  
        predicates:  
        - Method=GET,POST

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格式:

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格式:

{  
    "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格式:

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格式:

{  
    "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格式:

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格式:

{  
    "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格式:

spring:  
  cloud:  
    gateway:  
      routes:  
      - id: path_route
        uri: https://127.0.0.1:8080
        predicates:  
        - Cookie=session, fly

session是cookie的名称,fly是cookie的值。

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格式:

spring:  
  cloud:  
    gateway:  
      routes:  
      - id: path_route
        uri: https://example.org  
        predicates:  
        - Header=X-Request-Id, \d+

JSON格式:

{  
    "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格式:

spring:  
  cloud:  
    gateway:  
      routes:  
      - id: path_route
        uri: http://127.0.0.1:8082  
        predicates:  
        - Host=localhost:8080,localhost:8081

JSON格式:

{  
    "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格式:

spring:  
  cloud:  
    gateway:  
      routes:
      - id: path_route
        uri: http://127.0.0.1:8082
        predicates:  
        - Query=name, fly

有一个叫做name的参数,值为fly。

JSON格式:

{  
    "id": "path_route",  
    "uri": "http://127.0.0.1:8082",  
    "predicates":[  
        {  
            "name": "Query",  
            "args": {   
                "param": "name",  
                "regexp": "fly"  
            }  
        }  
    ]  
}

2.1.10、RemoteAddr

匹配指定来源的请求。

YAML格式:

spring:  
  cloud:  
    gateway:  
      routes:  
      - id: path_route
        uri: http://127.0.0.1:8082
        predicates:  
        - RemoteAddr=192.168.0.1

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格式:

spring:  
  cloud:  
    gateway:  
      routes:  
      - id: path_route
        uri: http://127.0.0.1:8082
        predicates:  
        - Weight=group1, 8

JSON格式:

{  
    "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格式:

spring:
  cloud:
	gateway:
	  filters:
		- AddRequestHeader = X-Request-Foo,Bar

添加一个名为X-Request-Foo的请求头参数,值为Bar。

JSON格式:

{
	"name":"AddRequestHeader",
	"args":{
		"_genkey_0":"X-Request-Foo",
		"_genkey_1":"Bar"
	}
}

2.2.2、RewritePath

路径重写。

YAML格式:

spring:
  cloud:
	gateway:
	  filters:
		- RewritePath = /path/(?<segment>.*), /$\{segment}

JSON格式:

{
	"name":"RewritePath",
	"args":{
		"_genkey_0":"/foo/(?<segment>.*)",
		"_genkey_1":"/$\\{segment}"
		}
}

2.2.3、AddRequestParameter

添加请求参数。

YAML格式:

spring:
  cloud:
	gateway:
	  filters:
		- AddRequestParameter = foo,bar

JSON格式:

{
	"name":"AddRequestParameter",
	"args":{
		"_genkey_0":"foo",
		"_genkey_1":"bar"
	}
}

2.2.4、AddResponseHeader

添加响应参数。

YAML格式:

spring:
  cloud:
	gateway:
	  filters:
		- AddResponseHeader = X-Request-Foo,Bar

JSON格式:

{
	"name":"AddResponseHeader",
	"args":{
		"_genkey_0":"X-Request-Foo",
		"_genkey_1":"Bar"
	}
}

2.2.5、PrefixPath

路径前缀增强(添加路径)。

YAML格式:

spring:
  cloud:
	gateway:
	  filters:
		- PrefixPath = /mypath

JSON格式:

{
	"name":"PrefixPath",
	"args":{
		"_genkey_0":"/mypath"
	}
}

2.2.6、StripPrefix

路径前缀删除。

YAML格式:

spring:
  cloud:
	gateway:
	  filters:
		- StripPrefix = 2

删除前面两个前缀。

JSON格式:

{
	"name":"StripPrefix",
	"args":{
		"_genkey_0":"2"
	}
}

2.2.7、RedirectTo

重定向。要指定响应码和重定向路径。

YAML格式:

spring:
  cloud:
	gateway:
	  filters:
		- RedirectTo = 302,https://www.baidu.com

JSON格式:

{
	"name":"RedirectTo",
	"args":{
		"_genkey_0":"302",
		"_genkey_1":"https://www.baidu.com"
	}
}

2.2.8、RemoveRequestHeader

删除请求头属性。

YAML 格式:

spring:
  cloud:
	gateway:
	  filters:
		- RemoveRequestHeader = X-Request-Foo

JSON格式:

{
	"name":"RemoveRequestHeader",
	"args":{
		"_genkey_0":"X-Request-Foo"
	}
}

2.2.9、RemoveResponseHeader

删除响应头属性。

YAML格式:

spring:
  cloud:
	gateway:
	  filters:
		- RemoveResponseHeader = X-Request-Foo

JSON格式:

{
	"name":"RemoveResponseHeader",
	"args":{
		"_genkey_0":"X-Request-Foo"
	}
}

3、动态路由的实现

动态路由的原理:将路由信息存放在某个中间层,然后在项目启动后对这个文件进行加载和监听,这个中间层可以是数据库或者文件,重要的是能在这个文件被修改后监听到这个文件的变化并重新加载。
这里的中间层使用的是nacos的配置心中,Gateway整合了Naocs配置中心后,可以很好的监听指定的配置文件。

3.1、Gateway中的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的基本配置放在配置中心中了。

配置如下:

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配置中心中。

[
    {
        "id":  "feign-test",
        "uri": "http://localhost:8082",
        "predicates":[{
            "name": "Path",
            "args":{
                "pattern": "/feign/**"
            }
        }],
        "filters":[{
            "name": "StripPrefix",
            "args":{
                "value1": 1
            }
        }]
    },
    ## 下面还可以写其他的路由配置。
]

3.2、实现ApplicationEventPublisherAware接口

我们可以使用ApplicationEventPublisherAware的实现类来监听路由文件的变化并重新加载。

@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));
        }
    }


}
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐