Code address of this series: github.com/JoJoTec/spr...
preface
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:
- It is important that the project team is more familiar with Java and asynchronous programming of Project Reactor
- We need to use the request based stateful retry pressure sensitive load balancer we implemented earlier in the gateway
- Retry needs to be implemented in the gateway
- You need to implement instance path breaking in the gateway
- Unified business encryption and decryption is required in the gateway
- It needs to be implemented in the gateway BFF (Backends For Frontends) interface, that is, according to the client request, the requests of several different interfaces are combined and returned at one time
- 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:
- When spring cloud sleuth is used in combination, there will be link information tracking, but link information will be lost in some cases.
- 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> Copy code
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 Copy code
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); } } Copy code
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 into an outer container ServerHttpRequest request and Serverhttpresponse org.springframework.http.server.reactive This bag is under).
Then, it will be handed over to WebHandler Process. WebHandler The implementation of is actually a responsibility chain decoration mode, as shown in the figure below. On each floor WebHandler Will request and response Decorate according to your own responsibility, and then hand it over to the inner layer WebHandler handle.
HttpWebHandlerAdapter - encapsulates the request as ServerWebExchange
WebHandler The interface definition is:
public interface WebHandler { Mono<Void> handle(ServerWebExchange exchange); } Copy code
But the parameters passed in from the outermost layer are request and response, they need to be encapsulated into Server web exchange, this work is done in the HttpWebHandlerAdapter. In fact, the main task of HttpWebHandlerAdapter is to encapsulate various parameters into Server web exchange (except for those related to this request) request and response, session manager, session manager, codec configuration, internationalization configuration, and ApplicationContext for extension).
In addition to these, deal with Forwarded also X-Forwarded* The related Header configuration logic is also carried out here. Then the encapsulated ServerWebExchange To the inside WebHandler Namely ExceptionHandlingWebHandler 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:
//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)); Copy code
The rest of the inner layer WebHandler, which we will continue to analyze in the next section