Spring Cloud Gateway deadly series of ten questions?

2023.03.09

Spring Cloud Gateway deadly series of ten questions?


In the traditional monolithic architecture, only one service is open to the client to call, but in the microservice architecture, a system is split into multiple microservices, so how to call these microservices as a client? If there is no gateway, the calling address of each microservice can only be recorded locally.

This article introduces an important role in microservices: the gateway, how to choose the gateway, because the Ali department has not released the gateway for the time being, of course I chose Spring cloud Gateway, after all, it is my son.

Friends who have already read this article can skip directly

The article directory is as follows:


Why do you need a gateway?

In the traditional monolithic architecture, only one service is open to the client to call, but in the microservice architecture, a system is split into multiple microservices, so how to call these microservices as a client? If there is no gateway, the calling address of each microservice can only be recorded locally.


The microservice architecture without gateway often has the following problems:

  • The client requests different microservices multiple times, which increases the complexity of client code or configuration writing.
  • Authentication is complex, and each service requires independent authentication.
  • There are cross-domain requests, and the processing is relatively complicated in certain scenarios.

The basic function of the gateway?

The gateway is the portal of all microservices. Routing and forwarding are only the most basic functions. In addition, there are other functions, such as: authentication, authentication, fusing, current limiting, log monitoring, etc... ...


The above application scenarios will be introduced in detail in subsequent articles, not the focus of today.

Why choose Spring Cloud Gateway?

The Zuul gateway was used in version 1.x; but in version 2.x, the upgrade of zuul kept skipping tickets. Spring Cloud finally developed a gateway to replace Zuul, which is Spring Cloud Gateway.

Definitely choose Spring Cloud Gateway, my son. Many of its ideas are borrowed from zuul.

One important reason:

