Spring cloud upgrade 2020.0.x - 41. Explanation of the basic process of spring cloud gateway

Keywords: spring-cloud

Code address of this series: https://github.com/JoJoTec/sp...

Next, we will enter another big module on our upgrade path, namely the gateway module. In the gateway module, we abandoned zuul that has entered the maintenance state and selected Spring Cloud Gateway as the internal gateway. The reasons for choosing Spring Cloud Gateway instead of nginx and Kong are:

  1. It is important that the project team is more familiar with Java and asynchronous programming of Project Reactor
  2. We need to use the request based stateful retry pressure sensitive load balancer we implemented earlier in the gateway
  3. Retry needs to be implemented in the gateway
  4. You need to implement instance path breaking in the gateway
  5. Unified business encryption and decryption is required in the gateway
  6. The BFF (Backends For Frontends) interface needs to be implemented in the gateway, that is, the requests of several different interfaces are combined and returned at one time according to the client request
  7. Redis should be used in the gateway to record some Token related values

Therefore, we use the Spring Cloud Gateway as the internal gateway. Next, we will implement these functions in turn. At the same time, during the use of this upgrade, the Spring Cloud Gateway also has some pits, such as:

  1. When spring cloud sleuth is used in combination, there will be link information tracking, but link information will be lost in some cases.
  2. The asynchronous API encapsulated by the three-party Reactor (for example, spring data Redis used by Redis) is not well understood, resulting in the occupation of key threads.

But first, we need to briefly understand what components Spring Cloud Gateway includes and what the whole calling process looks like. Since Spring Cloud Gateway is implemented based on spring boot and spring Webflux, we will start with the outer WebFilter, and then analyze how to go to the packaging logic of Spring Cloud Gateway, as well as the components contained in Spring Cloud Gateway, how to forward the request, and what processing has been done after coming back. We will analyze these one by one.

Create a simple API gateway

In order to analyze the process in detail, let's first create a simple gateway for quick start and analysis.

Create dependencies first:

pom.xml

<?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>spring-cloud-parent</artifactId>
        <groupId>com.github.jojotech</groupId>
        <version>2020.0.3-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-cloud-api-gateway</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.github.jojotech</groupId>
            <artifactId>spring-cloud-webflux</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
    </dependencies>
</project>

The parent points to the spring cloud parent of our project, adds the spring cloud weblux dependency implemented in the previous section, and also needs to add the spring cloud starter gateway. Since our spring cloud parent has specified the version dependency management of spring cloud parent, it is not necessary to specify the version of spring cloud starter gateway here

Then we start writing the configuration file:

application.yml

server:
  ##Port 8181
  port: 8181
spring:
  application:
    # The microservice name is apiGateway
    name: apiGateway
  cloud:
    gateway:
      httpclient:
        # The timeout of the HTTP connection forwarded by the gateway to other microservices is 500ms
        connect-timeout: 500
        # The timeout of HTTP response forwarded by the gateway to other microservices is 500ms
        response-timeout: 60000
      routes:
        # Write forwarding rules
        - id: first_route
          # Forward to microservice test service
          uri: lb://test-service
          # Which paths are included
          predicates:
            - Path=/test-ss/**
          #  For the micro service access path forwarded to, remove the first block in the path, that is, remove / test SS
          filters:
            - StripPrefix=1
    loadbalancer:
      # Specify a zone because we added the logic that only instances of the same zone can access each other in load balancing
      zone: test
      ribbon:
        # Close ribbon
        enabled: false
      cache:
        # Cache time of local microservice instance list
        ttl: 5
        # The cache size, which is set to 256 by default, depends on how many other microservices your microservice calls
        capacity: 256
    discovery:
      client:
        simple:
          # Using the simple discovery client service in spring common to discover the client is to write the microservice instance in the configuration file
          instances:
            # Specify the instance list of the micro service test service
            test-service:
              - host: httpbin.org
                port: 80
                metadata:
                  # Specify the zone of this instance because we added the logic that only instances of the same zone can access each other in load balancing 
                  zone: test
eureka:
  client:
    # Turn off eureka
    enabled: false

Finally, write the startup entry class:

package com.github.jojotech.spring.cloud.apigateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(scanBasePackages = "com.github.jojotech.spring.cloud.apigateway")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Startup, access path: http://127.0.0.1:8181/test-ss/anything, you can see that the request is sent to the anything path of httpbin.org, and this interface will return all the information in the request.

In this way, we have implemented a simple gateway. Next, let's analyze its workflow and source code in detail.

The core of request processing in asynchronous environment - spring boot + WebHandler of spring Webflux

The external service container of the Simple Gateway we created is actually a container based on Netty and Project Reactor. We skip these and directly enter the processing logic related to Spring Boot. We only need to know that the request and its corresponding response will be encapsulated by the outer container into ServerHttpRequest request and serverhttpresponse response (both under the package org.springframework.http.server.reactive).

Then, it will be handed over to WebHandler for processing. The implementation of WebHandler is actually a responsibility chain decoration mode, as shown in the figure below. The WebHandler of each layer will decorate the request and response with their own responsibilities, and then hand them over to the WebHandler of the inner layer for processing.

HttpWebHandlerAdapter - encapsulates the request as ServerWebExchange

The interface definition of WebHandler is:

public interface WebHandler {
    Mono<Void> handle(ServerWebExchange exchange);
}

However, the parameters passed in from the outermost layer are request and response. They need to be encapsulated into ServerWebExchange. This work is done in the HttpWebHandlerAdapter. In fact, the main task of HttpWebHandlerAdapter is to encapsulate various parameters into ServerWebExchange (in addition to the request and response related to this request, session manager, codec configuration, internationalization configuration, and ApplicationContext for extension).

In addition to these, the processing of Forwarded and the configuration logic of X-Forwarded * related headers are also carried out here. Then hand the encapsulated ServerWebExchange to the inner WebHandler, that is, exceptionhandling WebHandler, to continue processing. At the same time, it can be seen from the source code that Mono, which is handed over to the inner layer for processing, also adds the logic of exception handling and recording response information:

HttpWebHandlerAdapter.java

//Leave it to the inner layer to handle the encapsulated ` ServerWebExchange`
return getDelegate().handle(exchange)
        //Record the response log, trace level, which is generally not used
        .doOnSuccess(aVoid -> logResponse(exchange))
        //Handling exceptions not handled by the inner layer generally does not come here
        .onErrorResume(ex -> handleUnresolvedError(exchange, ex))
        //After all processing is completed, set the response to complete
        .then(Mono.defer(response::setComplete));

The remaining inner WebHandler will be analyzed in the next section

WeChat search "my programming meow" attention to the official account, daily brush, easy to upgrade technology, and capture all kinds of offer:

Posted by Ardivaba on Wed, 24 Nov 2021 14:57:02 -0800