Netflix Zuul service gateway of Spring Cloud series

Keywords: Programming Spring Nginx Java Redis

What is Zuul

  

Zuul is the front door for all requests from devices and websites to the back end of the application. As an edge service application, zuul aims to realize dynamic routing, monitoring, flexibility and security. Zuul includes two main functions of request routing and filtering.

Zuul is the open-source microservice gateway of Netflix, which can be used with Eureka, Ribbon, Hystrix and other components. The core of zuul is a series of filters, which can perform the following functions:

  • Authentication and security: identify authentication requirements for each resource and reject those that do not meet the requirements
  • Review and monitoring: track meaningful data and statistics at the edge to bring accurate production attempts
  • Dynamic routing: dynamically route requests to different back-end clusters
  • Stress testing: gradually increase the traffic that only wants to cluster to understand the performance
  • Load allocation: allocate corresponding capacity for each load type, and discard requests exceeding the limit value
  • Static response processing: establish partial response directly at the edge, so as to avoid forwarding to the internal cluster\
  • Multi region flexibility: request routing across AWS regions aims to diversify the use of ELB (Elastic Load Balancing) and make the edge of the system closer to the users of the system

  

What is a service gateway

  

API Gateway (APIGW / API Gateway), as the name implies, is an API oriented, serial and centralized strong management and control service appearing on the system boundary. The boundary here is the boundary of enterprise IT system, which can be understood as enterprise application firewall, which mainly plays the role of isolating external access and internal system. Before the popularity of the concept of micro service, API Gateway has been born, such as the common front-end system in banking, securities and other fields. It also solves the problems of access authentication, message conversion, access statistics and so on.

The popularity of API gateway stems from the rise of interconnection requirements between mobile applications and enterprises in recent years. Mobile applications and enterprise interconnection make the objects supported by background services expand from a single Web application to a variety of usage scenarios, and each usage scenario has different requirements for background services. This not only increases the response of the background service, but also increases the complexity of the background service. With the concept of microservice architecture proposed, API gateway has become a standard component of microservice architecture.

API gateway is a server and the only entrance to the system. API gateway encapsulates the internal architecture of the system and provides customized API for each client. All clients and consumers access microservices through a unified gateway, and process all non business functions in the gateway layer. API gateway is not a necessary component in the microservice scenario, as shown in the following figure. Whether there is an API gateway or not, the back-end microservices can support the client's access well through the API.

However, for services with large number, high complexity and large scale, the introduction of API gateway also has a series of advantages:

  • The aggregation interface makes the service transparent to the caller and reduces the coupling between the client and the back end
  • Aggregate background services, save traffic, improve performance and enhance user experience
  • Provide API management functions such as security, flow control, filtering, caching, billing, monitoring, etc

  

Why use a gateway

  

  • Monomer application: when the browser initiates a request to the machine where the monomer application is located, the application queries the data from the database and returns it to the browser in the original way. For the monomer application, the gateway is not required.
  • Microservice: the application of microservice may be deployed in different computer rooms, different regions and different domain names. At this time, if the client (Browser / mobile phone / software tool) wants to request the corresponding service, it needs to know the specific IP or domain name URL of the machine. When there are many micro service instances, it is very difficult to remember, and it is too complex for the client to maintain. At this point, there is a gateway, and the client related requests are sent directly to the gateway. The gateway will determine the specific microservice address according to the request identification resolution, and then forward the request to the microservice instance. All the memory functions are operated by the gateway.

summary

  

If the client directly interacts with various microservices:

  • Clients will request different microservices many times, which increases the complexity of clients
  • There are cross domain requests, which are relatively complex to handle in certain scenarios
  • Identity authentication problem, each microservice needs independent identity authentication
  • Difficult to refactor, as the project iterates, microservices may need to be re divided
  • Some microservices may use firewall / browser unfriendly protocols, so direct access will be difficult

  

Therefore, we need the gateway between the client and the server. All external requests first pass through the micro service gateway. The client only needs to interact with the gateway and only needs to know the gateway address. This simplifies development and has the following advantages:

  • It is easy to monitor and collect monitoring data in microservice gateway and push it to external system for analysis
  • Easy to authenticate, can authenticate on the microservice gateway, and then forward the request to the microservice at the back end, so there is no need to authenticate in each microservice
  • Reduce the number of interactions between clients and various microservices

  

What's the problem with the gateway

  

The gateway has the functions of identity authentication and security, review and monitoring, dynamic routing, load balancing, caching, request fragmentation and management, static response processing, etc. Of course, the main responsibility is to contact with the outside world.

In summary, the gateway should have the following functions:

  • Performance: API high availability, load balancing, fault tolerance mechanism.
  • Security: Authority identity authentication, desensitization, traffic cleaning, back-end signature (to ensure the trusted call of the whole link), blacklist (restriction of illegal call).
  • Log: log recording. Once distributed, full link tracking is essential.
  • Cache: data cache.
  • Monitoring: record request response data, API time-consuming analysis, performance monitoring.
  • Current limit: flow control, peak flow control, can define a variety of current limit rules.
  • Gray scale: Online gray scale deployment can reduce the risk.
  • Routing: dynamic routing rules.

  

Common Gateway Solutions

  

Nginx + Lua

  

Nginx is developed by IgorSysoev for the Rambler.ru site with the second largest number of visitors in Russia. It is a high-performance HTTP and reverse proxy server. On the one hand, Ngnix can be used as a reverse proxy, on the other hand, it can be used as a static resource server.

  • Nginx is C language development, while Zuul is Java language development
  • Nginx load balancing is realized by using servers, while Zuul load balancing is realized by using Ribbon + Eureka to realize local load balancing
  • Nginx is suitable for server-side load balancing, Zuul is suitable for implementing gateway in microservice
  • Nginx is more powerful than Zuul because it can integrate some scripting languages (Nginx + Lua)
  • Nginx is a high-performance HTTP and reverse proxy server, and also an IMAP / POP3 /SMIP server. Zuul is an open source API Gateway server in Spring Cloud Netflix, which is essentially a Servlet application, providing dynamic routing, monitoring, flexibility, security and other edge services framework. Zuul is the front door for all requests from devices and websites to the back end of the application.
  • Nginx is suitable to be a portal gateway, which is the gateway of the whole global and the outermost. Zuul is a business gateway, which is mainly used to provide services for different clients and to aggregate business. Each microservice is deployed independently with a single responsibility. When providing external services, there needs to be something to aggregate the business.
  • Zuul can realize functions such as fusing and retrying, which is not available in Nginx.

  

Kong

  