Spring Cloud Gateway is built on Spring Boot 2.x, Spring WebFlux and [Project Reactor.

There is no need to worry about the integration of Spring Boot, compatibility and performance.

How many must-know terms for Spring Cloud Gateway?

  1. Route (route): The basic building block of a gateway. It consists of an ID, a target URI, a collection of assertions, and a collection of filters. If the aggregation assertion result is true, the route is matched.
  2. Assertion (Predicate): Refer to the new feature Predicate of Java8, which allows developers to match anything in the HTTP request, such as headers or parameters.
  3. Filter (filter): Can modify the content of the request and response before or after returning the request.

How to build the gateway?


Why put this picture?

Be sure to adapt it according to the version in the picture above, otherwise there will be unexpected bugs, Chen has encountered it before, and it is all tears......

Create a new cloud-gateway9023 and add the following dependencies:

<!--gateway-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

Note: Be sure to remove the spring-boot-starter-web dependency, otherwise the startup will report an error

picture

Well, the project is built. In fact, such a dependency is added. The detailed configuration will be introduced below.

What is a Predict (Assertion)?

Predicate comes from the java8 interface. Predicate takes one input parameter and returns a boolean result. This interface contains a variety of default methods to combine Predicates into other complex logic (such as: and, or, not). Pay attention to the official z number: code ape technology column, reply to keywords: 1111 Get Ali's internal performance tuning manual!

It can be used for interface request parameter verification, and to determine whether there are changes in old and new data that need to be updated.

Spring Cloud Gateway has many built-in Predicts. The source code of these Predicts is in the org.springframework.cloud.gateway.handler.predicate package. If you are interested, you can read it. Some built-in assertions are as follows:

picture

built-in assertions

The above 11 kinds of assertions Chen will not introduce how to configure here, the official document is very clear.

Official documentation: https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/

Let's take the last weight assertion as an example to introduce how to configure it. The configuration is as follows:

spring:
  cloud:
    gateway:
      ## 路由
      routes:
        ## id只要唯一即可,名称任意
        - id: gateway-provider_1
          uri: http://localhost:9024
          ## 配置断言
          predicates:
            ## Path Route Predicate Factory断言,满足/gateway/provider/**这个请求路径的都会被路由到http://localhost:9024这个uri中
            - Path=/gateway/provider/**
            ## Weight Route Predicate Factory,同一分组按照权重进行分配流量,这里分配了80%
            ## 第一个group1是分组名,第二个参数是权重
            - Weight=group1, 8
            
        ## id必须唯一
        - id: gateway-provider_2
          ## 路由转发的uri
          uri: http://localhost:9025
          ## 配置断言
          predicates:
            ## Path Route Predicate Factory断言,满足/gateway/provider/**这个请求路径的都会被路由到http://localhost:9024这个uri中
            - Path=/gateway/provider/**
            ## Weight Route Predicate Factory,同一分组按照权重进行分配流量,这里分配了20%
            ## 第一个group1是分组名,第二个参数是权重
            - Weight=group1, 2
  • 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.

The routing strategy is configured under routes, and the components are as follows:

  • id: the unique id of the route, the name is arbitrary
  • uri: the uri forwarded by the route
  • predicates: assertion configuration, you can configure multiple

The assertion naming in Spring Cloud Gateway is standardized, in the format: xxxRoutePredicateFactory.

For example, the assertion of weight: WeightRoutePredicateFactory​, then directly take the previous Weight when configuring.

If the default route forwards to two routes, it will be forwarded according to the order of configuration. The path is configured above: Path=/gateway/provider/**​, if no weight is configured, it must be forwarded to http:// localhost:9024.

But since the configuration configures the weight and the same group, the traffic is distributed according to the weight ratio.

What is a filter?

The concept of filter is very familiar. I have been in contact with it in Spring mvc. The function and life cycle of Gateway's filter are similar.

Gateway life cycle:

  • PRE: This filter is invoked before the request is routed. We can use this filter to implement authentication, select requested microservices in the cluster, log debugging information, etc.
  • POST: This filter is executed after routing to the microservice. Such filters can be used to add standard HTTP headers to responses, collect statistics and metrics, send responses from microservices to clients, and more.

Gateway's Filter can be divided into two types from the scope of action:

  • GatewayFilter: Applied to a single route or a group of routes (need to be configured in the configuration file).
  • GlobalFilter: Applied to all routes (no configuration required, global effect)

GatewayFilter (local filter)

There are many local filters built into Spring Cloud Gateway, as shown in the following figure:

picture

The local filter needs to be configured in the specified route to take effect, and it does not take effect by default.

Take the filter AddResponseHeaderGatewayFilterFactory as an example to add a Header to the original response. The configuration is as follows:

spring:
  cloud:
    gateway:
      ## 路由
      routes:
        ## id只要唯一即可,名称任意
        - id: gateway-provider_1
          uri: http://localhost:9024
          ## 配置断言
          predicates:
            ## Path Route Predicate Factory断言,满足/gateway/provider/**这个请求路径的都会被路由到http://localhost:9024这个uri中
            - Path=/gateway/provider/**
          ## 配置过滤器(局部)
          filters:
            - AddRespnotallow=X-Response-Foo, Bar
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

The browser requests and finds that the key-value pair X-Response-Foo=Bar already exists in the response header, as shown in the figure below:

picture

Note: The name of the filter only needs to write the prefix, and the filter name must be xxxGatewayFilterFactory (including custom).

More filter configurations can be found in the official documentation: https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/#gatewayfilter-factories

Although the built-in filters can solve many scenarios, it is inevitable that there are still some special needs that require customizing a filter. Let's introduce how to customize local filters.

Scenario: Simulate an authorization verification process. If the request header or request parameter contains a token, it will be released, otherwise it will be directly intercepted and return 401. The code is as follows:

/**
 * 名称必须是xxxGatewayFilterFactory形式
 * todo:模拟授权的验证,具体逻辑根据业务完善
 */
@Component
@Slf4j
public class AuthorizeGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthorizeGatewayFilterFactory.Config> {

    private static final String AUTHORIZE_TOKEN = "token";

    //构造函数,加载Config
    public AuthorizeGatewayFilterFactory() {
        //固定写法
        super(AuthorizeGatewayFilterFactory.Config.class);
        log.info("Loaded GatewayFilterFactory [Authorize]");
    }

    //读取配置文件中的参数 赋值到 配置类中
    @Override
    public List<String> shortcutFieldOrder() {
        //Config.enabled
        return Arrays.asList("enabled");
    }

    @Override
    public GatewayFilter apply(AuthorizeGatewayFilterFactory.Config config) {
        return (exchange, chain) -> {
            //判断是否开启授权验证
            if (!config.isEnabled()) {
                return chain.filter(exchange);
            }

            ServerHttpRequest request = exchange.getRequest();
            HttpHeaders headers = request.getHeaders();
            //从请求头中获取token
            String token = headers.getFirst(AUTHORIZE_TOKEN);
            if (token == null) {
                //从请求头参数中获取token
                token = request.getQueryParams().getFirst(AUTHORIZE_TOKEN);
            }

            ServerHttpResponse response = exchange.getResponse();
            //如果token为空,直接返回401,未授权
            if (StringUtils.isEmpty(token)) {
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                //处理完成,直接拦截,不再进行下去
                return response.setComplete();
            }
            /**
             * todo chain.filter(exchange) 之前的都是过滤器的前置处理
             *
             * chain.filter().then(
             *  过滤器的后置处理...........
             * )
             */
            //授权正常,继续下一个过滤器链的调用
            return chain.filter(exchange);
        };
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Config {
        // 控制是否开启认证
        private boolean enabled;
    }
}
  • 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.

Local filters need to be configured in routing to take effect, the configuration is as follows:

spring:
  cloud:
    gateway:
      ## 路由
      routes:
        ## id只要唯一即可,名称任意
        - id: gateway-provider_1
          uri: http://localhost:9024
          ## 配置断言
          predicates:
            ## Path Route Predicate Factory断言,满足/gateway/provider/**这个请求路径的都会被路由到http://localhost:9024这个uri中
            - Path=/gateway/provider/**
          ## 配置过滤器(局部)
          filters:
            - AddRespnotallow=X-Response-Foo, Bar
            ## AuthorizeGatewayFilterFactory自定义过滤器配置,值为true需要验证授权,false不需要
            - Authorize=true
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

At this time, directly access: http://localhost:9023/gateway/provider/port, without carrying token, return the following picture:

picture

Request parameters with token: http://localhost:9023/gateway/provider/port?token=abcdcdecd-ddcdeicd12, successfully returned, as shown below:

picture

The above-mentioned AuthorizeGatewayFilterFactory​only involves the pre-processing of the filter, and the post-processing is done in the then()​method in chain.filter().then()​. For details, see TimeGatewayFilterFactory in the project source code. The code will no longer be posted, as shown below:

picture

GlobalFilter (global filter)

Global filters are applied to all routes without developer configuration. Spring Cloud Gateway also has some built-in global filters, as shown in the figure below:

picture

The function of GlobalFilter​is actually the same as GatewayFilter​, except that the scope of GlobalFilter​is all routing configurations, rather than being bound to a specified routing configuration. Multiple GlobalFilters can specify the execution order of each GlobalFilter through the @Order or getOrder() method. The smaller the order value, the higher the priority of GlobalFilter execution.

Note that since there are two types of filters, pre and post, if the pre type filter has a smaller order value, it should be at the top of the pre filter chain, and if the post type filter has a smaller order value, then it should be at the top The bottom layer of the pre filter chain. The schematic diagram is as follows:

picture

Of course, in addition to the built-in global filter, you also need to customize the filter in actual work. Let's introduce how to customize it.

Scenario: Simulate the Access Log function of Nginx to record the relevant information of each request. code show as below:

/**
 * 实现GlobalFilter
 */
@Slf4j
@Component
@Order(value = Integer.MIN_VALUE)
public class AccessLogGlobalFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //filter的前置处理
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().pathWithinApplication().value();
        InetSocketAddress remoteAddress = request.getRemoteAddress();
        return chain
                //继续调用filter
                .filter(exchange)
                //filter的后置处理
                .then(Mono.fromRunnable(() -> {
            ServerHttpResponse response = exchange.getResponse();
            HttpStatus statusCode = response.getStatusCode();
            log.info("请求路径:{},远程IP地址:{},响应码:{}", path, remoteAddress, statusCode);
        }));
    }
}
  • 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.

