background

In previous articles, we introduced Spring Cloud Netflix Zuul, a microservice Gateway, and two recent articles devoted to Spring Cloud’s new project, Spring Cloud Gateway, and its filter factory. This article describes migrating the microservices Gateway from Zuul to Spring Cloud Gateway.

Spring Cloud Netflix Zuul is an open source API gateway of Netflix. Under the microservice architecture, the gateway serves as the external portal to realize dynamic routing, monitoring, authorization, security, scheduling and other functions.

Zuul is based on Servlet 2.5 (using 3.x) and uses the blocking API. It does not support any persistent connections, such as WebSockets. The Gateway is built on top of Spring Framework 5, Project Reactor, and Spring Boot 2 and uses a non-blocking API. Excellent support for asynchronous non-blocking programming. The previous Spring series was mostly synchronous blocking, using the Thread-per-request processing model. Even if you annotated @async on a Spring MVC Controller method or returned a result of type DeferredResult or Callable, you would still just wrap a synchronous call to the method as a task and put it on a task queue in the thread pool. It’s still the Thread-per-request model. Websockets are supported in the Gateway, and because it is tightly integrated with Spring, it will be a much better development experience.

In a microservice-integration project, we integrated gateway, Auth permission services, and Backend services. Provides a set of microservice architecture, gateway service routing, authentication, and authorization authentication project cases. The architecture of the whole project is as follows:

See: Integration of gateway and Permission Services in microservices Architecture. This article uses the Zuul gateway upgrade from this project as an example.

Zuul gateway

In this project, the main functions of Zuul gateway are routing and forwarding, authentication and authorization, and secure access.

In Zuul, it is easy to configure dynamic routing and forwarding, such as:

zuul:
  ribbon:
    eager-load:
      enabled: true     # Zuul Hunger loading
  host:
    maxTotalConnections: 200
    maxPerRouteConnections: 20
  routes:
    user:
      path: /user/**
      ignoredPatterns: /consul
      serviceId: user
      sensitiveHeaders: Cookie,Set-Cookie
Copy the code

By default, Zuul filters sensitive information in the HTTP request header when routing requests, which we won’t cover here.

Request authentication is also configured in the gateway. In combination with Auth service, this function can be implemented through Zuul’s Pre filter. Of course, you can also use the Post filter to adapt and modify the results of the request.

In addition, flow limiting filters and circuit breakers can be configured, which will be added in the following sections.

Migrate to the Spring Cloud Gateway

I have created a new gateway-enhanced project because the changes are significant and it is not appropriate to modify on the basis of the previous Gateway project. The functions are as follows: routing and forwarding, weighted routing, circuit breaker, current limiting, authentication, blacklist and whitelist. This paper mainly realizes the following three functions based on:

  • Routing assertion
  • Filters (including global filters such as circuit breakers, current limiting, etc.)
  • Global authentication
  • The routing configuration
  • CORS

Rely on

The Spring Cloud Gateway version used in this article is 2.0.0.release. The major dependencies added are as follows, as detailed in the Github project.

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <! - < version > 2.0.1. RELEASE < / version > -- >
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-gateway-webflux</artifactId>
        </dependency>
    </dependencies>
        
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Finchley.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>        

Copy the code

Routing assertion

Spring Cloud Gateway supports both shortcut and Fluent apis for configuration files for route assertion, filter, and route definition. We’ll show you some of the features that are actually used in this project.

Route assertion The specific service that determines the route before the gateway forwards the request, usually according to the request path, request body, request mode (GET/POST), request address, request time, and request HOST. We mainly use the request path based method, as follows:

spring:
  cloud:
    gateway:
      routes:
      - id: service_to_web
        uri: lb://authdemo
        predicates:
        - Path=/demo/**
Copy the code

We define a route named service_to_web that forwards requests with the request path /demo/** to the AuthDemo service instance.

Our requirements for routing assertions in this project are not complex. Here are some other routing assertions configured through the Fluent API:

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route(r -> r.host("**.changeuri.org").and().header("X-Next-Url")
                        .uri("http://blueskykong.com"))
                .route(r -> r.host("**.changeuri.org").and().query("url")
                        .uri("http://blueskykong.com"))
                .build();
    }
Copy the code

In the route definition above, we configure the request HOST, request header, and request parameters. Multiple assertions can be configured in a route definition to take a relationship of yes or no.

The configuration added above is only an extension, and the reader can configure the assertions as needed.

The filter

Filters are divided into global filters and local filters. By implementing the GlobalFilter, GatewayFilter interface, we can customize the filter.

Global filter

In this project, we configured the following global filters:

  • Token bucket based flow limiting filter
  • Flow limiting filter based on leaky bucket algorithm
  • Global circuit breaker
  • Global authentication filter

Define global filter, can pass in the configuration file, add spring. Cloud. Gateway. The default – filters, or implement GlobalFilter interface.

Token bucket based flow limiting filter

Over time, tokens are added to the bucket at a constant 1/QPS interval (10ms if QPS=100), and no more if the bucket is full. Each request takes a Token and blocks or denies service if there is no more.

Another benefit of the token bucket is that it is easy to change the speed. As soon as the rate needs to be increased, the rate of the tokens into the bucket is increased as needed. While a certain number of tokens are added to the bucket on a timed (say, 100 milliseconds) basis, some variants calculate the number of tokens to be added in real time.

With the default implementation provided in the Spring Cloud Gateway, we need to introduce redis dependencies:

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

Perform the following configuration:

spring:
  redis:
    host: localhost
    password: pwd
    port: 6378
  cloud:
    default-filters:
      - name: RequestRateLimiter
        args:
          key-resolver: "#{@remoteAddrKeyResolver}"
          rate-limiter: "#{@customRateLimiter}"   # token
Copy the code

Note that two SpEL expressions are used in the configuration to define the configuration of the stream limiting key and the stream limiting. Therefore, we need to add the following configuration to the implementation:

    @Bean(name = "customRateLimiter")
    public RedisRateLimiter myRateLimiter(GatewayLimitProperties gatewayLimitProperties) {
        GatewayLimitProperties.RedisRate redisRate = gatewayLimitProperties.getRedisRate();
        if (Objects.isNull(redisRate)) {
            throw new ServerException(ErrorCodes.PROPERTY_NOT_INITIAL);
        }
        return new RedisRateLimiter(redisRate.getReplenishRate(), redisRate.getBurstCapacity());
    }
    
        @Bean(name = RemoteAddrKeyResolver.BEAN_NAME)
    public RemoteAddrKeyResolver remoteAddrKeyResolver(a) {
        return new RemoteAddrKeyResolver();
    }

Copy the code

In the above implementation, we initialize the two Bean instances RedisRateLimiter and RemoteAddrKeyResolver. RedisRateLimiter is the redis limiting property defined in the Gateway. And RemoteAddrKeyResolver lets us use custom, request-based addresses as stream-limiting keys. The definition of the flow limiting key is as follows:

public class RemoteAddrKeyResolver implements KeyResolver {
    private static final Logger LOGGER = LoggerFactory.getLogger(RemoteAddrKeyResolver.class);

    public static final String BEAN_NAME = "remoteAddrKeyResolver";

    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        LOGGER.debug("token limit for ip: {} ", exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
        returnMono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()); }}Copy the code

RemoteAddrKeyResolver implements the KeyResolver interface, overrides the interface defined in it, and returns the address in the request.

As above, the link filter based on token bucket algorithm is implemented, and the details will not be expanded.

Flow limiting filter based on leaky bucket algorithm

Bucket (Leaky Bucket) algorithm idea is very simple, water (request) to enter into the Bucket, Bucket at a certain speed of the water (interface response rate), when the water flow rate through the conference directly overflow (access frequency over the interface response rate), then I refused the request, it can be seen that Bucket algorithm can force limited data transfer rate.

This part of the implementation is referred to the GitHub project and the accompanying book at the end of the article, which will be skipped here.

Global circuit breaker

About Hystrix circuit breaker, it is a service fault-tolerant protection measure. Circuit breaker itself is a switching device, used to protect the circuit overload in the circuit, when there is a short circuit in the circuit, the circuit breaker can cut off the fault circuit in time to prevent overload, fire and other situations.

The function of circuit breaker mode is similar in microservice architecture. When a service unit fails, the fault monitoring of circuit breaker directly cuts off the original main logic call. For more information on circuit breakers and how Hystrix works, refer to the accompanying book at the end of this article.

Here we need to introduce the Spring-cloud-starter – Netflix-Hystrix dependency:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            <optional>true</optional>
        </dependency>
Copy the code

Add the following configuration:

      default-filters:
      - name: Hystrix
        args:
          name: fallbackcmd
          fallbackUri: forward:/fallbackcontroller
Copy the code

As shown above, the rest of the filters will be packaged with HystrixCommand and named fallBackcmd. We have also configured the optional fallbackUri. The degradation logic will be invoked and the request will be forwarded to the controller with the/fallBackController URI for processing. Define the degradation as follows:

    @RequestMapping(value = "/fallbackcontroller")
    public Map<String, String> fallBackController(a) {
        Map<String, String> res = new HashMap();
        res.put("code"."100");
        res.put("data"."service not available");
        return res;
    }
Copy the code
Global authentication filter

We implement a custom global filter to authenticate the validity of the request. The specific functions are not described here. The difference between the GlobalFilter interface and Webflux is the ServerWebExchange interface. The processing logic implemented before is determined by determining whether the external interface is an external interface (the external interface does not require login authentication).

public class AuthorizationFilter implements GlobalFilter.Ordered {

	//....

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        if (predicate(exchange)) {
            request = headerEnhanceFilter.doFilter(request);
            String accessToken = extractHeaderToken(request);

            customRemoteTokenServices.loadAuthentication(accessToken);
            LOGGER.info("success auth token and permission!");
        }

        return chain.filter(exchange);
    }
	// Raise the header token
    protected String extractHeaderToken(ServerHttpRequest request) {
        List<String> headers = request.getHeaders().get("Authorization");
        if (Objects.nonNull(headers) && headers.size() > 0) { // typically there is only one (most servers enforce that)
            String value = headers.get(0);
            if ((value.toLowerCase().startsWith(OAuth2AccessToken.BEARER_TYPE.toLowerCase()))) {
                String authHeaderValue = value.substring(OAuth2AccessToken.BEARER_TYPE.length()).trim();
                // Add this here for the auth details later. Would be better to change the signature of this method.
                int commaIndex = authHeaderValue.indexOf(', ');
                if (commaIndex > 0) {
                    authHeaderValue = authHeaderValue.substring(0, commaIndex);
                }
                returnauthHeaderValue; }}return null; }}Copy the code

Once the global filter is defined, the configuration is simple:

    @Bean
    public AuthorizationFilter authorizationFilter(CustomRemoteTokenServices customRemoteTokenServices, HeaderEnhanceFilter headerEnhanceFilter, PermitAllUrlProperties permitAllUrlProperties) {
        return new AuthorizationFilter(customRemoteTokenServices, headerEnhanceFilter, permitAllUrlProperties);
    }
Copy the code

Local filter

We often use local filters to increase or decrease the request and the corresponding head, increase or decrease the request path and other filters. What we use here is to remove the specified prefix of the request. This part of the prefix is only used by the user gateway for routing judgment. When the request is forwarded to a specific service, the prefix needs to be removed:

      - id: service_to_user
        uri: lb://user
        order: 8000
        predicates:
        - Path=/user/**
        filters:
        - AddRequestHeader=X-Request-Foo, Bar
        - StripPrefix=1
Copy the code

Also available through the Fluent API, as follows:

    @Bean
    public RouteLocator retryRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("retry_java", r -> r.path("/test/**")
                        .filters(f -> f.stripPrefix(1)
                                .retry(config -> config.setRetries(2).setStatuses(HttpStatus.INTERNAL_SERVER_ERROR)))
                        .uri("lb://user"))
                .build();
    }
Copy the code

In addition to setting the prefix filter, we also set the retry filter. See filter Factory: Retry filter in Spring Cloud Gateway

The routing configuration

The route definition is already listed in the example above, through the configuration file and the objects that define the RouteLocator. Note that the URI attribute in the configuration can be a specific service address (IP+ port number) or defined through service discovery plus load balancing: LB ://user: indicates the service instance forwarded to the user. This, of course, requires some configuration.

Introducing dependencies for service discovery:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
Copy the code

Gateway in the open spring. Cloud. Gateway. Discovery. A locator. Enabled = true.

CORS configuration

In Spring 5 Webflux, you can configure CORS by customizing WebFilter:

    private static final String ALLOWED_HEADERS = "x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN";
    private static final String ALLOWED_METHODS = "GET, PUT, POST, DELETE, OPTIONS";
    private static final String ALLOWED_ORIGIN = "*";
    private static final String MAX_AGE = "3600";

    @Bean
    public WebFilter corsFilter(a) {
        return (ServerWebExchange ctx, WebFilterChain chain) -> {
            ServerHttpRequest request = ctx.getRequest();
            if (CorsUtils.isCorsRequest(request)) {
                ServerHttpResponse response = ctx.getResponse();
                HttpHeaders headers = response.getHeaders();
                headers.add("Access-Control-Allow-Origin", ALLOWED_ORIGIN);
                headers.add("Access-Control-Allow-Methods", ALLOWED_METHODS);
                headers.add("Access-Control-Max-Age", MAX_AGE);
                headers.add("Access-Control-Allow-Headers",ALLOWED_HEADERS);
                if (request.getMethod() == HttpMethod.OPTIONS) {
                    response.setStatusCode(HttpStatus.OK);
                    returnMono.empty(); }}return chain.filter(ctx);
        };
    }
Copy the code

The above code implementation is relatively simple, the reader according to the actual need to configure parameters such as ALLOWED_ORIGIN.

conclusion

In high concurrency and potentially high latency scenarios, a basic requirement for a gateway to achieve high performance and high throughput is full link async without thread blocking. The synchronous blocking mode of Zuul gateway does not meet the requirements.

Spring Cloud Gateway is based on Webflux and perfectly supports asynchronous non-blocking programming. Many features are easier to implement. Spring5 must use Java 8, functional programming is an important feature of Java 8, and WebFlux supports functional programming to define routing endpoints to process requests.

With this implementation, we migrated the Gateway from Zuul to Spring Cloud Gateway. Rich routing assertions and filters are defined in the Gateway, which can be called and used directly through the configuration file or the Fluent API, very convenient. In performance, it is also better than the previous Zuul gateway.

For more detailed implementation principles and details, check out the author’s Spring Cloud Microservices Architecture In Progress, which will be published later this month. The main components of the Spring Cloud Finchley.RELEASE will be explained in principle and put into practice. The Gateway is based on the latest Spring Cloud Gateway.

This article source address:

Making:Github.com/keets2012/m…Or code cloud:Gitee.com/keets/micro…

Subscribe to the latest articles, welcome to follow my public number

reference

Spring Cloud Gateway (traffic limiting)