Kong is an API management software provided by Mashape. It is based on nginix + Lua, but it provides a simpler configuration mode than Nginx. Apache Cassandra / PostgreSQL is used for data storage, and some excellent plug-ins are provided, such as validation, log, call frequency limit, etc. The most attractive thing about Kong is that it provides a large number of plug-ins to extend the application. By setting different plug-ins, various enhanced functions can be provided for the service.

Advantage: Based on Nginx, there is no problem in performance and stability. As a commercial software, Kong has done a lot of extension work on Nginx, and there are many paid commercial plug-ins. Kong also has its own paid Enterprise Edition, which includes technical support, use training services and API analysis plug-ins.

Disadvantages: if you use Spring Cloud, how can Kong integrate the existing service governance system?

  

Traefik

  

Traifik is a modern HTTP reverse proxy and load balancing tool developed by GO language to make deployment of microservices more convenient. It supports multiple back ends( Docker, Swarm, Kubernetes, Marathon, Mesos, Consul, Etcd, Zookeeper, BoltDB , Rest API, file… )From dynamic, dynamic application of its configuration file settings. Traifik has a simple website interface written based on AngularJS, supports rest API, hot update of configuration file, and does not need to restart the process. High availability cluster mode, etc.

Compared with Spring Cloud and Kubernetes, it is more suitable for Kubernetes at present.

  

Spring Cloud Netflix Zuul

  

Zuul is an open-source API gateway component of Netflix. Spring Cloud encapsulates it in an annotation way based on Spring Boot for out of the box use. At present, with the service governance system provided by Sring Cloud, request forwarding can be achieved, routing and Load Balance can be performed according to the configuration or default routing rules, and Hystrix can be seamlessly integrated.

Although we can realize the functions we want by customizing Filter, Zuul's design is based on single thread receiving request and forwarding processing, blocking IO, and does not support long connection. At present, Zuul is very weak. With Zuul 2.x's ticket skipping (Zuul 2.0 was released in May 2019), Spring Cloud launched its own Spring Cloud Gateway.

Zuul is dead, Spring Cloud Gateway is immortal (manual dog head).

  

Zuul 1.0

  

  

Zuul 2.0

  

  

Spring Cloud Gateway

  

As a gateway in the Spring Cloud ecosystem, Spring Cloud Gateway aims to replace Netflix Zuul, which not only provides a unified routing mode, but also provides the basic functions of the gateway based on the Filter chain. At present, the latest version of Spring Cloud refers to Zuul 1.x, which is Filter based, blocking IO and does not support long connection.

Zuul 2.x has been skipping tickets all the time. In May 2019, Netflix finally opened up Zuul 2.0, which supports asynchronous invocation mode. It's a long-awaited release. But Spring Cloud is no longer integrated with Zuul 2.x.

Spring Cloud Gateway is an API gateway built on the spring ecosystem, including Spring 5, Spring Boot 2 and Project Reactor. Spring Cloud Gateway aims to provide a simple and effective way to route to APIs, and provide them with cross domain concerns, such as: security, monitoring / indicators, current limiting, etc. Since Spring 5.0 supports Netty and Http2, and Spring Boot 2.0 supports Spring 5.0, it makes sense that Spring Cloud Gateway supports Netty and Http2.

  

summary

  

The functions of API gateway in microservice architecture are as follows:

The gateway we have learned at present focuses on the part of Aggr API Gateway, where we do aggregation service operations.

  

Environmental preparation

  

Zuul demo aggregation project. SpringBoot 2.2.4.RELEASE,Spring Cloud Hoxton.SR1.

  • Eureka server: Registry
  • eureka-server02: Registry
  • Product service: a product service, which provides an interface to query products based on the primary key http://localhost:7070/product/{id}
  • Order service: order service. It provides the order interface http://localhost:9090/order/{id} based on the primary key, and the order service calls the goods service.

  

  

Nginx implements API gateway

  

In the previous course, we have explained in detail the use of Nginx's functions such as reverse proxy and load balancing, which will not be covered here. Here, we mainly use Nginx to realize API gateway, which is convenient for you to learn and understand Zuul better.

  

download

  

Official website: http://nginx.org/en/download.html Download the stable version. To facilitate learning, download the Windows version.

  

install

  

After decompressing the file, run the nginx.exe file in the root path directly.

The default port of Nginx is 80. Access: http://localhost:80/ See the figure below to show that the installation is successful.

  

Configure routing rules

  

Enter the conf directory of Nginx, open the nginx.conf file, and configure the routing rules:

http {

	...

    server {
        listen       80;
        server_name  localhost;

        ...

        # Routing to Commodity Services
        location /api-product {
            proxy_pass http://localhost:7070/;
        }

        # Route to order service
        location /api-order {
            proxy_pass http://localhost:9090/;
        }

        ...
    }
    
    ...
    
}

  

Visit

  

Before, if we want to access the service, we must specify the specific service address by the client. Now we can access Nginx uniformly, and Nginx can implement the gateway function to route the request to the specific service.

Visit: http://localhost/api-product/product/1 The results are as follows:

  

Visit: http://localhost/api-order/order/1 The results are as follows:

  

Zuul realizes API gateway

  

Official documents: https://cloud.spring.io/spring-cloud-static/spring-cloud-netflix/2.2.1.RELEASE/reference/html/#router-and-filter-zuul

  

Build gateway service

  

Create project

  

Create zuul server project.

  

Add dependency

  

Add spring cloud netflix zuul dependency.

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>zuul-server</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- Inherit parent dependency -->
    <parent>
        <groupId>com.example</groupId>
        <artifactId>zuul-demo</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <!-- Project dependence -->
    <dependencies>
        <!-- spring cloud netflix zuul rely on -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
    </dependencies>

</project>

  

configuration file

  

  application.yml

server:
  port: 9000 # port

spring:
  application:
    name: zuul-server # apply name

  

Startup class

  

The @ EnableZuulProxy annotation needs to be enabled for the startup class.

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@SpringBootApplication
// Open Zuul comment
@EnableZuulProxy
public class ZuulServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZuulServerApplication.class, args);
    }

}

  

Configure routing rules

  

URL address routing

  

