Spring cloud service gateway Zuul advanced

The last article mainly introduced the usage mode of Zuul gateway and automatic forwarding mechanism, but in fact, Zuul has more application scenarios, such as authentication, traffic forwarding, request statistics, etc. these functions can be realized by Zuul.

Zuul's core

Filter is the core of Zuul and is used to control external services. There are four life cycles of filter, namely "PRE", "ROUTING", "POST" and "ERROR". The whole life cycle can be represented by the following figure.

 

Most of Zuul's functions are implemented through filters, and these filter types correspond to the typical life cycle of the request.

  • PRE: this filter is called before the request is routed. We can use this filter to realize authentication, select the requested micro service in the cluster, record debugging information, etc.
  • ROUTING: this filter routes requests to microservices. This filter is used to build requests to microservices and request microservices using Apache HttpClient or Netfilx Ribbon.
  • POST: this filter is executed after routing to the microservice. This filter can be used to add standard HTTP headers for responses, collect statistics and indicators, send responses from microservices to clients, and so on.
  • ERROR: this filter is executed when errors occur in other phases. In addition to the default filter type, Zuul also allows us to create custom filter types. For example, we can customize a STATIC type filter to generate a response directly in Zuul without forwarding the request to the back-end microservice.

Filter implemented by default in Zuul

type order filter function
pre -3 ServletDetectionFilter Type of tag handling Servlet
pre -2 Servlet30WrapperFilter Wrap HttpServletRequest request request
pre -1 FormBodyWrapperFilter Packaging request body
route 1 DebugFilter Mark debug flag
route 5 PreDecorationFilter Process request context for subsequent use
route 10 RibbonRoutingFilter serviceId request forwarding
route 100 SimpleHostRoutingFilter url request forwarding
route 500 SendForwardFilter forward request forwarding
post 0 SendErrorFilter Processing request responses with errors
post 1000 SendResponseFilter Handle normal request responses

Disables the specified Filter

It can be found in application Configure the filter to be disabled in YML. Format:

zuul:
    FormBodyWrapperFilter:
        pre:
            disable: true

Custom Filter

To implement a custom Filter, you need to inherit the class of ZuulFilter and override four of its methods.

public class MyFilter extends ZuulFilter {
    @Override
    String filterType() {
        return "pre"; //definition filter Types of, there are pre,route,post,error Four kinds
    }

    @Override
    int filterOrder() {
        return 10; //definition filter The smaller the number, the higher the order, and the earlier the execution
    }

    @Override
    boolean shouldFilter() {
        return true; //Indicates whether the operation is required filter,true Indicates execution, false Indicates no execution
    }

    @Override
    Object run() {
        return null; //filter Specific operations to be performed
    }
}

Custom Filter example

We assume that there is such a scenario, because the service gateway deals with all external requests. In order to avoid potential security risks, we need to make certain restrictions on requests. For example, if the request contains a Token, let the request continue to go down. If the request does not contain a Token, return directly and give a prompt.

First, customize a Filter and verify whether the parameter contains Token in the run() method.

public class TokenFilter extends ZuulFilter {

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

    @Override
    public String filterType() {
        return "pre"; // Can be called before the request is routed
    }

    @Override
    public int filterOrder() {
        return 0; // filter Execution order, specified by number ,The priority is 0. The higher the number, the lower the priority
    }

    @Override
    public boolean shouldFilter() {
        return true;// Whether to execute this filter, here is true,Indicates that filtering is required
    }

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();

        logger.info("--->>> TokenFilter {},{}", request.getMethod(), request.getRequestURL().toString());

        String token = request.getParameter("token");// Get requested parameters

        if (StringUtils.isNotBlank(token)) {
            ctx.setSendZuulResponse(true); //Route requests
            ctx.setResponseStatusCode(200);
            ctx.set("isSuccess", true);
            return null;
        } else {
            ctx.setSendZuulResponse(false); //Do not route it
            ctx.setResponseStatusCode(400);
            ctx.setResponseBody("token is empty");
            ctx.set("isSuccess", false);
            return null;
        }
    }

}

Add TokenFilter to the request interception queue and add the following code to the startup class:

@Bean
public TokenFilter tokenFilter() {
    return new TokenFilter();
}

In this way, our customized Filter is added to the request interception.

test

We start the sample projects in turn: spring cloud Eureka, spring cloud producer, spring cloud zuul,

These three projects are all the sample projects of the previous article, and spring cloud zuul is slightly modified.

Access address: http://localhost:8888/spring-cloud-producer/hello?name=neo, return: token is empty, the request is intercepted and returned.
Access address: http://localhost:8888/spring -cloud-producer/hello? Name = Neo & token = XX, return: hello neo, this is first messge, indicating that the request responds normally.

From the above example, we can see that we can use "PRE" Filter to do a lot of verification work. In practical use, we can combine shiro and oauth2 0 and other technologies for authentication and verification.

Route fusing

When an exception occurs in our back-end service, we don't want to throw the exception to the outermost layer. We expect the service to be degraded automatically. Zuul has provided us with such support. When an exception occurs in a service, it directly returns our preset information.

We use a custom fallback method and assign it to a route to handle the access problems of the route.

