Zuul service gateway ☣
Summary of prerequisite knowledge points (not part of the text)
1, What is a gateway?
API Gateway (APIGW / API Gateway) , as its name implies, is an API oriented, serial and centralized strong control service that appears on the system boundary. The boundary here is the boundary of the enterprise IT system. It can be understood as an enterprise application firewall, which mainly plays the role of isolating external access from internal systems. Before the popularity of the concept of microservice, API Gateway has been born, such as banking, securities and other fields A common front-end computer system, which also solves the problems of access authentication, message conversion, access statistics, etc. (the gateway is the first gateway for users to access the system. All service addresses in the register center are recorded in the gateway, authenticated through the gateway and requested to be distributed)
- Nginx is suitable for being a portal gateway. It is the kind of gateway that is in the outermost layer externally. Zuul is a business gateway, which is mainly used to provide services corresponding to different clients and aggregate services. Each micro service is deployed independently and has a single responsibility. When providing services externally, there needs to be something to aggregate services.
- Zuul can realize functions such as fusing and retry, which Nginx does not have.
2, Zuul implements API gateway
Official website documents:
Prepare an aggregation project. There are two registries, one consumer and one service provider in a parent project. As mentioned in the previous article, start the project test to see whether the service is registered in the registry:
① Add sub module zuul server
Create a java project and introduce dependency and configuration
<?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.yjxxt</groupId> <artifactId>zuul-server</artifactId> <version>1.0-SNAPSHOT</version> <!-- Inherit parent dependency --> <parent> <groupId>com.yjxxt</groupId> <artifactId>zuul-demo</artifactId> <version>1.0-SNAPSHOT</version> </parent> <!-- Project dependency --> <dependencies> <!-- spring cloud netflix zuul rely on --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> </dependencies> </project>
application.yml configuration
server: port: 9000 # port spring: application: name: zuul-server # apply name
Startup class
@SpringBootApplication // Open Zuul annotation @EnableZuulProxy public class ZuulServerApplication { public static void main(String[] args) { SpringApplication.run(ZuulServerApplication.class, args); } }
Here the zuul module is established
② Configure routing
Service request assumptions after routing configuration: http://localhost:7070/order/10 , after configuring the route: http://localhost:7070/product-service/order/10, which specifies the interface under a service
application.yml configuration
# Routing rules zuul: routes: product-service: # Route id customization path: /product-service/** # Configure the mapping path of the request url url: http://localhost:7070 / # map the microservice address corresponding to the path
Start test:
The disadvantage is that you need to modify it manually every time you change the service
③ Service name routing
- 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 according to the serviceId and forward the request # Configure Eureka Server registry eureka: instance: prefer-ip-address: true # Whether to register with ip address 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.yjxxt; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; @SpringBootApplication // Open Zuul annotation @EnableZuulProxy // Open the Eureka client annotation. If the Eureka registry is configured in the current version, the annotation will be opened by default //@EnableEurekaClient public class ZuulServerApplication { public static void main(String[] args) { SpringApplication.run(ZuulServerApplication.class, args); } }
④ Simplified routing
Zuul provides the default routing configuration for your convenience: the routing id is consistent with the microservice name, and the default path corresponds to / microservice name / * *, so the following configuration is unnecessary.
# 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 according to the serviceId and forward the request
You can also exclude route addresses, route names, and route prefixes, which are not resolved here
3, Gateway filter
There are four filters. The pre filter is mainly used for request mapping and forwarding services, and some have routing filters for forwarding. The post filter is called after the routing and error filters to return the results. The error filter is triggered only when an error occurs
① Getting started Demo
Create class filter / customfilter. Java filter
The filter implemented in Spring Cloud Netflix Zuul must contain four basic features: filter type, execution order, execution condition and action (specific operation). These steps are the four abstract methods defined in the ZuulFilter interface:
/** * 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 lower 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.warn("CustomFilter...method={}, url={}", request.getMethod(), request.getRequestURL().toString()); return null; } }
test http://localhost:9000/product-service/product/521
Print request log
② Unified authentication
It is similar to authentication, that is, token. Otherwise, you have no permission to access the service
Modify filter:
/*Certified by filter*/ @Override public Object run() throws ZuulException { // Get request context RequestContext rc = RequestContext.getCurrentContext(); HttpServletRequest request = rc.getRequest(); // Get the token in the form String token = request.getParameter("token"); // Business logic processing if (null == token) { logger.warn("token is null..."); // The request ends, and the downward request is not continued. rc.setSendZuulResponse(false); // In response to the status code, the HTTP 401 error indicates that 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; }
test http://localhost:9000/product-service/product/521
③ Unified handling of gateway filter exceptions
Modify filter:
/*Manage exceptions uniformly through filters*/ @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; }
Create an exception and add a class accessfilter. Java (propose a class for the authentication 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 { // Simulation anomaly Integer.parseInt("zuul"); // Get request context RequestContext rc = RequestContext.getCurrentContext(); HttpServletRequest request = rc.getRequest(); // Get the token in the form String token = request.getParameter("token"); // Business logic processing if (null == token) { logger.warn("token is null..."); // The request ends, and the downward request is not continued. rc.setSendZuulResponse(false); // In response to the status code, the HTTP 401 error indicates that 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; } }
Add exception handling filter
/** * Exception 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); } }
Turn off the built-in exception handling
zuul: # Disable Zuul's default exception handling filter SendErrorFilter: error: disable: true
Test results:
④ Declaration cycle of Zuul request
- HTTP send request to Zuul gateway
- Zuul gateway first passes through pre filter
- After passing the 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
- Continue to execute post filter
- Finally, the response is returned to the HTTP client
4, Seamless combination of Zuul and Hystrix
To realize network monitoring, you need to configure Hystrix. Zuul's dependencies include the related jar packages of Hystrix, so we don't need to add additional Hystrix dependencies in the project. However, for projects that need to start data monitoring, we need to add dashboard dependencies.
① Prepare environment
# Introduce dependency <!-- spring cloud netflix hystrix dashboard rely on --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency>
# to configure # Metrics monitoring and health check management: endpoints: web: exposure: include: hystrix.stream
Start class / / open Zuul annotation @ EnableZuulProxy
Start project test access: http://localhost:9000/hystrix
Listening service address http://localhost:9000/actuator/hystrix.stream
② Gateway fuse
Before the Edgware version, Zuul provided the interface ZuulFallbackProvider to implement fallback processing. Since the Edgware version, Zuul provided the interface FallbackProvider to provide fallback processing. Write the implementation class: productproviderfallback (the fuse will fuse only when the service is down without any feedback, and the service can be suspended during all tests)
/** * 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 characterized fallback logic for the specified service. * Recommended - provide a fallback logic that handles all services. * Benefit - if a service times out, the specified fallback logic is executed. If a new service is launched and fallback logic is not provided, there is a general service. */ @Override public String getRoute() { return "product-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\":\"Goods and services are unavailable. Please try again later.\"}".getBytes()); } /** * ClientHttpResponse The status code of the fallback returns HttpStatus * @return */ @Override public HttpStatus getStatusCode() throws IOException { return HttpStatus.INTERNAL_SERVER_ERROR; } /** * ClientHttpResponse The status code of the fallback of returns int * @return */ @Override public int getRawStatusCode() throws IOException { return this.getStatusCode().value(); } /** * ClientHttpResponse The status code of the fallback returns String * @return */ @Override public String getStatusText() throws IOException { return this.getStatusCode().getReasonPhrase(); } /** * Resource recovery method * It is used to reclaim the resource object opened by the current fallback logic. */ @Override public void close() { } }; } }
The refresh will occur only after the service is down:
③ Gateway current limiting
It is to specify the number of requests within the specified time. Once it exceeds the standard, it will not accept the request, and more than the request will be returned directly. There are three current limiting algorithms:
- Counter algorithm
- Leaky Bucket algorithm
- Token Bucket algorithm
Counter
Set a counter. For example, 100 requests are allowed in a minute. Therefore, if the counter is less than 100 in a minute, it will be counted again. The disadvantage is that 59s100 requests and 100 requests in one minute lead to the collapse of the service, which is a vulnerability and easy to idle
Leaky bucket algorithm
It is to use a hole under a bucket to store a large number of requests, and then process the requests one by one through a small hole. Once the request is larger than the bucket, it will directly fall back, similar to message queue
Token Bucket
When a token is stored in a bucket, the request will get the token, and the token will always be generated. The redundant token will be discarded directly. The request to get the token can access the resources. The request to get the token will be executed, otherwise it will fall back or cache
Code implementation:
//Zuul's current limiting protection needs to additionally rely on the spring cloud zuul ratelimit component. The current limiting data is stored in Redis, so Redis component should be added. //[official website document of RateLimit]: 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
Using the global current limit configuration, Zuul will provide current limit protection for all services of the agent (remember to open the redis cache database).
server: port: 9000 # port spring: application: name: zuul-server # apply name # redis cache redis: host: 192.168.10.106 # Redis server address # Configure Eureka Server registry eureka: instance: prefer-ip-address: true # Whether to register with ip address 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 current limiting ratelimit: # Open current limiting protection enabled: true # Current limiting data storage mode repository: REDIS # Default policy list is configured by default and takes effect globally default-policy-list: - limit: 3 refresh-interval: 60 # If the request exceeds 3 times within 60s, the server will throw an exception, and the normal request can be restored after 60s type: - origin - url - user
Start test
The number of requests exceeds the threshold set by us, so there are too many return requests, followed by local flow restriction, user-defined flow restriction, gateway tuning, Zuul and Sentinel integration, etc. there are a lot of contents, so let's stop here first.