# Routing rules
zuul:
  routes:
    product-service:              # Route id customization
      path: /product-service/**   # Configure the mapping path of the request url
      url: http://localhost:7070 / ා the microservice address corresponding to the mapping path

  

Wildcard meaning:

wildcard Meaning Give an example explain
? Match any single character /product-service/? /product-service/a,/product-service/b,...
* Match any number of characters excluding subpath /product-service/* /product-service/aa,/product-service/bbb,...
** Match any number of characters including all child paths /product-service/** /product-service/aa,/product-service/aaa/b/ccc

  

Visit: http://localhost:9000/product-service/product/1 The results are as follows:

  

Service name routing

  

Microservices are generally composed of dozens or hundreds of services. For URL address routing, it is obviously unreasonable to manually specify a unique access address for each service instance.

Zuul supports the integrated development with Eureka. According to the serviceId, it automatically obtains the service address from the registry and forwards the request. The advantage of this is not only that all services of the application can be accessed through a single endpoint, but also that zuul's routing configuration is not modified when adding or removing service instances.

  

Add Eureka Client dependency

  

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

  

Configure registry and routing rules

  

# Routing rules
zuul:
  routes:
    product-service:              # Route id customization
      path: /product-service/**   # Configure the mapping path of the request url
      serviceId: product-service  # Automatically obtain the service address from the registry and forward the request according to the serviceId

# Configure Eureka Server registry
eureka:
  instance:
    prefer-ip-address: true       # Use ip address to register
    instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
  client:
    service-url:                  # Set service registry address
      defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/

  

Startup class

  

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@SpringBootApplication
// Open Zuul comment
@EnableZuulProxy
// Enable the EurekaClient annotation. If the current version is configured with Eureka registry, the annotation will be enabled by default
//@EnableEurekaClient
public class ZuulServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZuulServerApplication.class, args);
    }

}

  

Visit

  

Visit: http://localhost:9000/product-service/product/1 The results are as follows:

  

Simplify routing configuration

  

In order to make it convenient for everyone to use, Zuul provides the default routing configuration: the routing id is consistent with the microservice name, and the path corresponds to the microservice name / * * by default, so the following configuration is unnecessary to write again.

# Routing rules
zuul:
  routes:
    product-service:              # Route id customization
      path: /product-service/**   # Configure the mapping path of the request url
      serviceId: product-service  # Automatically obtain the service address from the registry and forward the request according to the serviceId

  

Visit

  

At this time, we do not configure the routing rules of any order service. To access: http://localhost:9000/order-service/order/1 The results are as follows:

  

Route exclusion

  

We can set resources that are not allowed to be accessed through routing exclusion. The resources allowed to be accessed can be set through routing rules.

  

URL address exclusion

  

# Routing rules
zuul:
  ignored-patterns: /**/order/**  # URL address exclusion, excluding all paths containing / order /
  # Not affected by routing exclusion
  routes:
    product-service:              # Route id customization
      path: /product-service/**   # Configure the mapping path of the request url
      serviceId: product-service  # Automatically obtain the service address from the registry and forward the request according to the serviceId

  

Service name exclusion

  

# Routing rules
zuul:
  ignored-services: order-service # Service name exclusion, multiple services comma separated, '*' excludes all
  # Not affected by routing exclusion
  routes:
    product-service:              # Route id customization
      path: /product-service/**   # Configure the mapping path of the request url
      serviceId: product-service  # Automatically obtain the service address from the registry and forward the request according to the serviceId

  

Routing prefix

  

zuul:
  prefix: /api

  

Visit

  

Visit: http://localhost:9000/api/product-service/product/1 The results are as follows:

  

Gateway filter

  

Zuul includes two core functions of request routing and filtering. The routing function is responsible for forwarding external requests to specific microservice instances, which is the basis of realizing unified access to external access. The filter function is responsible for intervening the request processing, which is the basis of realizing request verification, service aggregation and other functions. However, in fact, when the routing function is running, its routing mapping and request forwarding are completed by several different filters.

The route mapping is mainly completed by the pre type filter, which matches the request path with the configured routing rules to find the destination address to be forwarded; the part of request forwarding is completed by the routing type filter, which forwards the route address obtained by the pre type filter. Therefore, the filter can be said to be the core part of Zuul to realize the API gateway function. Each http request entering Zuul will go through a series of filter processing chains to get the request response and return it to the client.

  

Key nouns

  

  • Type: defines the stage in the routing process where the filter is applied. There are four types of pre, routing, post and error.
  • Execution order: in the same type, define the execution order of the filter. For example, the execution order of multiple pre types.
  • Condition: the condition required to execute the filter. true on, false off.
  • Action: the action to be performed if the condition is met. Specific operation.

  

Filter type

  

  • pre: filter executed before the request is routed to the source server
    • identity authentication
    • Routing
    • Request log
  • routing: handles filters that send requests to the source server
  • post: filter executed when the response returns from the source server
    • Add HTTP header to response
    • Collect statistics and metrics
    • Stream the response back to the client
  • Error: filter to be executed when an error occurs in the above phase

  

Introductory case

  

Create filter

  

The implementation of filter in Spring Cloud Netflix Zuul must contain four basic characteristics: filter type, execution order, execution condition and action (specific operation). These steps are four abstract methods defined in ZuulFilter interface:

package com.example.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

/**
 * Gateway filter
 */
@Component
public class CustomFilter extends ZuulFilter {

    private static final Logger logger = LoggerFactory.getLogger(CustomFilter.class);

    /**
     * Filter type
     *      pre
     *      routing
     *      post
     *      error
     *
     * @return
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * Execution sequence
     *      The smaller the value, the higher the priority
     *
     * @return
     */
    @Override
    public int filterOrder() {
        return 0;
    }

    /**
     * conditions for execution
     *      true open
     *      false Close
     *
     * @return
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * Action (specific operation)
     *      Concrete logic
     *
     * @return
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {
        // Get request context
        RequestContext rc = RequestContext.getCurrentContext();
        HttpServletRequest request = rc.getRequest();
        logger.info("CustomFilter...method={}, url={}",
                request.getMethod(),
                request.getRequestURL().toString());
        return null;
    }

}
  • filterType: this function needs to return a string representing the filter type, which is defined in each stage of the http request process. Four different life cycle process types are defined by default in Zuul, as follows:
    • pre: call before request is routed.
    • Routing: called when routing a request
    • post: routing and error filters are called after
    • Error: called when an error occurs while processing a request
  • filterOrder: defines the execution order of the filter through the int value. The smaller the value, the higher the priority.
  • shouldFilter: returns a boolean value to determine whether the filter is to be executed.
  • run: the specific logic of the filter. In this function, we can implement user-defined filtering logic to determine whether to intercept the current request without subsequent routing, or process the processing result after the request route returns the result.

  

Visit

  

Visit: http://localhost:9000/product-service/product/1 The console output is as follows:

CustomFilter...method=GET, url=http://localhost:9000/product-service/product/1

  

Unified authentication

  

Next, we judge whether users log in through token in the gateway filter to complete a unified authentication case.

  

Create filter

  

package com.example.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * Permission validation filter
 */
@Component
public class AccessFilter extends ZuulFilter {