Well, the global filter does not need to be configured on the route, it can be injected into the IOC container to take effect globally.

At this time, a request is sent, and the console prints the following information:

请求路径:/gateway/provider/port,远程IP地址:/0:0:0:0:0:0:0:1:64114,响应码:200 OK
  • 1.

How to integrate the registry?

The registration center is not integrated in the above demo, and each routing configuration specifies a fixed service uri, as shown in the following figure:

picture

What's the downside?

  • Once the IP address of the service is modified, the uri in the routing configuration must be modified
  • Load balancing cannot be achieved in the service cluster

At this time, an integrated registration center is needed, so that the gateway can automatically obtain uri (load balancing) from the registration center.

Of course, the registration center here chooses Nacos, and friends who are not familiar with it, please read the first article of Chen's "Spring Cloud Advanced" column: Fifty-five pictures tell you how strong Nacos, the soul ferryer of microservices, is?

Add Nacos dependencies in the pom file, as follows:

<!--nacos注册中心-->
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

Enable the registration center function on the startup class, as shown in the figure below:

picture

The address of the nacos registration center is specified in the configuration file:

spring:
  application:
    ## 指定服务名称,在nacos中的名字
    name: cloud-gateway
  cloud:
    nacos:
      discovery:
        # nacos的服务地址,nacos-server中IP地址:端口号
        server-addr: 127.0.0.1:8848
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

The only difference in the routing configuration is the routing uri​, format: lb://service-name, which is a fixed way of writing:

  • lb: fixed format, refers to obtaining microservices by name from nacos, and following the load balancing strategy
  • service-name: The service name of the nacos registration center, which is not in the form of an IP address

The complete configuration demo of the integrated Nacos registry is as follows:

