Spring cloud learning notes 12 service gateway spring cloud zuul

demand

In the previous section, we built several service modules, User, Order and Pay, which communicate with each other directly to access objects. In the production environment, we generally require users to log in and authorize before performing related operations. If each microservice does a set of login check logic, it will cause a lot of performance loss, so we need a plug-in to check the login For unified extraction of common logic, only one set of checking logic is needed. Then we need to use Zuul plug-in of spring cloud to realize this function;

What is Zuul

Zuul is an open-source API Gateway server of Netflix, which is essentially a Web servlet application. Zuul provides a framework for dynamic routing (request distribution), monitoring, flexibility, security and other edge services on the cloud platform. Zuul is equivalent to the front door of all requests on the back end of the Web site of devices and Netflix streaming applications. It also needs to register in Eureka and use a diagram to understand zuul's role in the architecture:

It should be noted that zuul itself is an independent service. By default, it integrates with Ribbon. Zuul distributes the client's requests to the downstream microservices through Ribbon. Therefore, zuul needs to publish the services through Eureka, and zuul also integrates with Hystrix.

According to the above figure, we need to build an independent project to build zuul service, and at the same time, we need to register zuul with EurekaServer, because when the request comes, zuul needs to obtain the downstream microservice communication address through EurekaServer, and use Ribbon to initiate the call.

Build Zuul module

Create a new sub module and import the related jar package

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
         <!--    This is Alibaba processing Json We'll use it later -->
     	<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.50</version>
        </dependency>  

Main configuration class:

@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy //Opening zuul can be regarded as an enhanced version of @ EnableZuulServer, which is commonly used
public class ZuulApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class,args);
    }
}

yml configuration:

eureka:
  client:
    serviceUrl:  #Address of Registration Center
      defaultZone: http://peer1:1010/eureka/,http://peer2:1011/eureka/,http://peer3:1012/eureka/
  instance:
    prefer-ip-address: true  #Register with ip address
    instance-id: zuul-server:1060   #Real column id address
spring:
  application:
    name:  zuul-server  #service name
server:
  port: 1060 #port
zuul:
  prefix: "/servers"  #Uniform access prefix
  ignoredServices: "*"  #Disable using browser to access service by service name
  routes:
    pay-server: "/pay/**"   #Specify pay server the service uses / pay path to access - alias
    order-server: "/order/**"   #Specify the order server service to use the / order path to access

Note: we have three main configurations for zuul

zuul.prefix : as a unified prefix, it needs to be added when the browser accesses
zuul.ignoredServices : ignore using the service name to access the service, but through the path specified by routes
zuul.routes : configure the access path of the service
Note: before zuul was used, we passed the http://localhost:1040/pay/1 to directly access the payment service. Now it needs to be accessed through zuul. The format is as follows: http:// zuul IP: zuul port /zuul prefix / service path / controller path of the service, that is:
http://localhost:1060/servers/pay/pay/1
Special note: actually, we can also access the target service through the direct browser, that is, we can use: http://localhost:1040/pay/1 Bypass zuul, but don't worry about this situation, because when the product goes online, we are all deployed on the intranet. Only zuul is deployed on the Internet, that is to say, the way to directly access the target microservice is not accessible, so we only need to access through zuul.

Start service, access address:
The result shows that zuul has been successfully integrated
And then there's the point

How zuul works

The underlying layer of zuul is implemented by various filters. The filters in zuul are divided into "pre" (custom is usually pre), "routing" route, "post" post, and "error" exception filters according to the execution order. When there are exceptions in various filters, the request will jump to "error filter", and then go through "post filter" Finally, the result is returned. The following is the execution flow chart of filter:
be careful:
Normal process:
When the request arrives, it will first pass through the pre type filter, then it will arrive at the routing type for routing, and then it will arrive at the real service provider, execute the request, and then it will arrive at the post filter after returning the result. Then return the response.
Abnormal process:
In the whole process, if there is an exception in the pre or routing filter, it will directly enter the error filter. After the error is processed, the request will be handed over to the POST filter and finally returned to the user.
If the error filter itself has an exception, it will eventually enter the POST filter and return.
If there is an exception in the POST filter, it will jump to the error filter, but unlike pre and routing, the request will no longer reach the POST filter.

Custom Zuul login check filter

Zuul provides an abstract Filter:ZuulFilter We can use this abstract class to define filter, which has four core methods, as follows:

public class LoginCheckFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return null;
    }

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

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

    @Override
    public Object run() throws ZuulException {
        return null;
    }
}

filterType: used to specify the type of filter (see constant class: FilterConstants for the type)
filterOrder: it is the execution order of the filter. The smaller the order, the earlier the execution
shouldFilter: the method of its parent interface IZuulFilter, which is used to determine whether the run method is to be executed
run: is the method of its parent interface IZuulFilter, which is the core business method of Filter

Specific business realization

package cn.wly.filter;

