gateway 集成 nacos 实现动态路由,前几篇单独分析gateway网关时,仅介绍了静态路由的原理和使用,今天我们先分析动态路由,然后介绍一下两者的区别和优缺点。
一、动态路由实战分析:
1、构建一个springboot 服务,去掉web相关的jar:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
加上:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
引入相关核心jar
artifactId>common-gateway</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>common-gateway</name>
<description>网关服务</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- springboot web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
</dependencies>
2、项目中yaml配置:
spring:
main:
web-application-type: reactive
application:
name: @project.artifactId@
version: @project.version@
cloud:
nacos:
config:
namespace: common
file-extension: yaml
shared-configs:
- data-id: common-shared-config.yaml
group: common
refresh: true
loggingRoot: logs
3、idea中nacos环境配置:
4、nacos页面查找配置:
5、在 common-gateway-dev.yaml 处点击详情查看:
配置内容:
server:
port: 80000
servlet:
context-path: /gateway-server
loggingRoot: logs
spring:
cloud:
nacos:
server-addr: 98.10.1.12:80
username: common
password: common
config:
refresh-enabled: true
discovery:
namespace: 7da18ff0-32b8-490c-9ec4-d3ee3d1963b4
group: DEFAULT_GROUP
gateway:
globalcors:
cors-configurations:
'[/**]':
allow-credentials: true
allowed-origins: "*"
allowed-methods: "*"
allowed-headers: "*"
add-to-simple-url-handler-mapping: true
management:
endpoints:
web:
exposure:
include: "*"
dynamic:
route:
enabled: true
dataId: gateway-dynamic-dev.json
group: DEFAULT_GROUP
namespace: 7da18ff0-32b8-490c-9ec4-d3ee3d1963b4
serverAddr: 98.10.1.12:80
username: common
password: common
filter:
authorize:
ignoreuris:
- /api/good-api/v
- /api/good-server/v
- /api/goodorder-api/v
- /api/goodorder-server/v
route:
interfaces:
-
code: auth-server
interfaceApi: /api/auth-server/test/test1
serviceName: common-auth-server
profile: env
erpx:
internal:
address: localhost
里面包含一个json路由配置文件:gateway-dynamic-dev.json,点开详情,内容如下:
[{
"id": "goods-api",
"order": 9,
"predicates": [{
"args": {
"pattern": "/api/goods-api/**"
},
"name": "Path"
}],
"filters":[{
"args": {
"_genkey_0": "2"
},
"name": "StripPrefix"
}],
"uri": "lb://common-goods-xapi"
},
{
"id": "goods-server",
"order": 9,
"predicates": [{
"args": {
"pattern": "/api/goods-server/**"
},
"name": "Path"
}],
"filters":[{
"args": {
"_genkey_0": "2"
},
"name": "StripPrefix"
}],
"uri": "lb://common-goods-server"
},
{
"id": "goodorder-api",
"order": 34,
"predicates": [{
"args": {
"pattern": "/api/goodorder-api/**"
},
"name": "Path"
}],
"filters":[{
"args": {
"_genkey_0": "2"
},
"name": "StripPrefix"
}],
"uri": "lb://common-goodorder-api"
},
{
"id": "goodorder-server",
"order": 35,
"predicates": [{
"args": {
"pattern": "/api/goodorder-server/**"
},
"name": "Path"
}],
"filters":[{
"args": {
"_genkey_0": "2"
},
"name": "StripPrefix"
}],
"uri": "lb://common-goodorder-server"
}]
6、核心类配置:
1)过滤地址配置:
package com.example.nandao.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;
@Configuration
public class ComCorsConfig {
@Bean
public CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedMethod("*");
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
2)提取路由参数配置:
package com.example.nandao.config;
import lombok.Data;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @Description 加载动态路由配置,通过此配置加载配置文件 gateway-dynamic-dev.json 里的路由信息
**/
@Data
@Component
//https://blog.youkuaiyun.com/weixin_38357164/article/details/101030680
//https://www.jianshu.com/p/9f2c129047c8
//https://www.cnblogs.com/ushowtime/p/15993816.html
@ConfigurationProperties(prefix = "dynamic.route")
//只要存在 ComDynamicRouteConfig 类,便会初始化 ComDynamicRouteProperties 类
@ConditionalOnBean( ComDynamicRouteConfig.class)
public class ComDynamicRouteProperties {
/**
* nacos 配置管理 dataId
*/
private String dataId;
/**
* nacos 配置管理 group
*/
private String group;
/**
* nacos 服务地址
*/
private String serverAddr;
/**
* 启动动态路由的标志,默认关闭
*/
private boolean enabled = false;
private String namespace;
private String username;
private String password;
}
3)动态路由配置:
package com.example.nandao.config;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.AbstractListener;
import com.alibaba.nacos.api.exception.NacosException;
import com.example.nandao.service.impl.ComDynamicRouteServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
/**
* @Description 核心配置
**/
@Slf4j
@Configuration
@ConditionalOnProperty(name = "dynamic.route.enabled", matchIfMissing = true)
public class ComDynamicRouteConfig {
private static final List<String> ROUTE_LIST = new ArrayList<>();
@Autowired//依赖注入,先初始化完成
private ComDynamicRouteProperties properties;
@Autowired
private ComDynamicRouteServiceImpl dynamicRouteService;
@PostConstruct
private void init() {
dynamicRouteByNacosListener();
}
private void dynamicRouteByNacosListener() {
try {
Properties prop = new Properties();
prop.setProperty(PropertyKeyConst.NAMESPACE, properties.getNamespace());
prop.setProperty(PropertyKeyConst.SERVER_ADDR, properties.getServerAddr());
prop.setProperty(PropertyKeyConst.USERNAME, properties.getUsername());
prop.setProperty(PropertyKeyConst.PASSWORD, properties.getPassword());
ConfigService configService = NacosFactory.createConfigService(prop);
//ConfigService configService = NacosFactory.createConfigService(properties.getServerAddr());
String content = configService.getConfigAndSignListener(
properties.getDataId(),
properties.getGroup(),
5000,
new AbstractListener() {
@Override
public void receiveConfigInfo(String configInfo) {
statsConfig(configInfo);
}
});
statsConfig(content);
} catch (NacosException e) {
log.error("nacos 获取动态路由配置和监听异常", e);
}
}
private void statsConfig(String configInfo) {
try {
clearRoute();
List<RouteDefinition> gatewayRouteDefinitions = JSONObject.parseArray(configInfo, RouteDefinition.class);
for (RouteDefinition routeDefinition : gatewayRouteDefinitions) {
addRoute(routeDefinition);
}
} catch (Exception e) {
log.error("statsConfig异常", e);
}
}
private void clearRoute() {
for(String id : ROUTE_LIST) {
dynamicRouteService.delete(id);
}
ROUTE_LIST.clear();
}
private void addRoute(RouteDefinition definition) {
dynamicRouteService.add(definition);
ROUTE_LIST.add(definition.getId());
}
}
4)动态路由的增删改查:
package com.example.nandao.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
@Slf4j
@Service
public class ComDynamicRouteServiceImpl implements ApplicationEventPublisherAware {
private ApplicationEventPublisher publisher;
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
/**
* 增加路由
* @param definition
* @return
*/
public void add(RouteDefinition definition) {
try {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
} catch (Exception ex) {
log.error(String.format("add -> save route fail【routeId: %s】", definition.getId()), ex);
}
}
/**
* 更新路由
* @param definition
* @return
*/
public void update(RouteDefinition definition){
try {
this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
} catch (Exception ex) {
log.error(String.format("update -> update route fail【routeId: %s】", definition.getId()), ex);
}
try {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
} catch (Exception ex) {
log.error(String.format("update -> save route fail【routeId: %s】", definition.getId()), ex);
}
}
/**
* 删除路由
* @param id
* @return
*/
public void delete(String id) {
try {
this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
} catch (Exception ex) {
log.error(String.format("delete -> delete route fail【routeId: %s】", id), ex);
}
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
}
5)boot核心启动类:
package com.example.nandao;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class NandaoApplication {
public static void main(String[] args) {
SpringApplication.run(NandaoApplication.class, args);
}
}
到此基本配置大概完成,注释也很清晰。
二、静态路由和动态路由的区别:
1、静态路由特点:
静态路由必须由管理员手工指定。当网络拓扑发生变化时,需要管理员手工更新配置。
同时静态路由只适合简单小型的网络,当网络结构复杂、路由条目繁多的时候,静态路由将难以胜任。静态路由是管理员通过手动配置的,不便于拓展网络拓扑,一旦网络拓扑发生改变,
静态路由配置量会很大。动态路由是路由器通过网络协议,动态的学习路由,当网络拓扑发生变化的时候,路由器会根据路由协议自动学习新的路由。
2、动态路由特点:
动态路由通过网络中运行的路由协议收集网络信息。当网络拓扑发生变化时,路由器会自动更新路由信息,不必管理员手工去更新。动态路由,因为OSPF,RIP等路由协议都会周期更新,所以他的更新量会很大,会很占带宽。
3、两者对比:
通过对比后可以发现,动态路由协议更适用于大规模的网络部署。使用静态路由的好处是网络安全保密性高。动态路由因为需要路由器之间频繁地交换各自的路由表,而对路由表的分析可以揭示网络的拓扑结构和网络地址等信息。
4、精炼总结:
静态路由:安全,占用带宽小,简单,高效,转发效率高。
动态路由:灵活性高。
到此,关于动态路由的原理和使用分享完毕,下篇我们分析网关中的过滤器使用,敬请期待!