Spring Cloud Alibaba | Gateway Dynamic Gateway Routing Based on Nacos

Keywords: Java Spring github Maven JSON

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:

  1. Routing information is no longer configured in the configuration file, but is configured in the configuration of Nacos.
  2. Open monitoring in the service gateway Spring Cloud Gateway to listen for changes to the Nacos configuration file.
  3. 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.

Sample Code

Github-Sample Code

Gitee-Sample Code

Posted by pradeepmamgain on Wed, 04 Sep 2019 18:36:45 -0700