上一篇文章 SpringCloud与Nacos动态路由 中,探讨了如何通过服务名进行动态路由的实现方式。更近一步的需求,既然uri中的http可以配置变量,那么uri中lb能不能也使用变量的形式呢?比如我的路由/lsmm/bigdata,由lsmm微服务进行服务,路由lsmm2/bigdata由lsmm2进行服务。
在配置文件中有一个gateway.discovery.locator配置项中,有一个url-expression,这个是不是可以做一点文章呢?
(1) 经过我多方尝试,和参阅相关资料,还是没有找到动态服务名的方法。因为你在uri中添加变量,最后都会被转义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 spring: application: name: ${app-name} cloud: nacos: discovery: server-addr: 127.0 .0 .1 :8848 ip: 127.0 .0 .1 gateway: discovery: locator: enabled: true routes: - id: url-proxy-1 uri: lb://$\{segment}-bigdata predicates: - Path=/lsmfhx/bigdata/** filters: - RewritePath=/lsmfhx(?<segment>/?.*), $\{segment}-bigdata
但是使用uri中使用http协议的时候,就可以使用变量的形式,比如上面的注释的地方。
(2) 经过查看错误地方,出错的地方主要是自定义了一个Filter,(RouteToRequestUrlFilter implements GlobalFilter, Ordered)
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 @Override public Mono<Void> filter (ServerWebExchange exchange, GatewayFilterChain chain) { Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); if (route == null ) { return chain.filter(exchange); } log.trace("RouteToRequestUrlFilter start" ); URI uri = exchange.getRequest().getURI(); boolean encoded = containsEncodedParts(uri); URI routeUri = route.getUri(); if (hasAnotherScheme(routeUri)) { exchange.getAttributes().put(GATEWAY_SCHEME_PREFIX_ATTR, routeUri.getScheme()); routeUri = URI.create(routeUri.getSchemeSpecificPart()); } if ("lb" .equalsIgnoreCase(routeUri.getScheme()) && routeUri.getHost() == null ) { throw new IllegalStateException ("Invalid host: " + routeUri.toString()); } URI mergedUrl = UriComponentsBuilder.fromUri(uri) .scheme(routeUri.getScheme()).host(routeUri.getHost()) .port(routeUri.getPort()).build(encoded).toUri(); exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, mergedUrl); return chain.filter(exchange); }
(3) 经过苦心思索和资料查找,还是没有一个正经的可以在uri中填写参数的方案。然后我想到了另一个方案,就是动态路由的方法。多说的nacos动态路由,不都是通过使用配置中心的方式吗?那能不能进行结合一下呢,将配置中心填写动态路由,改为通过获取服务名称来获取和更新动态路由。也就是说,根据获取到的服务名,然后动态创建predicates和filters。
找来找去,还是不行啊。nacos没有提供监听服务注册和销毁的方法。根据github上的一个ISSUE:是否可以增加一个方法,可以监听任意一个ServiceInfo的变化事件? ,这个2019年12月的Issue,发现也有人遇到了和我一样的需求,但是苦于没有对应的服务上下线的监听函数,所以有人使用轮询的方式,监听服务列表的变化,有人通过在上下线时,修改配置文件的方法,间接更新路由。
在目前严重的官方不支持的情况下,如何实现自己的业务需求呢?是重新更换服务注册中心,还是通过迂回的方式解决问题呢?
经过不断的尝试和探索,终于实现了通过配置中心实现动态路由的方法
1.pom.xml 我这里使用了模块的开发设计,所以有主模块的pom.xml,有子模块的pom.xml (1) 主模块的pom.xml,主要就是dependencyManagement管理spring-cloud-dependencies的依赖
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 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > com.bibichuan</groupId > <artifactId > SpringBootCould</artifactId > <version > 1.0-SNAPSHOT</version > <packaging > pom</packaging > <name > lsmfhx</name > <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.1.6.RELEASE</version > <relativePath /> </parent > <modules > <module > gateway</module > </modules > <dependencyManagement > <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-dependencies</artifactId > <version > Greenwich.SR1</version > <type > pom</type > <scope > import</scope > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-alibaba-dependencies</artifactId > <version > 0.2.2.RELEASE</version > <type > pom</type > <scope > import</scope > </dependency > </dependencies > </dependencyManagement > <dependencies > </dependencies > </project >
(2) 子模块的pom.xml,当然,主模块和子模块还是可以直接合并到一起的。
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 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <parent > <artifactId > SpringBootCould</artifactId > <groupId > com.bibichuan</groupId > <version > 1.0-SNAPSHOT</version > <relativePath > ../</relativePath > </parent > <modelVersion > 4.0.0</modelVersion > <artifactId > gateway</artifactId > <packaging > jar</packaging > <description > 网关</description > <properties > <java.version > 1.8</java.version > <spring-cloud.version > Greenwich.RELEASE</spring-cloud.version > </properties > <dependencies > <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 > <version > 2.1.0.RELEASE</version > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.codehaus.mojo</groupId > <artifactId > build-helper-maven-plugin</artifactId > </plugin > </plugins > </build > </project >
2.编写三个类 (1) 应用程序启动时直接执行的事件注册以及路由注册的类
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 87 88 89 90 91 92 package com.bibichuan;import com.alibaba.cloud.nacos.NacosConfigProperties;import com.alibaba.fastjson.JSON;import com.alibaba.nacos.api.annotation.NacosInjected;import com.alibaba.nacos.api.config.ConfigService;import com.alibaba.nacos.api.config.annotation.NacosConfigurationProperties;import com.alibaba.nacos.api.config.listener.Listener;import com.alibaba.nacos.api.exception.NacosException;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.CommandLineRunner;import org.springframework.cloud.gateway.route.RouteDefinition;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;import java.util.List;import java.util.concurrent.Executor;@Component @NacosConfigurationProperties(dataId = "${nacos.config.data-id}", autoRefreshed = true, groupId = "${nacos.config.group}") public class NacosGatewayDefineConfig implements CommandLineRunner { private static final Logger log= LoggerFactory.getLogger(NacosGatewayDefineConfig.class); @Autowired NacosConfigProperties nacosConfigProperties; @Value("${nacos.config.data-id}") private String dataId; @Value("${nacos.config.group}") private String group; @Autowired NacosDynamicRouteService nacosDynamicRouteService; @Override public void run (String... args) throws Exception { addRouteNacosListen(); } private void addRouteNacosListen () { try { ConfigService configService = nacosConfigProperties.configServiceInstance(); String configInfo = configService.getConfig(dataId, group, 5000 ); log.info("从Nacos返回的配置:" + configInfo); getNacosDataRoutes(configInfo); configService.addListener(dataId, group, new Listener () { @Override public void receiveConfigInfo (String configInfo) { log.info("Nacos更新了!" ); log.info("接收到数据:" +configInfo); getNacosDataRoutes(configInfo); } @Override public Executor getExecutor () { return null ; } }); } catch (NacosException e) { log.error("nacos-addListener-error" , e); e.printStackTrace(); } } private void getNacosDataRoutes (String configInfo) { List<RouteDefinition> list = JSON.parseArray(configInfo, RouteDefinition.class); list.stream().forEach(definition -> { log.info("" +JSON.toJSONString(definition)); nacosDynamicRouteService.update(definition); }); } }
(2) 路由更新的服务接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.bibichuan;import org.springframework.cloud.gateway.route.RouteDefinition;public interface NacosDynamicRouteService { String update (RouteDefinition gatewayDefine) ; }
(3) 路由更新的服务接口实现类
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 package com.bibichuan;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.stereotype.Service;import reactor.core.publisher.Mono;@Service public class NacosDynamicRouteServiceImpl implements NacosDynamicRouteService { @Autowired private RouteDefinitionWriter routeDefinitionWriter; @Autowired private ApplicationEventPublisher publisher; @Override public String update (RouteDefinition definition) { try { this .routeDefinitionWriter.delete(Mono.just(definition.getId())); } catch (Exception e) { return "删除路由失败: RouteId:" + definition.getId(); } try { routeDefinitionWriter.save(Mono.just(definition)).subscribe(); this .publisher.publishEvent(new RefreshRoutesEvent (this )); return "更新路由成功" ; } catch (Exception e) { return "更新路由失败" ; } } }
3.配置文件 (1) application.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 app-name: gateway server: port: 7000 spring: application: name: ${app-name} cloud: nacos: discovery: server-addr: 127.0 .0 .1 :8848 ip: 127.0 .0 .1 nacos: config: server-addr: 127.0 .0 .1 :8848 group: DEFAULT_GROUP file-extension: json data-id: route-list
(2) bootstrap.properties
1 spring.cloud.nacos.config.server-addr=127.0.0.1:8848
4.打开nacos编辑配置 打开localhost:8848/nacos,编辑路由配置文件route-list.DEFAULT_GROUP
1 2 3 4 5 6 7 8 9 10 11 12 [ { "filters" : [ ] , "id" : "jd_route" , "order" : 0 , "predicates" : [ { "args" : { "pattern" : "/jd" } , "name" : "Path" } ] , "uri" : "http://www.baidu.com" } ]
这里有一点需要说明的就是,如果刚开始使用这个json进行路由配置,可能会对filter传参产生疑问,比如如何添加
1 2 filters: - StripPrefix=1
经过反复的尝试和参考相关文章,我找打了方法:
1 2 3 4 5 6 "filters" : [ { "name" : "StripPrefix" , "args" : { "_genkey_0" : 1 } } ]
如果有多个参数,那就是”_genkey_1”、”_genkey_2”
5.启动应用程序 启动应用程序,就可以看到从nacos配置中心中获取到了路由配置,然后在配置中心中修改了配置,路由也会相应的更新了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.bibichuan;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;@SpringBootApplication public class Application { public static void main (String[] args) { SpringApplication.run(Application.class, args); } }
总结 (1) 需要使用spring-cloud-starter-alibaba-nacos-config依赖 (2) 在应用程序启动时监听配置改变configService.addListener (3) ApplicationEventPublisher发布路由更新事件
问题 (1) @NacosInjected 注入Nacos 的NamingService实例为null
解决方法就是使用:ConfigService configService = nacosConfigProperties.configServiceInstance(); 代替@NacosInjected注入
1 2 3 4 5 6 7 8 @Autowired NacosDiscoveryProperties nacosDiscoveryProperties; @Autowired NacosConfigProperties nacosConfigProperties; ConfigService configService = nacosConfigProperties.configServiceInstance();NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
还有种方法就是使用NacosFactory构造工厂,通过服务地址生成配置实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 try { String serverAddr = "{serverAddr}" ; String dataId = "{dataId}" ; String group = "{group}" ; Properties properties = new Properties (); properties.put("serverAddr" , serverAddr); ConfigService configService = NacosFactory.createConfigService(properties); String content = configService.getConfig(dataId, group, 5000 ); System.out.println(content); } catch (NacosException e) { e.printStackTrace(); }