Detailed explanation and practice of Gateway---SpringCloud component (5)

An introduction

1.1 Introduction

Spring Cloud GateWay is a brand new project of Spring Cloud, aiming to replace Netflix Zuul. It is based on Spring5.0+SpringBoot2.0+WebFlux (based on the high-performance Reactor mode responsive communication framework Netty, asynchronous non-blocking model), etc. Technology development, performance is higher than Zuul, official testing, GateWay is 1.6 times that of Zuul, aiming to provide a simple, effective and unified API routing management method for microservice architecture.
Spring Cloud GateWay not only provides a unified routing method (reverse proxy) but also provides basic gateway functions based on the Filter (define filters to filter requests and complete some functions) chain method, such as: authentication, traffic Control, fusing, path rewriting, log monitoring, etc.

The location of the gateway in the architecture

Insert image description here

1.2.GateWay core concept

Zuul1.x blocking IO 2.x is based on Netty.
Spring Cloud GateWay is inherently asynchronous and non-blocking, based on the Reactor model.

A request -> the gateway matches according to certain conditions - after successful matching, the request can be forwarded to the specified service address; and in this process, we can perform some more specific controls (current limiting, logging , black list)

  • 路由(route): The most basic part of the gateway and the relatively basic working unit of the gateway. Routing consists of an ID, a target URL (the address to which the final route is directed), a series of assertions (matching condition judgment) and filters (refined control) . If the assertion is true, the route is matched.
  • 断⾔(predicates): With reference to the assertion java.util.function.Predicate in Java8, developers can match all content in the HTTP request (including request headers, request parameters, etc.) (similar to location matching in nginx). If the assertion is interrupted If the language matches the request, it will be routed.
  • 过滤器(filter): A standard Spring webFilter that uses filters to execute business logic before or after requests.

In a picture from the official website
Insert image description here
, Predicates assertion is our matching condition, and Filter can be understood as an omnipotent interceptor. With these two elements, combined with the target URL, you can achieve a A specific route forwarding.

1.3.GateWay core functions

Insert image description here

  • Routing : After the gateway is added, all requests must first go through the gateway, so the gateway must forward the request to a certain microservice according to certain rules. This process is called routing.
  • Permission control : When a request passes through routing, we can determine whether the requester is qualified to request the request, and intercept it if not.
  • Current limiting : When the request traffic is too high, the gateway will release the request at the speed that the downstream microservices can accept to avoid excessive service pressure.

2. Gateway entry case

Source code address
Gateway’s routing function, the basic steps are as follows:

  1. Create SpringBoot project gateway_server and introduce gateway dependencies
  2. Write startup class
  3. Write basic configuration: service port, application name
  4. Write routing rules
  5. Start the gateway service for testing

Insert image description here

2.1 gateway dependency

    <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

2.2 gateway configuration

server:
  port: 10010