spring:
  application:
    ## 指定服务名称,在nacos中的名字
    name: cloud-gateway
  cloud:
    nacos:
      discovery:
        # nacos的服务地址,nacos-server中IP地址:端口号
        server-addr: 127.0.0.1:8848
    gateway:
      ## 路由
      routes:
        ## id只要唯一即可,名称任意
        - id: gateway-provider_1
        ## 使用了lb形式,从注册中心负载均衡的获取uri
          uri: lb://gateway-provider
          ## 配置断言
          predicates:
            ## Path Route Predicate Factory断言,满足/gateway/provider/**这个请求路径的都会被路由到http://localhost:9024这个uri中
            - Path=/gateway/provider/**
          ## 配置过滤器(局部)
          filters:
            - AddRespnotallow=X-Response-Foo, Bar
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

Why load balancing can be enabled by specifying lb? As mentioned earlier, the global filter LoadBalancerClientFilter is responsible for routing and load balancing. You can see the following source code:

picture

How to achieve dynamic routing?

In the above examples, a series of configurations of the gateway are written into the configuration file of the project. Once the routing changes, the project must be rebuilt, which is very costly to maintain.

In fact, we can store the configuration of the gateway in the configuration center, which is managed by the configuration center in a unified manner. Once the route changes, it only needs to be modified in the configuration center, so that one modification can be achieved and more effective.

Of course, Nacos is used as the configuration center here, and the dependencies are added as follows:

<!--    nacos配置中心的依赖-->
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

In the bootstrap.yml file, specify Nacos as some related configuration of the configuration center:

spring:
  application:
    ## 指定服务名称,在nacos中的名字
    name: cloud-gateway
  cloud:
    nacos:
      ## todo 此处作为演示,仅仅配置了后缀,其他分组,命名空间根据需要自己配置
      config:
        server-addr: 127.0.0.1:8848
        ## 指定文件后缀未yaml
        file-extension: yaml
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

Create a configuration with dataId as cloud-gateway.yaml in the public namespace in nacos (the environment is not specified), and the configuration content is as follows:

picture

The configuration has been completed here. As for the effect, try it with your own hands......

How to customize global exception handling?

Through the previous test, we can see a phenomenon: once the routing microservice goes offline or loses connection, Spring Cloud Gateway directly returns an error page, as shown in the following figure:

picture

Obviously, this kind of exception information is not friendly, and the returned exception information must be customized in the front-end and back-end separation architecture.

Traditional Spring Boot services use @ControllerAdvice to wrap global exception handling, but because the service is offline, the request does not arrive.

Therefore, it is necessary to customize a layer of global exception handling in the gateway, so as to interact with the client more friendly.

Spring Cloud Gateway provides a variety of global processing methods. Today, Chen only introduces one of the methods, and the implementation is quite elegant.

Directly create a class GlobalErrorExceptionHandler​, implement ErrorWebExceptionHandler​, rewrite the handle method, the code is as follows:

/**
 * 用于网关的全局异常处理
 * @Order(-1):优先级一定要比ResponseStatusExceptionHandler低
 */
@Slf4j
@Order(-1)
@Component
@RequiredArgsConstructor
public class GlobalErrorExceptionHandler implements ErrorWebExceptionHandler {

 private final ObjectMapper objectMapper;

 @SuppressWarnings({"rawtypes", "unchecked", "NullableProblems"})
 @Override
 public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
  ServerHttpResponse response = exchange.getResponse();
  if (response.isCommitted()) {
   return Mono.error(ex);
  }

  // JOSN格式返回
  response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
  if (ex instanceof ResponseStatusException) {
   response.setStatusCode(((ResponseStatusException) ex).getStatus());
  }

  return response.writeWith(Mono.fromSupplier(() -> {
   DataBufferFactory bufferFactory = response.bufferFactory();
   try {
    //todo 返回响应结果,根据业务需求,自己定制
    CommonResponse resultMsg = new CommonResponse("500",ex.getMessage(),null);
    return bufferFactory.wrap(objectMapper.writeValueAsBytes(resultMsg));
   }
   catch (JsonProcessingException e) {
    log.error("Error writing response", ex);
    return bufferFactory.wrap(new byte[0]);
   }
  }));
 }
}
  • 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.

Well, the global exception handling has been customized. Let’s test it, and the JSON data is returned normally, as shown in the figure below:

picture


The style of JSON is customized according to the needs of the architecture.

Summarize

Spring Cloud Gateway shared here today, mainly introducing the following knowledge points:

  • Why do you need a gateway? Basic functions of the gateway
  • How to build a microservice gateway from scratch
  • The concept of Predict (assertion)
  • The concept of filters, Spring Cloud Gateway's built-in filters and how to customize them
  • How to integrate Nacos registration center and achieve load balancing
  • How to integrate Nacos to achieve dynamic routing, achieve one modification, and more effective effects
  • Handling of global exceptions

Do you think the introduction of Spring Cloud Gateway is over? Impossible, there will be more in-depth and practical introductions in the follow-up, and the next article will introduce...