    private static final Logger logger = LoggerFactory.getLogger(AccessFilter.class);

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        // Get request context
        RequestContext rc = RequestContext.getCurrentContext();
        HttpServletRequest request = rc.getRequest();
        // Get token in the form
        String token = request.getParameter("token");
        // Business logic processing
        if (null == token) {
            logger.warn("token is null...");
            // Request end, no further down request.
            rc.setSendZuulResponse(false);
            // Response status code, HTTP 401 error means the user does not have access rights
            rc.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
            // Response type
            rc.getResponse().setContentType("application/json; charset=utf-8");
            PrintWriter writer = null;
            try {
                writer = rc.getResponse().getWriter();
                // Response content
                writer.print("{\"message\":\"" + HttpStatus.UNAUTHORIZED.getReasonPhrase() + "\"}");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (null != writer)
                    writer.close();
            }
        } else {
            // Use token for authentication
            logger.info("token is OK!");
        }
        return null;
    }

}

  

Visit

  

Visit: http://localhost:9000/product-service/product/1 The results are as follows:

  

Visit: http://localhost:9000/product-service/product/1?token=abc123 The results are as follows:

  

The lifecycle of Zuul requests

  

  1. HTTP sends request to Zuul gateway
  2. Zuul gateway first passes through pre filter
  3. After verification, enter the routing filter, and then forward the request to the remote service. The remote service returns the result after execution. If there is an error, execute the error filter
  4. Continue with post filter
  5. Finally, the response is returned to the HTTP client

  

Unified handling of gateway filter exceptions

  

Create filter

  

package com.example.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.PrintWriter;

/**
 * Abnormal filter
 */
@Component
public class ErrorFilter extends ZuulFilter {

    private static final Logger logger = LoggerFactory.getLogger(ErrorFilter.class);

    @Override
    public String filterType() {
        return "error";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext rc = RequestContext.getCurrentContext();
        Throwable throwable = rc.getThrowable();
        logger.error("ErrorFilter..." + throwable.getCause().getMessage(), throwable);
        // Response status code, HTTP 500 server error
        rc.setResponseStatusCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
        // Response type
        rc.getResponse().setContentType("application/json; charset=utf-8");
        PrintWriter writer = null;
        try {
            writer = rc.getResponse().getWriter();
            // Response content
            writer.print("{\"message\":\"" + HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase() + "\"}");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != writer)
                writer.close();
        }
        return null;
    }

}

  

Simulated abnormality

  

Add the simulation exception code to the pre filter.

// Simulated abnormality
Integer.parseInt("zuul");

  

configuration file

  

Disable Zuul's default exception handling filter: SendErrorFilter

zuul:
  # Disable Zuul's default exception handling filter
  SendErrorFilter:
    error:
      disable: true

  

Visit

  

Visit: http://localhost:9000/product-service/product/1 The results are as follows:

  

Zuul and Hystrix seamlessly

  

In the Spring Cloud, the Zuul initiator contains the related dependency of the Hystrix. In the Zuul Gateway project, the Hystrix Dashboard service monitoring data (hystrix.stream) is provided by default, but the interface display of the monitoring panel will not be provided. In Spring Cloud, Zuul and Hystrix are seamlessly combined. We can easily implement fault-tolerant gateway processing.

  

Gateway monitoring

  

Zuul's dependency contains the related jar package of Hystrix, so we do not need to add the dependency of Hystrix in the project.

However, dashboard dependency should be added to projects that need to turn on data monitoring.

<!-- spring cloud netflix hystrix dashboard rely on -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>

  

configuration file

  

Open the hystrix.stream endpoint in the configuration file.

# Measurement monitoring and health examination
management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream

  

Startup class

  

Add the @ EnableHystrixDashboard annotation to the project startup class where data monitoring needs to be enabled.

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@SpringBootApplication
// Open Zuul comment
@EnableZuulProxy
// Enable data monitoring annotation
@EnableHystrixDashboard
public class ZuulServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZuulServerApplication.class, args);
    }

}

  

Access and view data

  

Visit: http://localhost:9000/hystrix The interface of monitoring center is as follows:

  

Multiple requests: http://localhost:9000/product-service/product/1?token=abc123 The results are as follows:

  

Gateway fuses

  

Prior to Edgware, Zuul provided the interface ZuulFallbackProvider to implement fallback processing. Starting with Edgware, Zuul provides the interface FallbackProvider to provide fallback processing.

Zuul's fallback fault-tolerant processing logic only deals with timeout exceptions. When a request is routed by zuul, as long as the service returns (including exceptions), zuul's fallback fault-tolerant logic will not be triggered.

Because for Zuul gateway, when request routing is distributed, the result is calculated by remote service. The remote service feeds back the exception information, and the Zuul gateway will not handle the exception, because it is unable to determine whether the error is the one that the application really wants to feed back to the client.

  

Code example

  

  ProductProviderFallback.java

package com.example.fallback;

import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;

/**
 * Service fault tolerance for goods and services
 */
@Component
public class ProductProviderFallback implements FallbackProvider {

    /**
     * return - Returns which service fallback handles. The name of the service is returned.
     * Recommendation - defines the specialized fallback logic for the specified service.
     * Recommendation - provides a fallback logic to handle all services.
     * Benefit - if a service times out, the specified fallback logic is executed. If a new service goes online, no fallback logic is provided. There is a general one.
     */
    @Override
    public String getRoute() {
        return "product-service";
    }

    /**
     * Service fault tolerance for goods and services
     *
     * @param route Fault tolerant service name
     * @param cause Service exception information
     * @return
     */
    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return new ClientHttpResponse() {
            /**
             * Set the header information of the response
             * @return
             */
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders header = new HttpHeaders();
                header.setContentType(new MediaType("application", "json", Charset.forName("utf-8")));
                return header;
            }

            /**
             * Set response body
             * Zuul The input stream data returned by this method will be read and output to the client through the output stream of HttpServletResponse.
             * @return
             */
            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream("{\"message\":\"Product service is not available. Please try again later.\"}".getBytes());
            }

            /**
             * ClientHttpResponse The status code of fallback of returns HttpStatus
             * @return
             */
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.INTERNAL_SERVER_ERROR;
            }

            /**
             * ClientHttpResponse The status code of fallback of returns int
             * @return
             */
            @Override
            public int getRawStatusCode() throws IOException {
                return this.getStatusCode().value();
            }

            /**
             * ClientHttpResponse The status code of fallback of returns String
             * @return
             */
            @Override
            public String getStatusText() throws IOException {
                return this.getStatusCode().getReasonPhrase();
            }

            /**
             * Recycling methods
             * Used to recycle resource objects that are currently opened by fallback logic.
             */
            @Override
            public void close() {
            }
        };
    }

}

  