import com.alibaba.fastjson.JSON;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Component
public class LoginCheckFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;  // "pre";
    }

    @Override
    public int filterOrder() {
        return FilterConstants.SEND_ERROR_FILTER_ORDER;  //0
    }

    @Override
    public boolean shouldFilter() {
        //Get current request object
        HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
        //Get the request address of the current object
        String requestURI = request.getRequestURI();
        //Determine whether the current request is login and registration. If not, it needs to be verified. If so, it will not be verified
        if (requestURI.endsWith("/login") || requestURI.endsWith("/register")){
            return false;
        }
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        //Get current request object
        HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
        HttpServletResponse response = RequestContext.getCurrentContext().getResponse();
        //Get the token of the current object
        String token = request.getHeader("token");
        //Determine whether the current request header contains a token. If not, return a json format prompt to the customer
        if(!StringUtils.hasLength(token)){
            Map<String,Object> resultMap=new HashMap<>();
            resultMap.put("error", false);
            resultMap.put("message","Please log in and visit" );
            //Using Alibaba json tool class to convert objects into json strings
            String jsonString = JSON.toJSONString(resultMap);
            //Format response encoding
            response.setContentType("application/json;charset=utf-8");
            //Set response status code
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); //int SC_UNAUTHORIZED = 401;
            try {
                //Output to browser in json form
                response.getWriter().print(jsonString);
            } catch (IOException e) {
                e.printStackTrace();
            }
            //Tell zuul not to go back
            RequestContext.getCurrentContext().setSendZuulResponse(false);
        }
        return null;
    }
}

be careful:
Remember to add @ Component annotation to this class, otherwise it will not take effect
In the filterType method, we return the constant of "pre" pre filter to make it a prefilter (login check needs to be done at the front of the request)
The order value returned in the filterOrder method is 1. The smaller the execution order, the earlier the execution
In the shouldFilter method, determine whether the login check is needed by judging the url of the request. The return of true means that the run method will be executed only after it is done
In the run method, we determine whether to log in by getting the token in the request header. If not, we will return an error message to prevent further execution.
RequestContext.getCurrentContext() is a request context object provided by Zuul
I used fastjson to return the error message in JSON format, and I need to import the dependency in zuul project
Restart Zuul service access test: http://localhost:1060/servers/pay/pay/1
The results are shown in the figure

Explain that the filter has taken effect, and then we use the postman tool to carry a token test:

Get the results;

zuul's fuse configuration

Zuul, as a service gateway, is aimed at the client (browser). When there is an exception in the service call link, we do not want to directly throw the exception information to the client, but we want to trigger degradation and return friendly prompt information, so we need to configure zuul's fuse mechanism.

To realize the fusing function in zuul, the ZuulFallbackProvider interface needs to be implemented. This interface provides two methods: getRoute is used to specify which routing services the fusing function is applied to, and fallbackResponse is the method executed when fusing function (used to return the supporting data). The interface code is as follows:

public interface FallbackProvider {

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

	/**
	 * Provides a fallback response based on the cause of the failed execution.
	 *
	 * @param route The route the fallback is for
	 * @param cause cause of the main method failure, may be <code>null</code>
	 * @return the fallback response
	 */
	ClientHttpResponse fallbackResponse(String route, Throwable cause);
}

Code implementation:

Let's make a fuse configuration for pay server. When zuul sends a call to pay server first, the service call fails, triggering the fuse, and a sentence "sorry, the service is not available" is returned

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;


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

    // Specifies the service to process.
    @Override
    public String getRoute() {
        return "pay-server";  //"*" means that all services work
    }

    /**
     * @param route :Routing of services
     * @param cause :  abnormal
     * @return ClientHttpResponse: Replacement value after fusing
     */
    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        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("Sorry, service is not available".getBytes());
            }

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

Tip: if you want this fusing function to work on all services, change the return and return values in the getRoute method to "*".
Close payment service test: http://localhost:1060/servers/pay/1 prompt service unavailable

zuul's parameter configuration

Timeout configuration

Zuul integrates with hystrix if the service is called

If the chain is too long, or the ribbon call event is too long, the fusing mechanism of the Hystrix may be triggered, resulting in the request not getting the normal result. We usually configure the timeout of the ribbon and the Hystrix. The following configuration is useful for all consumer microservices:
zuul profile plus the following configuration:

zuul:
  retryable: true #Whether to turn on retry function
ribbon:
  MaxAutoRetries: 1 #Number of retries to the current service
  MaxAutoRetriesNextServer: 1 #Number of times to switch the same Server
  OkToRetryOnAllOperations: false # Retry all operation requests. For example, post cannot retry. If idempotent processing is not done, multiple retries of post will result in multiple addition or modification of data
  ConnectTimeout: 3000 #Timeout for connection request
  ReadTimeout: 6000#Timeout for request processing
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 8000
            #If the retry of ribbon is configured, the timeout of hystrix is longer than that of ribbon

zuul's hungry load

In the chapter of learning the Ribbon, in order to speed up the first call of the service, we can set the hungry load of the Ribbon, and zuul implements the load balancer through the Ribbon at the bottom layer, so we also need to specify the hungry load. The configuration is as follows:

zuul:
  ribbon:
    eager-load.enabled: true  	# Hungry load

It should be noted that zuul implements starvation loading by reading the routing configuration, so if you want to make eager-load.enabled: true works. In general, we will not use the default routing method, but configure the routing rules separately, as shown above.

Tags: JSON Java Spring encoding

Posted on Sun, 21 Jun 2020 05:36:11 -0400 by jdorsch