Gateway traffic limiting and dynamic routing

One, the current limit

Why limit the flow

Internet projects and traditional projects different Internet projects are exposed to the Internet, facing all netizens, may appear

1) The server is overloaded with requests

2) The server breaks down due to frequent access by malicious users

3) Web crawler. In these cases, we need to limit the flow of users’ access

How the current limit

Three common traffic limiting algorithms are listed below

Counter algorithm

A counter is used to accumulate the access times within a period. When the specified traffic limiting value is reached, the traffic limiting policy is triggered. In the period range class (1s), if the threshold has been reached at the first time (10ms), the remaining time (990ms) will reject all requests. (The period range may not be 1s, for example only)

Bucket algorithm

When a request enters the server, it is first put into the local container, and then the server processes the requests in the container at a certain rate. When the container is full of requests (starting from traffic limiting policy), the subsequent requests are discarded.



The picture is from Baidu Baike

Token bucket algorithm

The server will put the token into the bucket at a certain rate, and the request will enter. First, the token will be obtained, and the request will pass if the token is obtained. Otherwise, the request will be rejected.

The picture is from Baidu Baike

The difference between a leaky bucket algorithm and a token bucket

Leaky bucket algorithm is unable to deal with short bursts of traffic because the incoming requests are not fixed.

Token bucket algorithm, token inflow is fixed, can be stored in the bucket, can deal with short time burst traffic

Gateway implements the token bucket algorithm

Spring Cloud Gateway official provides RequestRateLimiterGatewayFilterFactory this class, applicable Redis and lua scripts for the way the token bucket.

This article only tests redis alone.

Introduction of depend on

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

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

Define KeyResolver

/ / into the bean
@Bean
public KeyResolver ipKeyResolver(a){
    return (ServerWebExchange exchange) -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
Copy the code

application.yml

server:
  port: 9000 Specify the port for the gateway service
spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes: Routing specifies which microservice to refer to when a request meets any criteria.
        - id: cookie_route
          uri: http://localhost:8087
          predicates:
            - Path=/tool/**
          filters:
            - name: RequestRateLimiter
              args:
           The Bean object name for the parser of the key used for limiting the flow. It uses SpEL expressions to get Bean objects from the Spring container based on #{@beanname}.
                key-resolver: '#{@ipKeyResolver}'  
                redis-rate-limiter.replenishRate: 1   # average token bucket fill rate per second
                redis-rate-limiter.burstCapacity: 3   Total token bucket capacity
Copy the code

Start the project and test it using JMeter

As you can see, in the case of one request per second, all three threads will only be successful if the bucket is filled with three tokens. Subsequent requests will only be successful once each token is loaded into the bucket.

Dynamic routing

As a unified gateway for microservices, the gateway service cannot be restarted every time the route configuration is modified. Dynamic routing means that the route configuration can be added when the service is running and takes effect without restarting the service. The following example uses Redis to persist the new routing information.


RouteDefinitionWriter also provides an interface for adding/deleting routes that can be used by RouteDefinitionWriter, but all new routes are stored in memory, not persisted, and will not be available after the service restarts. So we can realize RouteDefinitionRepository ourselves


These interfaces are provided by default for gateway management

You can see in the source interface is the result style development, so call which interface by way of request (POST/GET/DELETE), calls the format for http://ip:port/actuator/gateway/routes/ {param}

First introduce dependencies

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.72</version>
</dependency>
Copy the code

Expose all endpoints in the configuration file

management:
  endpoints:
    web:
      exposure:
        include: The '*'
  endpoint:
    health:
      show-details: always
Copy the code

Implement RouteDefinitionRepository interface

/** * Custom route persistence */
@Component
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository {

    private final static String GATETORY_ROUTER_KEY = "gateway_dynamic_route";

    // Start the redis service locally
    @Autowired
    private RedisTemplate redisTemplate;


    // Get routing information
    @Override
    public Flux<RouteDefinition> getRouteDefinitions(a) {
        List<RouteDefinition> routeDefinitions = new ArrayList<RouteDefinition>();
        // Get routing information from redis
        redisTemplate.opsForHash().values(GATETORY_ROUTER_KEY).stream().forEach(route -> {
            routeDefinitions.add(JSON.parseObject(route.toString(),RouteDefinition.class));
        });
        return Flux.fromIterable(routeDefinitions);
    }

    // Save the routing information
    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        return route.flatMap(routeDefinition -> {
            // Convert the route to json string and store it in Redis
      redisTemplate.opsForHash()
          .put(GATETORY_ROUTER_KEY,routeDefinition.getId(),JSON.toJSONString(routeDefinition));
            return Mono.empty();
        });
    }

    // Delete the route information
    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        return routeId.flatMap(id -> {
            // If the route ID is included
            if(redisTemplate.opsForHash().hasKey(GATETORY_ROUTER_KEY,id)){
                redisTemplate.opsForHash().delete(GATETORY_ROUTER_KEY,id);
                return Mono.empty();
            }
            // An exception was thrown if the route ID to be deleted was not found
            return Mono.defer(()->Mono.error(new Exception("routeDefinition not found:"+id))); }); }}Copy the code

Need to pay attention to

You need to invoke the interface that refreshes routes, no matter whether the interface is added or deleted

A POST request to http://localhost:8080/actuator/refresh


The POST request invokes the new interface

localhost:8080/actuator/gateway/routes/jd_router

Parameters in JSON format

{
    "id":"jd_router"."uri":"https://www.jd.com/"."order":"0"."filters":[
        {
            "name":"StripPrefix"."args": {"_genkey_0":"1"}}]."predicates":[
        {
            "name":"Path"."args": {"_genkey_0":"/jd/**"}}}]Copy the code

After refreshing, you can see the following information


DELETE Requests that the interface be deleted

localhost:8080/actuator/gateway/routes/jd_router

After refreshing, you can see that the routing information is empty

Finished!

Below is all the dependency information, if necessary can be copied

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>2020.0.4</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.72</version>
        </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>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

Copy the code