Visit

  

Close product services, visit: http://localhost:9000/product-service/product/1?token=abc123 The results are as follows:

  

Gateway current limiting

  

As the name implies, flow restriction is to limit traffic, just like your broadband package has 1 G of traffic, it will be gone when it is used up. By limiting the current, we can control the QPS of the system well, so as to achieve the purpose of protecting the system. Zuul gateway components also provide current limiting protection. When the request concurrency reaches the threshold, the current limiting protection will be triggered automatically and an error result will be returned. Just provide error handling mechanism.

  

Why is it necessary to limit current

  

For example, Web services and external API s, the following types of services may cause the machine to be dragged down:

  • User growth is too fast (good news)
  • Because of a hot event (Weibo hot search)
  • Competitor crawler
  • Malicious request

These situations are unpredictable. I don't know when there will be 10 or even 20 times of traffic coming in. If this happens, it's too late to expand the capacity.

  

  

As can be seen from the figure above, internally speaking, upstream A and B services directly depend on downstream basic service C. in this scenario, service A and B are actually in A competitive relationship. If the concurrency threshold of service A is set too large, when the peak flow comes, it may directly drag down basic service C and affect the service B. Avalanche effect.

  

Current limiting algorithm

  

Common current limiting algorithms include:

  • Counter algorithm
  • Leaky Bucket algorithm
  • Token Bucket algorithm

  

Counter algorithm

  

Counter algorithm is the simplest and easiest algorithm in current limiting algorithm. For example, we stipulate that for the A interface, we can't access more than 100 times in one minute. Then we can do this: at the beginning, we can set A counter. When A request comes, the counter will be increased by 1. If the value of counter is greater than 100 and the interval between the request and the first request is within 1 minute, the current limit will be triggered. If the interval between the request and the first request is greater than 1 minute, the reset will be performed Counter counts again. The specific algorithm is as follows:

  

Although this algorithm is simple, there is a very fatal problem, that is, the critical problem. Let's see the following figure:

From the above figure, we can see that if a malicious user sends 100 requests at 0:59 and another 100 at 1:00, in fact, the user sends 200 requests in one second. What we just specified is that there are at most 100 requests in one minute, that is, at most 1.7 requests per second. Users can exceed our speed limit in an instant by burst requests at the reset node of the time window. It is possible for users to crush our application in an instant through this flaw of the algorithm.

  

There is also the problem of data waste. Our expectation is that 100 requests can be evenly distributed in this minute. If we have the upper limit of requests within 30s, the remaining half minute server will be idle, as shown in the following figure:

  

Leaky bucket algorithm

  

In fact, the leaky bucket algorithm is also very simple. It can be roughly considered as the process of water injection and leakage. It flows into the bucket at any rate and out of the bucket at a certain rate. When the water exceeds the bucket flow, it will be discarded, because the bucket capacity is constant, ensuring the overall rate.

The leaky bucket algorithm is implemented by using queue mechanism.

  

The main purpose of leaky bucket algorithm is to protect other people (services). If the inflow is large and the outflow is slow, the resource accumulation of the gateway may cause the gateway to be paralyzed. The target service may be able to handle a large number of requests, but the slow water output of the leaky bucket algorithm results in a waste of resources on the service side.

The leaky bucket algorithm cannot cope with the burst call. No matter how much flow is on the top, the speed of outflow from the bottom remains unchanged. Because the processing speed is fixed, the speed of the request coming in is unknown, and many requests may come in suddenly. The requests that are not processed in time are put in the bucket first. Since it is a bucket, there must be a capacity limit. If the bucket is full, the new requests will be discarded.

  

Token Bucket

  

Token bucket algorithm is an improvement of leaky bucket algorithm. Leaky bucket algorithm can limit the rate of request calls, while token bucket algorithm can limit the average rate of calls and also allow a certain degree of burst calls. In the token bucket algorithm, there is a bucket to store a fixed number of tokens. There is a mechanism in the algorithm to put tokens into the bucket at a certain rate. Each request call needs to obtain a token first. Only when the token is obtained, can it continue to execute. Otherwise, choose to wait for the available token or refuse directly. The action of putting token is continuous. If the number of tokens in the bucket reaches the upper limit, the token will be discarded.

The scenario is like this: there are a large number of tokens available in the bucket all the time. At this time, the incoming requests can be directly executed by the token. For example, if QPS is set to 100/s, then one second after the initialization of the current limiter, there will be 100 tokens in the bucket. When the service is started and the external service is provided, the current limiter can resist 100 instantaneous requests. When there is no token in the bucket, the request waits and finally executes at a certain rate.

  

In Zuul, Ratelimit component is used to realize current limiting. This algorithm is used, which is described as follows:

  • All requests need to get a usable token before they can be processed;
  • According to the current limiting size, a token is added to the bucket at a certain rate;
  • When the bucket is full, the newly added token will be discarded or rejected;
  • After the request arrives, the token in the token bucket must be obtained first, and then other business logic can be carried out with the token. After the business logic is processed, the token can be deleted directly;
  • The token bucket has a minimum limit. When the token in the bucket reaches the minimum limit, the token will not be deleted after the request is processed to ensure sufficient flow restriction.

The main purpose of the leaky bucket algorithm is to protect other people, while the token bucket algorithm is to protect itself, leaving the request pressure to the target service. Suppose a lot of requests come in suddenly, as long as the token is obtained, these requests will be processed and called the target service.

  

Add dependency

  

Zuul's current limiting protection needs to rely on the spring cloud zuul ratelimit component. The current limiting data is stored in Redis, so the Redis component must be added.

RateLimit official website document: https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit

<!-- spring cloud zuul ratelimit rely on -->
<dependency>
    <groupId>com.marcosbarbero.cloud</groupId>
    <artifactId>spring-cloud-zuul-ratelimit</artifactId>
    <version>2.3.0.RELEASE</version>
</dependency>
<!-- spring boot data redis rely on -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- commons-pool2 Object pool dependency -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

  

Global current limiting configuration

  

With a global throttling configuration, Zuul provides throttling protection for all services of the agent.

server:
  port: 9000 # port