spring:
  application:
    name: gateway-server # 服务名
  cloud:
    gateway:
      routes: #路由规则的列表
        - id: consumer-service # 当前路由的唯一标识
          uri: http://127.0.0.1:8080 # 路由的目标微服务地址
          predicates: # 断言,定义请求的匹配规则
            - Path=/consumer/** # Path代表按照路径匹配的规则,/consumer/**是指路径必须以/consumer开头

2.3 gateway test

Start the following three services.
Insert image description here
When we access http://localhost:10010/consumer/depart/get/1, we will first enter the gateway service. The assertion determines that it matches =/consumer/**, so the request will be proxied to http:/ /localhost:8080/consumer/depart/get/1
Insert image description here

Three Gateway service-oriented routing

3.1. Door case problem

Insert image description here
In the entry-level case, the target address of the route is hard-coded. In the case of microservices, it may be that the target service is a cluster, so it is obviously unreasonable to do so. We should go to the Eureka registration center to find a list of all instances corresponding to the service based on the name of the service, and load balance the service list!

Case module
Insert image description here

3.2.gateway-server combined with eureka steps

feign-consumer-8080 feign-eureka-server feign-provider-8081 These three modules follow the previous old parts, and the specific construction steps will not be repeated. Focus on the combination of gateway-server and eureka.

3.2.1. Add Eureka client dependency

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

3.2.2. Add Eureka configuration

Inject gateway service into eure

eureka:
  client:
    service-url: # EurekaServer地址
      defaultZone: http://127.0.0.1:10086/eureka/
  instance:
    prefer-ip-address: true #偏好使用ip地址,而不是host主机名
    ip-address: 127.0.0.1
    instance-id: ${
    
    spring.application.name}.${
    
    eureka.instance.ip-address}.${
    
    server.port}

3.2.3 Modify mapping configuration

Because we already have the Eureka client, we can get the address information of the service from Eureka, so there is no need to specify the IP address when mapping, but access it through the service name, and Zuul has integrated Ribbon's load balancing function.

server:
  port: 10010

eureka:
  client:
    service-url: # EurekaServer地址
      defaultZone: http://127.0.0.1:10086/eureka/
  instance:
    prefer-ip-address: true #偏好使用ip地址,而不是host主机名
    ip-address: 127.0.0.1
    instance-id: ${
    
    spring.application.name}.${
    
    eureka.instance.ip-address}.${
    
    server.port}

spring:
  application:
    name: gateway-server # 服务名
  cloud:
    gateway:
      routes: #路由规则的列表,可以有多个
        - id: feign-consumer # 当前路由的唯一标识
          uri: lb://feign-consumer  # 路由的目标微服务,lb:代表负载均衡,feign-consumer:代表服务id
          predicates: # 断言,定义请求的匹配规则
            - Path=/consumer/** # Path代表按照路径匹配的规则,/consumer/**是指路径必须以/consumer开头

The routing method of uri is modified here:

  • lb: Load balancing protocol, Ribbon will be used to achieve load balancing in the future
  • feign-consumer: service id

Insert image description here

3.2.4. Start testing

  • Start module

    Insert image description here

  • View eureka client

    Insert image description here

  • Visit and observe the results

    When we access http://localhost:10010/consumer/depart/get/1, we will first enter the gateway service and assert that the judgment is consistent with =/consumer/**, so the request will be proxied to http://localhost:8080/ consumer/depart/get/1
    Insert image description here

    Insert image description here

3.3 Local filter

GatewayFilter Factories are local filter factories in Gateway that act on a specific route and allow incoming HTTP requests or returned HTTP responses to be modified in some way.Insert image description here

3.3.1.Hystrix

The gateway does request routing and forwarding. If the called request is blocked, Hystrix needs to be used for thread isolation and circuit breaker to prevent failures.

1) Introduce Hystrix dependencies
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2) Turn on Hystrix and add @EnableHystrix
@EnableHystrix
@SpringBootApplication
public class GatewayApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(GatewayApplication.class, args);
    }
}
3) Define downgrade processing rules

It can be configured through default-filter and will apply to all routing rules.

spring:
  application:
    name: gateway-server # 服务名
  cloud:
    gateway:
      routes: #路由规则的列表,可以有多个
        - id: feign-consumer # 当前路由的唯一标识
          uri: lb://feign-consumer  # 路由的目标微服务,lb:代表负载均衡,feign-consumer:代表服务id
          predicates: # 断言,定义请求的匹配规则
            - Path=/consumer/** # Path代表按照路径匹配的规则,/consumer/**是指路径必须以/consumer开头
      default-filters: # 默认过滤项
        - name: Hystrix # 指定过滤工厂名称(可以是任意过滤工厂类型)
          args: # 指定过滤的参数
            name: fallbackcmd  # hystrix的指令名
            fallbackUri: forward:/fallbackTest # 失败后的跳转路径
hystrix:
    command:
      default:
        execution.isolation.thread.timeoutInMilliseconds: 1000 # 失败超时时长
  • default-filters: Default filter items, applied to all routing rules
    • name: filter factory name, specify Hystrix here, which means configuring the Hystrix type
    • args: Configure the configuration of the filter factory
      • name: Hystrix command name, used to configure information such as timeout duration and other information
      • fallbackUri: Jump path when downgrade fails
        Insert image description here
4) Define the downgrade processing function

Define a controller to write failure handling logic:

@RestController
public class FallbackController {
    
    

    @RequestMapping(value = "/fallbackTest")
    public Map<String, String> fallBackController() {
    
    
        Map<String, String> response = new HashMap<>();
        response.put("code", "502");
        response.put("msg", "服务超时");
        return response;
    }
}

5) Test

Restart the gateway without starting the consumer. After visiting http://localhost:10010/consumer/depart/get/1, observe the results after one second and find that the timeout method is used.
Insert image description here
Insert image description here

3.3.2.Routing prefix

1) Problem demonstration

Insert image description here

We previously used a mapping path like /consumer/** to represent the feign-consumer service. Therefore, all paths requesting the feign-consumer service must start with /consumer/**

For example, accessing: localhost:10010/consumer/depart/get/1 will be proxied to: http://localhost:8080/consumer/depart/get/1

Now, we define a new interface in the controller in feign-consumer:

@RestController
@RequestMapping("/test/depart")
public class TestController {
    
    
    @GetMapping("/get/{id}")
    public DepartVO getHandle(@PathVariable("id") int id) {
    
    
        DepartVO departVO = new DepartVO();
        departVO.setId(id);
        departVO.setName("测试名称");
        return departVO;
    }
}

The path of this interface is /test/depart/get/1, which does not start with /consumer/. When accessing: localhost:10010/test/depart/get/, it does not match the mapping path, so you will get 404.

Both /consumer/** and /test/** are controller paths in feign-consumer and cannot be used as the mapping path from the gateway to feign-consumer.

Therefore we need to define an additional mapping path, for example: /feign-consumer, the configuration is as follows

# 路由前缀配置
spring:
  application:
    name: gateway-server # 服务名
  cloud:
    gateway:
      routes: #路由规则的列表,可以有多个
        - id: feign-consumer # 当前路由的唯一标识
          uri: lb://feign-consumer  # 路由的目标微服务,lb:代表负载均衡,feign-consumer:代表服务id
          predicates: # 断言,定义请求的匹配规则
            - Path=/feign-consumer/** # Path代表按照路径匹配的规则,feign-consumer:代表服务id。/feign-consumer/**是指路径必须以/feign-consumer
      default-filters: # 默认过滤项
        - name: feign-consumer # 指定过滤工厂名称(可以是任意过滤工厂类型)
          args: # 指定过滤的参数
            name: fallbackcmd  # hystrix的指令名
            fallbackUri: forward:/fallbackTest # 失败后的跳转路径

So here comes the question:

When we visit:: localhost:10010/feign-consumer/consumer/depart/get/1, the mapping path/feign-consumer points to the user service and will be proxied to:http://localhost:8080/feign-consumer/consumer/depart/get/1

When we access: localhost:10010/feign-consumer/test/depart/get/1, the mapping path/feign-consumer points to the user service and will be proxied to:http://localhost:8080/feign-consumer/test/depart/get/1

In feign-consumer, both are /feign-consumer/consumer/depart/get/1wrong /feign-consumer/test/depart/get/1because there is one more /feign-consumer .

This /feign-consumer is the mapping path in the gateway and should not be proxied to the microservice. What should I do?

2) Remove routing prefix

The solution is very simple. When we access http://localhost:10010/feign-consumer/consumer/depart/get/1, the gateway uses the /feign-consumer mapping path to match the user microservice. When requesting the proxy, we only need to remove the /feign-consumer mapping path.

There happens to be a filter: one StripPrefixFilterFactorythat meets our needs.

https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.3.RELEASE/reference/html/#the-stripprefix-gatewayfilter-factory

Let’s modify the routing configuration just now:
Insert image description here

# 路由前缀配置
spring:
  application:
    name: gateway-server # 服务名
  cloud:
    gateway:
      routes: #路由规则的列表,可以有多个
        - id: feign-consumer # 当前路由的唯一标识
          uri: lb://feign-consumer  # 路由的目标微服务,lb:代表负载均衡,feign-consumer:代表服务id
          predicates: # 断言,定义请求的匹配规则
            - Path=/feign-consumer/** # Path代表按照路径匹配的规则,feign-consumer:代表服务id。/feign-consumer/**是指路径必须以/feign-consumer
          filters:
            - StripPrefix=1
      default-filters: # 默认过滤项
        - name: feign-consumer # 指定过滤工厂名称(可以是任意过滤工厂类型)
          args: # 指定过滤的参数
            name: fallbackcmd  # hystrix的指令名
            fallbackUri: forward:/fallbackTest # 失败后的跳转路径

At this time, when the gateway acts as a routing proxy, /feign-consumer will not be used as part of the target request path.
When we access:: localhost:10010/feign-consumer/consumer/depart/get/1, we will be proxy to:http://localhost:8080/consumer/depart/get/1

When we access: localhost:10010/feign-consumer/test/depart/get/1, the mapping path/feign-consumer points to the user service and will be proxied to:http://localhost:8080/test/depart/get/1

access test
Insert image description here

3.4 Global filters

The global filter Global Filter and the local GatewayFilter will be merged into a filter chain at runtime, and then org.springframework.core.Orderedexecuted according to order. The order can be specified through getOrder()methods or @Orderannotations.

3.4.1.GlobalFilter interface

The top-level interface of the global filter:
Insert image description here
To implement the interface, you must implement the filter method and complete the filtering logic inside the method. The parameters include:

  • ServerWebExchange: A domain object similar to Context that encapsulates service-related attributes such as Request and Response.

    Insert image description here

  • GatewayFilterChain: filter chain, used to release requests to the next filter

    Insert image description here

3.4.2. Filter order

By adding @Orderannotations, you can control the priority of filters, thereby determining the order in which filters are executed.

The execution of a filter includes "pre"two "post"processes: the logic written
in the method belongs to the pre stage , and the use stage belongs to the Post stage .GlobalFilter.filter()
GatewayFilterChain.filter().then()

The filter with the highest priority will be executed first in the pre process and last in the post process, as shown in the figure:

Insert image description here

There are many things we can do in the pre-stage, such as:

  • Login status judgment
  • Permission verification
  • Request current limit, etc.

3.4.3. Custom filters

To define a filter, you only need to implement GlobalFilter, but we have many ways to do it:

  • Method 1: Define a filter class and implement the GlobalFilter interface
  • Method 2: Combine lambda expression through @Configuration class
3.4.3.1. Login interceptor (implementing GlobalFilter interface method)

Now, through custom filters, a login verification function is simulated. The logic is very simple:

  • Get the access-token parameter in the user request parameters
  • Determine whether it is "admin"
    • If not, prove that you are not logged in and intercept the request.
    • If yes, prove that you have logged in and release the request.

code show as below:

@Order(0) // 通过注解声明过滤器顺序
@Component
public class LoginFilter implements GlobalFilter {
    
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    
    
        // 获取token
        String token = exchange.getRequest().getQueryParams().toSingleValueMap().get("access-token");
        // 判断请求参数是否正确
        if(StringUtils.equals(token, "admin")){
    
    
            // 正确,放行
            return chain.filter(exchange);
        }
        // 错误,需要拦截,设置状态码
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        // 结束任务
        return exchange.getResponse().setComplete();
    }
}

Insert image description here
test:

  • With wrong parameters:
    http://localhost:10010/feign-consumer/consumer/depart/get/1Insert image description here

  • With correct parameters:
    http://localhost:10010/feign-consumer/consumer/depart/get/1?access-token=adminInsert image description here

3.4.3.2.Multiple filter demonstration (lambda expression)
@Configuration
public class FilterConfiguration {
    
    

    @Bean
    @Order(-2)
    public GlobalFilter globalFilter1(){
    
    
        return ((exchange, chain) -> {
    
    
            System.out.println("过滤器1的pre阶段!");
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
    
    
                System.out.println("过滤器1的post阶段!");
            }));
        });
    }

    @Bean
    @Order(-1)
    public GlobalFilter globalFilter2(){
    
    
        return ((exchange, chain) -> {
    
    
            System.out.println("过滤器2的pre阶段!");
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
    
    
                System.out.println("过滤器2的post阶段!");
            }));
        });
    }

    @Bean
    @Order(0)
    public GlobalFilter globalFilter3(){
    
    
        return ((exchange, chain) -> {
    
    
            System.out.println("过滤器3的pre阶段!");
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
    
    
                System.out.println("过滤器3的post阶段!");
            }));
        });
    }
}

http://localhost:10010/feign-consumer/consumer/depart/get/1?access-token=admin
Insert image description here

3.5. Gateway current limiting

In addition to request routing and authentication, the gateway also plays a very important role: request current limiting. When the system faces high concurrent requests, in order to reduce the pressure on business processing services, it is necessary to limit the request flow in the gateway and release the requests at a certain rate.

Insert image description here

Common current limiting algorithms include:

  • Counter algorithm
  • leaky bucket algorithm
  • Token Bucket Algorithm

3.5.1. Principle of Token Bucket Algorithm

SpringGateway uses the token bucket algorithm. The principle of the token bucket algorithm is:

  • Prepare a token bucket with a fixed capacity, usually the upper limit of service concurrency.
  • At a fixed rate, tokens are generated and stored in the token bucket. If the number of tokens in the bucket reaches the upper limit, the tokens are discarded.
  • Each request call needs to obtain a token first. Only when the token is obtained can the execution continue. Otherwise, you can choose to wait or reject it directly.

Insert image description here

3.5.2. Current limiting in Gateway

SpringCloudGateway uses the token bucket algorithm, and its token-related information is recorded in redis, so we need to install redis and introduce Redis-related dependencies.

1) Introduce redis

Introduce Redis related dependencies:

<!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

Note: This is not an ordinary redis dependency, but a responsive Redis dependency, because SpringGateway is a responsive project based on WebFlux.

Configure the Redis address in application.yml:

spring:
  redis:
    host: localhost
2) Configure filter condition key

Gateway will record token-related information in Redis. We can define the rules of the token bucket ourselves, for example:

  • Set different token buckets for different request URI paths
  • Set different token buckets for different logged in users
  • Set different token buckets for different request IP addresses

A Key and Value pair in Redis is a token bucket. Therefore, the key generation rules are the bucket definition rules. The key generation rules in SpringCloudGateway are defined in KeyResolverthe interface:

public interface KeyResolver {
    
    

	Mono<String> resolve(ServerWebExchange exchange);

}

The return value of the method in this interface is the key generated for the token bucket. API description:

  • Mono: It is a single element container used to store the key of the token bucket.
  • ServerWebExchange: Context object, which can be understood as ServletContext, from which request, response, cookie and other information can be obtained

For example, for the above three token bucket rules, the key generation method is as follows:

  • Set different token buckets for different request URI paths, sample code:

    return Mono.just(exchange.getRequest().getURI().getPath());// 获取请求URI
    
  • Set different token buckets for different logged in users

    return exchange.getPrincipal().map(Principal::getName);// 获取用户
    
  • Set different token buckets for different request IP addresses

    return Mono.just(exchange.getRequest().getRemoteAddress().getHostName());// 获取请求者IP
    

Here we choose the last one, using the token bucket key of the IP address.

We configdefine a class in and configure a Bean instance of IpKeyResolver:

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class IpKeyResolver implements KeyResolver {
    
    
    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
    
    
        return Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
    }
}

3) Configure bucket parameters

In addition, the parameters of the token bucket need to be configured through the yaml file. There are 2 parameters:

  • replenishRate: The rate at which tokens are generated per second, basically the maximum number of requests allowed per second

  • burstCapacity: The capacity of the token bucket is the maximum number of tokens stored in the token bucket.

The complete configuration is as follows:

spring:
  application:
    name: ly-gateway
  cloud:
    gateway:
      default-filters: # 默认过滤项
      - StripPrefix=1 # 去除路由前缀
      - name: Hystrix # 指定过滤工厂名称(可以是任意过滤工厂类型)
        args: # 指定过滤的参数
          name: fallbackcmd  # hystrix的指令名
          fallbackUri: forward:/hystrix/fallback # 失败后的跳转路径
      - name: RequestRateLimiter #请求数限流 名字不能随便写
        args:
          key-resolver: "#{@ipKeyResolver}" # 指定一个key生成器
          redis-rate-limiter.replenishRate: 2 # 生成令牌的速率
          redis-rate-limiter.burstCapacity: 2 # 桶的容量

A filter is configured here: RequestRateLimiter, and three parameters are set:

  • key-resolver: "#{@ipKeyResolver}"It is a SpEL expression, written as #{@bean的名称}, ipKeyResolver is the name of the Bean we defined

  • redis-rate-limiter.replenishRate: The rate at which tokens are generated per second

  • redis-rate-limiter.burstCapacity: Capacity of token bucket

The effects that such a current limiting configuration can achieve:

  • Each IP address can initiate up to 2 requests per second.
  • If there are more than 2 requests per second, an exception status code of 429 will be returned.

3.8.3.Testing

If we quickly access http://localhost:10010/feign-consumer/consumer/depart/get/1?access-token=admin multiple times in the browser, we will get an error:
Insert image description here

429: Indicates that the number of requests is too many and current limiting is triggered.

Source code address

Source code address
05-gateway-eureka

Previous article: Detailed explanation and practice of Hystrix—SpringCloud components (4)

Guess you like

Origin blog.csdn.net/weixin_43811057/article/details/130671392