It is mainly implemented by inheriting the ZuulFallbackProvider interface. ZuulFallbackProvider has two methods by default, one is used to indicate which service to intercept, and the other is to customize the returned content.

public interface ZuulFallbackProvider {
   /**
     * The route this fallback will be used for.
     * @return The route the fallback will be used for.
     */
    public String getRoute();

    /**
     * Provides a fallback response.
     * @return The fallback response.
     */
    public ClientHttpResponse fallbackResponse();
}

The implementation class tells Zuul which route it is responsible for by implementing the getRoute method. The fallbackResponse method tells Zuul what return value it will provide to process the request when an open circuit occurs.

Later, Spring extended this class to enrich the return method and add exception information to the returned content. Therefore, the latest version recommends directly inheriting the class FallbackProvider.

We take the spring cloud producer service above as an example to customize its return content.

@Component
public class ProducerFallback implements FallbackProvider {
    private final Logger logger = LoggerFactory.getLogger(FallbackProvider.class);

    //Specify the to process service. 
    @Override
    public String getRoute() {
        return "spring-cloud-producer";
    }

    public ClientHttpResponse fallbackResponse() {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return 200;
            }

            @Override
            public String getStatusText() throws IOException {
                return "OK";
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream("The service is unavailable.".getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }

    @Override
    public ClientHttpResponse fallbackResponse(Throwable cause) {
        if (cause != null && cause.getCause() != null) {
            String reason = cause.getCause().getMessage();
            logger.info("Excption {}",reason);
        }
        return fallbackResponse();
    }
}

When an exception occurs in the service, print the relevant exception information and return "The service is unavailable.".

Start the project spring-cloud-producer-2. At this time, there will be two spring-cloud-producer projects in the service center. Let's restart Zuul project.

Then manually close the spring-cloud-producer-2 project and visit the address for multiple times: http://localhost:8888/spring -cloud-producer/hello? Name = Neo & token = XX, will return alternately:

hello neo,this is first messge
The service is unavailable.
...

According to the returned results, it can be seen that the spring-cloud-producer-2 project has been enabled. The return: The service is unavailable

Zuul currently only supports service level fusing, and does not support fusing specific to a URL.

Route Retry

Sometimes the service may be temporarily unavailable due to network or other reasons. At this time, we hope to retry the service again. Zuul also helps us realize this function, which needs to be implemented in combination with Spring Retry. Let's take the above project as an example.

Add Spring Retry dependency

First, add the Spring Retry dependency in the spring cloud zuul project.

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

Open Zuul Retry

Enable Zuul Retry in the configuration file

#Enable retry function
zuul.retryable=true
#Number of retries on the current service
ribbon.MaxAutoRetries=2
#Number of times to switch the same Server
ribbon.MaxAutoRetriesNextServer=0

In this way, we have enabled Zuul's retry function.

test

We modified spring-cloud-producer-2, added timing in the hello method, and printed parameters at the beginning of the request.

@RequestMapping("/hello")
public String index(@RequestParam String name) {
    logger.info("request two name is "+name);
    try{
        Thread.sleep(1000000);
    }catch ( Exception e){
        logger.error(" hello two error",e);
    }
    return "hello "+name+",this is two messge";
}

Restart the spring-cloud-producer-2 and spring-cloud-zuul projects.

Access address: http://localhost:8888/spring -cloud-producer/hello? Name = Neo & token = XX, when the page returns: The service is unavailable View the background log of the project spring-cloud-producer-2 as follows:

2018-01-22 19:50:32.401  INFO 19488 --- [io-9001-exec-14] o.s.c.n.z.f.route.FallbackProvider       : request two name is neo
2018-01-22 19:50:33.402  INFO 19488 --- [io-9001-exec-15] o.s.c.n.z.f.route.FallbackProvider       : request two name is neo
2018-01-22 19:50:34.404  INFO 19488 --- [io-9001-exec-16] o.s.c.n.z.f.route.FallbackProvider       : request two name is neo

It indicates that the request has been made three times, that is, two retries have been made.

This verifies our configuration information and completes Zuul's retry function.

be careful

Turning on retry is problematic in some cases. For example, when the pressure is too high and one instance stops responding, the routing will transfer the traffic to another instance, which is likely to lead to the collapse of all instances in the end. In the final analysis, one of the functions of the circuit breaker is to prevent failure or pressure diffusion. With retry, the circuit breaker works only if all instances of the service are inoperable. The circuit breaker is a kind of false information, which is more friendly to the user.

Instead of retry, only using load balancing and fusing, we must consider whether we can accept the short-time fusing between the shutdown of a single service instance and eureka refreshing the service list. If acceptable, there is no need to use retry.

Zuul high availability

 

The way we actually use zuul is shown in the figure above. Different clients use different loads to distribute requests to the back-end zuul. Zuul calls the back-end service through Eureka and finally outputs it to the outside.

Therefore, in order to ensure the high availability of Zuul, the front end can start multiple Zuul instances for load at the same time, and use Nginx or F5 for load forwarding at the front end of Zuul to achieve high availability.

Reference website

Posted by lewis987 on Thu, 05 May 2022 16:48:57 +0300