spring:
  application:
    name: zuul-server # apply name
  # redis cache
  redis:
    timeout: 10000        # Connection timeout
    host: 192.168.10.101  # Redis server address
    port: 6379            # Redis server port
    password: root        # Redis server password
    database: 0           # Select which library, default 0 Library
    lettuce:
      pool:
        max-active: 1024  # Maximum connections, default 8
        max-wait: 10000   # Maximum connection blocking waiting time, in milliseconds, default - 1
        max-idle: 200     # Maximum idle connection, default 8
        min-idle: 5       # Minimum free connection, default 0

# Configure Eureka Server registry
eureka:
  instance:
    prefer-ip-address: true       # Use ip address to register
    instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
  client:
    service-url:                  # Set service registry address
      defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/

zuul:
  # Service restriction
  ratelimit:
    # Open current limiting protection
    enabled: true
    # Current limiting data storage mode
    repository: REDIS
    # Default policy list default configuration, global effective
    default-policy-list:
      - limit: 3
        refresh-interval: 60    # If there are more than three requests within 60s, the server will throw an exception, and the normal request can be recovered after 60s
        type:
          - origin
          - url
          - user

  

Zuul ratelimiter basic configuration item:

Configuration item Optional value Explain
enabled true/false Enable current limiting or not
repository Redis: Based on Redis, Redis related dependency must be introduced when using < br / > consul: Based on consul < br / > JPA: Based on SpringDataJPA, database needs to be used < br / > token bucket algorithm based flow restriction library written in Java: < br / > bucket4j ﹤ jcache < br / > bucket4j ﹤ hazelcast < br / > bucket4j ﹤ ignite < br / > bucket4j ﹤ infinispan Storage method of current limiting data, no default value is required
key-prefix String Current limiting key prefix
default-policy-list List of Policy Default policy
policy-list Map of Lists of Policy Custom policy
post-filter-order - postFilter filtering order
pre-filter-order - preFilter filter order

Bucket4j Implementation needs to be relevant bean @Qualifier("RateLimit"):

  • JCache - javax.cache.Cache
  • Hazelcast - com.hazelcast.core.IMap
  • Ignite - org.apache.ignite.IgniteCache
  • Infinispan - org.infinispan.functional.ReadWriteMap

  

Policy restriction policy configuration item description:

term Explain
limit Limit of requests per unit time
quota Cumulative request time limit in unit time (seconds), unnecessary parameter
refresh-interval Unit time (seconds), default 60 seconds
type Flow restriction method: < br / > origin: access IP flow restriction < br / > URL: access URL flow restriction < br / > User: specific user or user group flow restriction (for example, non member users are limited to download only one file per minute) < br / > URL ﹐ pattern < br / > role < br / > http ﹐ method

  

Visit

  

Visit: http://localhost:9000/product-service/product/1?token=abc123 The console results are as follows:

ErrorFilter...com.netflix.zuul.exception.ZuulException: 429 TOO_MANY_REQUESTS

  

View Redis

127.0.0.1:6379> keys *
1) "zuul-server:product-service:0:0:0:0:0:0:0:1:/product/1:anonymous"
2) "zuul-server:product-service:0:0:0:0:0:0:0:1:/product/1:anonymous-quota"

  

Local current limiting configuration

  

With local current limiting configuration, Zuul only provides current limiting protection for configured services.

zuul:
  # Service restriction
  ratelimit:
    # Open current limiting protection
    enabled: true
    # Current limiting data storage mode
    repository: REDIS
    # Policy list custom configuration, local effective
    policy-list:
      # Specify the service name to be restricted
      order-service:
        - limit: 5
          refresh-interval: 60  # If the request is more than 5 times in 60s, the server will throw an exception, and the normal request can be recovered after 60s
          type:
            - origin
            - url
            - user

  

Visit: http://localhost:9000/order-service/order/1?token=abc123 The console results are as follows:

ErrorFilter...com.netflix.zuul.exception.ZuulException: 429 TOO_MANY_REQUESTS

  

View Redis

127.0.0.1:6379> keys *
1) "zuul-server:order-service:0:0:0:0:0:0:0:1:/order/1:anonymous-quota"
2) "zuul-server:order-service:0:0:0:0:0:0:0:1:/order/1:anonymous"

  

Custom current limiting policy

  

If you want to control the current limiting policy by yourself, you can add your own policy logic by customizing the implementation of RateLimitKeyGenerator.

Modify the commodity service control layer code as follows, and add / product/single:

package com.example.controller;

import com.example.pojo.Product;
import com.example.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/product")
public class ProductController {

    @Autowired
    private ProductService productService;

    /**
     * Query goods according to the primary key
     *
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public Product selectProductById(@PathVariable("id") Integer id) {
        return productService.selectProductById(id);
    }

    /**
     * Query goods according to the primary key
     *
     * @param id
     * @return
     */
    @GetMapping("/single")
    public Product selectProductSingle(Integer id) {
        return productService.selectProductById(id);
    }

}

  

Custom current limiting policy class.

package com.example.ratelimit;

import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.RateLimitUtils;
import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.properties.RateLimitProperties;
import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.support.DefaultRateLimitKeyGenerator;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

/**
 * Custom current limiting policy
 */
@Component
public class RateLimitKeyGenerator extends DefaultRateLimitKeyGenerator {

    public RateLimitKeyGenerator(RateLimitProperties properties, RateLimitUtils rateLimitUtils) {
        super(properties, rateLimitUtils);
    }

    /**
     * Current limiting logic
     *
     * @param request
     * @param route
     * @param policy
     * @return
     */
    @Override
    public String key(HttpServletRequest request, Route route, RateLimitProperties.Policy policy) {
        // Restrict the flow of the same id value in the request parameter
        return super.key(request, route, policy) + ":" + request.getParameter("id");
    }

}

  

Multiple visits: http://localhost:9000/api/product-service/product/single?token=abc123&id=1 After being restricted, immediately change the id=2 to access the discovery service again, and then continue to visit it many times. It is found that the changed id=2 is also restricted. Redis information is as follows:

127.0.0.1:6379> keys *
1) "zuul-server:product-service:0:0:0:0:0:0:0:1:/product/single:anonymous:1"
2) "zuul-server:product-service:0:0:0:0:0:0:0:1:/product/single:anonymous:2"

  

error handling

  

Configure the error type gateway filter for processing. Modify the previous ErrorFilter to make it universal.

package com.example.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.zuul.util.ZuulRuntimeException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * Abnormal filter
 */
@Component
public class ErrorFilter extends ZuulFilter {

