Relevant versions of Spring used in this battle:
SpringBoot:2.1.7.RELEASE
Spring Cloud:Greenwich.SR2
Spring CLoud Alibaba:2.1.0.RELEASE
In the previous articles we introduced Nacos Service Registration and Discovery and Nacos Configuration Management For those of you who haven't seen it yet, this article is based on two articles.
Background introduction
Under the Spring Cloud micro-service system, common service gateways are Netflix's open source Zuul, and Spring Cloud's own open source Spring Cloud Gateway, where NetFlix's open source Zuul version has iterated to 2.x, but Spring Cloud is not integrated. Currently Spring Cloud's integrated Spring Cloud Zuul is still Zuul1.x, this version of Zuul is built on a Servlet and uses a blocked multithreading scheme, where a thread processes a connection request, which can cause an increase in surviving connections and threads when internal delays are high and device failures are high.Spring Cloud's own open source Spring Cloud Gateway is built based on Spring Webflux, which has a new, non-blocking, functional Reactive Web framework for building asynchronous, non-blocking, event-driven services that perform well in scalability.With a non-blocking API, Websockets are supported and, because it is tightly integrated with Spring, will provide a better development experience.
This article describes how to use Nacos configuration capabilities to implement dynamic routing of service gateways based on Gateway service gateways.
Implementation scheme
Before we begin, let's talk about how we can do this:
- Routing information is no longer configured in the configuration file, but is configured in the configuration of Nacos.
- Open monitoring in the service gateway Spring Cloud Gateway to listen for changes to the Nacos configuration file.
- Once the Nacos configuration file changes, Spring Cloud Gateway refreshes its routing information.
Environmental preparation
First, you need to prepare a Nacos service. My version here is Nacos v1.1.3. If you don't configure the Nacos service for your classmates, please refer to the previous article A Preliminary Study of Nacos Service Center
Engineering Practice
Create the project gateway-nacos-config, which relies on pom.xml as follows:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.7.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.springcloud.alibaba</groupId> <artifactId>gateway-nacos-config</artifactId> <version>0.0.1-SNAPSHOT</version> <name>gateway-nacos-config</name> <description>gateway-nacos-config</description> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR2</spring-cloud.version> <spring-cloud-alibaba.version>2.1.0.RELEASE</spring-cloud-alibaba.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</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-gateway</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${spring-cloud-alibaba.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/libs-milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
- When using the Spring Cloud Alibaba component, you need to configure spring-cloud-alibaba-dependencies in <dependency management>, which manages the version dependencies of the Spring Cloud Alibaba component.
The configuration file application.yml is as follows:
server: port: 8080 spring: application: name: spring-cloud-gateway-server cloud: nacos: discovery: server-addr: 192.168.44.129:8848 management: endpoints: web: exposure: include: '*'
- spring.cloud.nacos.discovery.server-addr: Configured as Nacos service address in ip:port
Next to the core section, configure Spring Cloud Gateway dynamic routing, where you need to implement a Spring-provided event push interface, ApplicationEventPublisherAware, with the following code:
@Component public class DynamicRoutingConfig implements ApplicationEventPublisherAware { private final Logger logger = LoggerFactory.getLogger(DynamicRoutingConfig.class); private static final String DATA_ID = "zuul-refresh-dev.json"; private static final String Group = "DEFAULT_GROUP"; @Autowired private RouteDefinitionWriter routeDefinitionWriter; private ApplicationEventPublisher applicationEventPublisher; @Bean public void refreshRouting() throws NacosException { Properties properties = new Properties(); properties.put(PropertyKeyConst.SERVER_ADDR, "192.168.44.129:8848"); properties.put(PropertyKeyConst.NAMESPACE, "8282c713-da90-486a-8438-2a5a212ef44f"); ConfigService configService = NacosFactory.createConfigService(properties); configService.addListener(DATA_ID, Group, new Listener() { @Override public Executor getExecutor() { return null; } @Override public void receiveConfigInfo(String configInfo) { logger.info(configInfo); boolean refreshGatewayRoute = JSONObject.parseObject(configInfo).getBoolean("refreshGatewayRoute"); if (refreshGatewayRoute) { List<RouteEntity> list = JSON.parseArray(JSONObject.parseObject(configInfo).getString("routeList")).toJavaList(RouteEntity.class); for (RouteEntity route : list) { update(assembleRouteDefinition(route)); } } else { logger.info("Routing unchanged"); } } }); } @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; } /** * Route Update * @param routeDefinition * @return */ public void update(RouteDefinition routeDefinition){ try { this.routeDefinitionWriter.delete(Mono.just(routeDefinition.getId())); logger.info("Route Update Successful"); }catch (Exception e){ logger.error(e.getMessage(), e); } try { routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe(); this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this)); logger.info("Route Update Successful"); }catch (Exception e){ logger.error(e.getMessage(), e); } } public RouteDefinition assembleRouteDefinition(RouteEntity routeEntity) { RouteDefinition definition = new RouteDefinition(); // ID definition.setId(routeEntity.getId()); // Predicates List<PredicateDefinition> pdList = new ArrayList<>(); for (PredicateEntity predicateEntity: routeEntity.getPredicates()) { PredicateDefinition predicateDefinition = new PredicateDefinition(); predicateDefinition.setArgs(predicateEntity.getArgs()); predicateDefinition.setName(predicateEntity.getName()); pdList.add(predicateDefinition); } definition.setPredicates(pdList); // Filters List<FilterDefinition> fdList = new ArrayList<>(); for (FilterEntity filterEntity: routeEntity.getFilters()) { FilterDefinition filterDefinition = new FilterDefinition(); filterDefinition.setArgs(filterEntity.getArgs()); filterDefinition.setName(filterEntity.getName()); fdList.add(filterDefinition); } definition.setFilters(fdList); // URI URI uri = UriComponentsBuilder.fromUriString(routeEntity.getUri()).build().toUri(); definition.setUri(uri); return definition; } }
This article mainly introduces the refreshRouting() method, which is mainly responsible for monitoring configuration changes of Nacos. First, a ConfigService is built with parameters, then a listening is opened with ConfigService, and the routing information is refreshed in the listening method.
The Nacos configuration is shown in the following figure:
{ "refreshGatewayRoute":false, "routeList":[ { "id":"github_route", "predicates":[ { "name":"Path", "args":{ "_genkey_0":"/meteor1993" } } ], "filters":[ ], "uri":"https://github.com", "order":0 } ] }
Configuration format choices JSON, Data ID, and Group are consistent with the configuration in the program. Note that the program here configures namespace, which is not needed if the default namespace is used.
A route/meteor1993 is configured here, direct access to this route will access the author's Github repository.
The rest of the code is not shown here. It has been uploaded to the code warehouse and can be used by students who need it.
test
Start the project without any routing information. Open the browser to access: http://localhost : 8080/meteor1993, page returns 404 error message, as shown in Fig.
You can also access links: http://localhost : 8080/actuator/gateway/routes, you can see the following prints:
[]
Open the UI interface on the Nacos Server side, select Listen Query, select namespace as the column for springclouddev, enter DATA_ID as zuul-refresh-dev.json and Group as DEFAULT_GROUP, click Query, and you can see that the project gateway-nacos-config we started is listening on the Nacos Server side, as shown in the following figure:
Author's local ip here is 192.168.44.1.When listening is normal, we modify the configuration we just created and change the refreshGatewayRoute inside to true, as follows:
{"refreshGatewayRoute": true, "routeList":[{"id":"github_route","predicates":[{"name":"Path","args":{"_genkey_0":"/meteor1993"}}],"filters":[],"uri":"https://github.com","order":0}]}
Click Publish to see the console print log for the project gateway-nacos-config as follows:
2019-09-02 22:09:49.254 INFO 8056 --- [38-2a5a212ef44f] c.s.a.g.config.DynamicRoutingConfig : { "refreshGatewayRoute":true, "routeList":[ { "id":"github_route", "predicates":[ { "name":"Path", "args":{ "_genkey_0":"/meteor1993" } } ], "filters":[ ], "uri":"https://github.com", "order":0 } ] } 2019-09-02 22:09:49.268 INFO 8056 --- [38-2a5a212ef44f] c.s.a.g.config.DynamicRoutingConfig : Route Update Successful
At this time, our project gateway-nacos-config's route has been updated successfully, access path: http://localhost : 8080/actuator/gateway/routes, you can see the following prints:
[{"route_id":"github_route","route_definition":{"id":"github_route","predicates":[{"name":"Path","args":{"_genkey_0":"/meteor1993"}}],"filters":[],"uri":"https://github.com","order":0},"order":0}]
We access the link again in the browser: http://localhost : 8080/meteor1993, you can see that the page is routing normally to the Github repository, as shown in the figure:
summary
At this point, the Nacos dynamic gateway routing is finished. It mainly uses the function of service gateway side to monitor Nacos configuration changes, to achieve dynamic refresh of service gateway routing configuration. Similarly, we can also use service gateway Zuul to implement Nacos-based dynamic routing function.
Based on this idea, we can use the configuration center to route the gateway dynamically instead of using the configuration file that comes with the service gateway itself, so that each route information changes without modifying the configuration file and then restarting the service.
There are currently more configuration centers in the market with open source Apollo for Portable and Spring Cloud Zuul for Service Gateway. In the next article, we will introduce how to use Apollo to implement dynamic routing for Spring Cloud Zuul.