    private static final Logger logger = LoggerFactory.getLogger(ErrorFilter.class);

    @Override
    public String filterType() {
        return "error";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext rc = RequestContext.getCurrentContext();
        ZuulException exception = this.findZuulException(rc.getThrowable());
        logger.error("ErrorFilter..." + exception.errorCause, exception);

        HttpStatus httpStatus = null;
        if (429 == exception.nStatusCode)
            httpStatus = HttpStatus.TOO_MANY_REQUESTS;

        if (500 == exception.nStatusCode)
            httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;

        // Response status code
        rc.setResponseStatusCode(httpStatus.value());
        // Response type
        rc.getResponse().setContentType("application/json; charset=utf-8");
        PrintWriter writer = null;
        try {
            writer = rc.getResponse().getWriter();
            // Response content
            writer.print("{\"message\":\"" + httpStatus.getReasonPhrase() + "\"}");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != writer)
                writer.close();
        }
        return null;
    }

    private ZuulException findZuulException(Throwable throwable) {
        if (throwable.getCause() instanceof ZuulRuntimeException)
            return (ZuulException) throwable.getCause().getCause();

        if (throwable.getCause() instanceof ZuulException)
            return (ZuulException) throwable.getCause();

        if (throwable instanceof ZuulException)
            return (ZuulException) throwable;
        return new ZuulException(throwable, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, null);
    }

}

Another way is to implement org.springframework.boot.web.servlet.error.ErrorController rewriting getErrorPath(), which is not covered in this article.

  

Multiple visits: http://localhost:9000/product-service/product/1?token=abc123 The results are as follows:

  

Gateway tuning

  

Use Zuul's Spring Cloud microservice structure chart:

As can be seen from the above figure. The overall request logic is relatively complex. In the absence of Zuul gateway, when the client requests service, there is also the possibility of request timeout. When Zuul gateway is added, the possibility of request timeout is more obvious.

When the request is routed to the service through Zuul gateway and waiting for the service to return a response, Zuul also has timeout control in the process. The underlying layer of Zuul uses Hystrix + Ribbon to implement request routing.

  

  

Hytrix in Zuul uses thread pool isolation mechanism to provide request routing implementation, and its default timeout is 1000ms. The default timeout length of Ribbon bottom layer is 5000 Ms. If the Hystrix times out, the timeout exception will be returned directly. If the Ribbon times out and the Hystrix does not, the Ribbon will automatically poll and retry the service cluster until the Hystrix times out. If the Hystrix timeout is less than the Ribbon timeout, the Ribbon will not poll and retry the service cluster.

  

configuration file

  

There are two configurable timeout positions in Zuul: Hystrix and Ribbon. The specific configuration is as follows:

zuul:
  # Open Zuul gateway and try again
  retryable: true

# Hystrix timeout settings
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 10000  # Thread pool isolation, default timeout 1000ms

# Ribbon timeout setting: recommended setting is less than Hystrix
ribbon:
  ConnectTimeout: 5000                    # Timeout for connection request: default timeout 1000ms
  ReadTimeout: 5000                       # Timeout of request processing: default timeout 1000ms
  # retry count
  MaxAutoRetries: 1                       # MaxAutoRetries means to access the original node under the service cluster (same path access)
  MaxAutoRetriesNextServer: 1             # MaxAutoRetriesNextServer means to access other nodes under the service cluster (exchange server)
  # Ribbon on Retry
  OkToRetryOnAllOperations: true

  

Add dependency

  

Spring Cloud Netflix Zuul gateway retry mechanism requires spring retry component.

<!-- spring retry rely on -->
<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>

  

Startup class

  

Startup class needs to open @ EnableRetry retry annotation.

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.retry.annotation.EnableRetry;

@SpringBootApplication
// Open Zuul comment
@EnableZuulProxy
// Enable the EurekaClient annotation. If the current version is configured with Eureka registry, the annotation will be enabled by default
//@EnableEurekaClient
// Enable data monitoring annotation
@EnableHystrixDashboard
// Enable retry comment
@EnableRetry
public class ZuulServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZuulServerApplication.class, args);
    }

}

  

Analog timeout

  

Commodity service simulation timed out.

package com.example.controller;

import com.example.pojo.Product;
import com.example.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/product")
public class ProductController {

    @Autowired
    private ProductService productService;

    /**
     * Query goods according to the primary key
     *
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public Product selectProductById(@PathVariable("id") Integer id) {
        // Analog timeout
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return productService.selectProductById(id);
    }

}

  

Visit

  

Access before configuration: http://localhost:9000/product-service/product/1?token=abc123 The results are as follows (gateway service degradation triggered):

  

Access after configuration: http://localhost:9000/product-service/product/1?token=abc123 The results are as follows:

Thinking: according to the current configuration, what happens if you access the order service?

  

Zuul and Sentinel integration

  

Sentinel supports current limiting for mainstream API gateways such as Spring Cloud Gateway and Netflix Zuul.

Official documents:

  

Gateway current limiting and fault tolerance

  

Create project

  

Create zuul server sentinel project.

  

Add dependency

  

Use alone to add sentinel zuul adapter dependency.

If you want to use it with Sentinel Starter, you need to add the spring cloud Alibaba sentinel gateway dependency. At the same time, you need to add the spring cloud starter Netflix Zuul dependency to make the Zuul automatic configuration class in the spring cloud Alibaba sentinel gateway module take effect.

At the same time, please set the spring.cloud.sentinel.filter.enabled configuration item to false (if you see the URL resource on the gateway flow control console, this configuration item is not set to false).

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>zuul-server-sentinel</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- Inherit parent dependency -->
    <parent>
        <groupId>com.example</groupId>
        <artifactId>zuul-demo</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <!-- Project dependence -->
    <dependencies>
        <!-- spring cloud netflix zuul rely on -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
        <!-- netflix eureka client rely on -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- Use alone -->
        <!-- sentinel zuul adapter rely on -->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-zuul-adapter</artifactId>
        </dependency>
        <!-- and Sentinel Starter Cooperative use -->
        <!--
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
        </dependency>
        -->
    </dependencies>

</project>

  

configuration file

  

server:
  port: 9001 # port

spring:
  application:
    name: zuul-server-sentinel # apply name
  cloud:
    sentinel:
      filter:
        enabled: false

# Configure Eureka Server registry
eureka:
  instance:
    prefer-ip-address: true       # Use ip address to register
    instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
  client:
    service-url:                  # Set service registry address
      defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/

  

Gateway service configuration class

  

Configure the gateway service filter and the gateway restriction rules.

package com.example.config;

import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.zuul.fallback.ZuulBlockFallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.zuul.filters.SentinelZuulErrorFilter;
import com.alibaba.csp.sentinel.adapter.gateway.zuul.filters.SentinelZuulPostFilter;
import com.alibaba.csp.sentinel.adapter.gateway.zuul.filters.SentinelZuulPreFilter;
import com.netflix.zuul.ZuulFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.Set;

/**
 * Gateway service configuration class
 */
@Configuration
public class ZuulConfig {

    // The underlying layer inherits ZuulFilter
    @Bean
    public ZuulFilter sentinelZuulPreFilter() {
        // We can also provider the filter order in the constructor.
        return new SentinelZuulPreFilter();
    }

    // The underlying layer inherits ZuulFilter
    @Bean
    public ZuulFilter sentinelZuulPostFilter() {
        return new SentinelZuulPostFilter();
    }

    // The underlying layer inherits ZuulFilter
    @Bean
    public ZuulFilter sentinelZuulErrorFilter() {
        return new SentinelZuulErrorFilter();
    }

    /**
     * Spring Execute this method when the container is initialized
     */
    @PostConstruct
    public void doInit() {
        // Load gateway current restriction rules
        initGatewayRules();
    }

    /**
     * Gateway current restriction rules
     */
    private void initGatewayRules() {
        Set<GatewayFlowRule> rules = new HashSet<>();
        /*
            resource: Resource name, which can be the route name in the gateway or the user-defined API group name
            count: Current limiting threshold
            intervalSec: Statistics time window, unit is second, default is 1 second
         */
        rules.add(new GatewayFlowRule("order-service")
                .setCount(3) // Current limiting threshold
                .setIntervalSec(60)); // Statistics time window, unit is second, default is 1 second
        // Load gateway current restriction rules
        GatewayRuleManager.loadRules(rules);
    }

}

  

Startup class

  

  ZuulServerSentinelApplication.java

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@SpringBootApplication
// Open Zuul comment
@EnableZuulProxy
// Enable the EurekaClient annotation. If the current version is configured with Eureka registry, the annotation will be enabled by default
//@EnableEurekaClient
public class ZuulServerSentinelApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZuulServerSentinelApplication.class, args);
    }

}

  

Visit

  

Multiple visits: http://localhost:9001/order-service/order/1 When current limiting is triggered, a fixed prompt will be returned:

  

Custom current limiting processing (Gateway fusing)

  

Processing flow after flow restriction:

  • After current restriction, you can customize the return parameters. By implementing the ZuulBlockFallbackProvider interface, the default implementation is DefaultBlockFallbackProvider.
  • The default fallback route rule is route ID or custom API group name.

  

Write current limiting processing class

  

  OrderBlockFallbackProvider.java

package com.example.fallback;

import com.alibaba.csp.sentinel.adapter.gateway.zuul.fallback.BlockResponse;
import com.alibaba.csp.sentinel.adapter.gateway.zuul.fallback.ZuulBlockFallbackProvider;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Service fault tolerance for order service
 */
public class OrderBlockFallbackProvider implements ZuulBlockFallbackProvider {

    private Logger logger = LoggerFactory.getLogger(OrderBlockFallbackProvider.class);

    @Override
    public String getRoute() {
        return "order-service"; // Service name
    }

    @Override
    public BlockResponse fallbackResponse(String route, Throwable cause) {
        logger.error("{} Service triggered current limiting", route);
        if (cause instanceof BlockException) {
            return new BlockResponse(429, "Service access pressure is too high. Please try again later.", route);
        } else {
            return new BlockResponse(500, "System error, please contact the administrator.", route);
        }
    }

}

  

Register the throttling class to Zuul container

  

  ZuulConfig.java

// Execute this method when Spring container initializes
@PostConstruct
public void doInit() {
    // Register FallbackProvider
    ZuulBlockFallbackManager.registerProvider(new OrderBlockFallbackProvider());
    // Load gateway current restriction rules
    initGatewayRules();
}

  

Multiple visits: http://localhost:9001/order-service/order/1 The user-defined prompt is returned after the current limit is triggered:

  

High availability gateway

  

The industry usually uses 9 to measure the usability of the website. For example, the usability of QQ is 4 9. That is to say, QQ can guarantee that the service is available 99.99% of the time in a year, only 0.01% of the time is unavailable, about 53 minutes at most.

For most websites, 2 9s are basically available; 3 9s are highly available; 4 9s are highly available with automatic recovery capability.

The main means to achieve high availability are redundant backup of data and failure transfer of services. What can we do about these two means and how can they be reflected in the gateway? There are mainly the following directions:

  • Cluster deployment
  • load balancing
  • Health examination
  • Automatic restart of nodes
  • Fuse
  • service degradation
  • Interface retry

  

Nginx + gateway cluster for high availability gateway

  

  

download

  

Official website: http://nginx.org/en/download.html Download the stable version. To facilitate learning, download the Windows version.

  

install

  

After decompressing the file, run the nginx.exe file in the root path directly.

The default port of Nginx is 80. Access: http://localhost:80/ See the figure below to show that the installation is successful.

  

Configure gateway cluster

  

Enter the conf directory of Nginx, open the nginx.conf file, and configure the gateway cluster:

http {

	...

    # Gateway cluster
	upstream gateway {
		server 127.0.0.1:9000;
		server 127.0.0.1:9001;
	}
	
    server {
        listen       80;
        server_name  localhost;

        ...

        # Proxy gateway cluster, load balancing call
		location / {
            proxy_pass http://gateway;
        }

        ...
    }
    
    ...
    
}

  

Visit

  

Start two gateway servers http://localhost:9000 /, http://localhost:9001 /, and related services.

Visit: http://localhost/product-service/product/1 Implement high availability gateway.

  

summary

  

When a request comes, it first passes through the Layer-1 load of Nginx to the gateway, and then the gateway loads it to the real back-end. If there is a problem in the back-end, the gateway will retry the access. After multiple accesses, it still returns the failure. You can return the result immediately by fusing or service degradation. Moreover, due to load balancing, the gateway may not access the wrong back end when it retries.

At this point, all the knowledge points of Zuul service gateway are finished.

This paper adopts Intellectual sharing "signature - non-commercial use - no deduction 4.0 international" License Agreement.

You can go through classification See more about Spring Cloud Article.

  

Your comments and forwarding are the biggest support for me.

   A kind of Scan the code and follow Mr. halloward's "document + video". Each article is provided with a special video explanation, which makes learning easier~

Posted by doucie on Mon, 30 Mar 2020 21:01:30 -0700