1. General
Zuul official website of previous generation gateway
-
Gateway is an API gateway service built on the Spring ecosystem, which is based on technologies such as Spring5, springboot2 and ProjectReactor.
-
Gateway aims to provide a simple and effective way to route API s and provide some powerful filter functions, such as fusing, current limiting, Retry, etc
-
Spring Cloud gateway is a new project of Spring Cloud, which is based on spring 5 0 + spring boot20, Project Reactor and other technologies. It aims to provide a simple and effective unified API routing management method for microservice architecture.
-
As a gateway in the spring cloud ecosystem, the spring cloud gateway aims to replace Zuul in spring cloud 2 Among the versions above Zuul 2.0, the latest performance versions above Zuul 2.0 are not integrated, and Zuul 1.0 is still used X is an older version of non Reactor mode. In order to improve the performance of the gateway, the spring cloud gateway is implemented based on the WebFlux framework, and the underlying WebFlux framework uses the high-performance Reactor mode communication framework Netty.
-
The goal of spring cloud gateway is to provide a unified routing method, and the Filter chain based method provides the basic functions of the gateway, such as security, monitoring / indicators, and current limiting.
The underlying component of cloud gateway is the response framework of netfly
1.1 function
-
Reverse proxy
-
authentication
-
flow control
-
Fuse
-
Log monitoring
1.2 where is the gateway in the microservice architecture?
1.3 why gateway?
-
zuul2.0 has been delayed due to Zuul1 0 has entered the maintenance stage, and the Gateway is developed by the spring cloud team. It is a son-in-law product. It is trustworthy, and many functions Zuul does not have. It is also very simple and convenient to use.
-
Gateway is developed based on asynchronous non male plug model, so there is no need to worry about performance. Although Netflix has long released the latest zuul 2 x. But spring cloud seems to have no integration plan. Moreover, Netflix related components are declared to enter the maintenance period; I wonder what the future holds?
-
Spring cloud gateway has the following features:
3.1 build based on Spring Framework 5, Project Reactor and Spring Boot 2.0; Dynamic routing: it can match any request attribute;
3.2 you can specify predicate and filter for the route; Integrated circuit breaker function of Hystrix;
Integrate Spring Cloud service discovery function;
3.3 easy to write predicate and filter; Request current limiting function; Path rewriting is supported.
2. The difference between springcloud gateway and Zuul
-
Before the official version of spring cloud Finchley, the gateway recommended by spring cloud was zuul1.0 provided by Netflix x. Is an API based on blocking 1/O;
-
Zuul1x is based on servlet2 5. Using blocking architecture, it does not support any long connection (such as websocket). Zuul's design mode is more similar to Nginx. Each I/O operation is executed by selecting one of the working threads, and the request thread is blocked until the working thread is completed. However, the difference is that Nginx is implemented in C + +, zuul is implemented in Java, and the JVM itself will load slowly for the first time, which makes zuul's performance relatively poor.
-
Zuul2. The X concept is more advanced. It wants to be non blocking based on Netty and support long connections, but the spring cloud has not been integrated yet. Zuul2.x has better performance than zuul 1 X has been greatly improved. In terms of performance, according to the official benchmark, the RPS (requests per second) of Spring Cloud Gateway is 1.6 times that of zuul. But in the end 2 X died prematurely;
-
Spring Cloud Gateway is built on Spring Framework5, Project Reactor and Spring Boot2 and uses non blocking APl.
-
Spring Cloud Gateway also supports WebSocket and is closely integrated with spring to have a better development experience;
2.1 Zuul1.x model
The Zuul version integrated in spring cloud uses the Tomcat container and the traditional Servlet IO processing model.
Servlets are lifecycle managed by servlet container s.
When the container starts, construct a servlet object and call servletinit() for initialization;
The container runtime accepts the request and assigns a thread for each request (usually obtains the idle thread from the thread pool) and then calls service(). When the container is closed, call servlet destory0 to destroy the servlet;
Disadvantages of the above mode:
Servlet is a simple network IO model. When a request enters the servlet container, the servletcontainer will bind a thread for it. This model is applicable in scenarios with low concurrency. However, once high concurrency (such as jemeter pressure for ventilation), the number of threads will rise, and the cost of thread resources is expensive (online text switching, large memory consumption), which seriously affects the processing time of requests. In some simple business scenarios, you don't want to allocate a thread to each request. You only need one or several threads to deal with extremely concurrent requests. In this business scenario, the servlet model has no advantage
So zuul1 X is a blocking processing model based on servlets, that is, spring implements a servlet (dispatcher servlet) that handles all request requests and is blocked by the servlet. Therefore, Springcloud Zuul cannot get rid of the disadvantages of servlet model;
2.2 Gateway model
Gateway is based on Spring WebFlux
What is webFlux?
-
Feature 1: asynchronous non blocking
As we all know, spring MVC is an IO model with synchronous blocking, and the waste of resources is relatively serious. When we are dealing with a time-consuming task, such as uploading a large file, first of all, the server thread has been waiting to receive the file. During this period, it is like a fool waiting there (don't go after school) and can't do anything, Finally, when the file comes and is received, we have to write the file to the disk. In the process of writing, the thread is confused bi again. We have to wait until the file is written before we can do other things. Isn't this wait a waste of resources?
Yes, Spring WebFlux is designed to solve this problem. Spring WebFlux can be asynchronous and non blocking. In the above example of uploading files, Spring WebFlux does this: when the thread finds that the file is not ready, it first does other things. When the file is ready, it notifies the thread to handle it. When the file is received and written to the disk (choose whether to do asynchronous non blocking according to the specific situation), After writing, notify this thread to process again (in the case of asynchronous non blocking). -
Feature 2: reactive function programming
If you think the lambda of java8 is great, you will like Spring WebFlux again because it supports functional programming and benefits from the support for reactive stream (implemented through the reactor framework); -
Feature 3: no longer bound to Servlet container
In the past, our applications were running in Servlet containers, such as Tomcat, Jetty... And so on. Now Spring WebFlux can not only run in the traditional Servlet container (provided that the container supports Servlet3.1, because non blocking IO uses the feature of Servlet3.1), but also in Netty and Undertow that support NIO.
3. Gateway principle
From the official website: https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/
In fact, the best teacher is the official website. Just go and have a look
The client sends a request to the Spring Cloud Gateway, and then the Gateway Handler Mapping determines the route matching the request and sends it to the Gateway Web Handler The handler sends the request to our actual service through the specified filter chain, executes the business logic, and then returns. The reason that the traverser is separated by virtual money is that the filter can run logic before and after sending the proxy request All pre filter logic is executed. Then issue the proxy request. After issuing the proxy request, the post filter logic will be run,
4. Three core concepts of gateway
- Route: route is the most basic part of the gateway. The route information consists of lD, target URI, a set of assertions and a set of filters. If the asserted route is true, the requested URI and configuration match.
- Predicate: predicate in Java8. The input type of the assertion function in the Spring Cloud Gateway is spring 5 Serverwebexchange.com in 0 framework The assertion function in Spring Cloud Gateway allows developers to define and match any information from Http Request, such as request header and parameters.
- Filter: a standard Spring Web Filter. There are two types of filters in Spring Cloud Gateway: Gateway Filter and Global Filter The filter processes requests and responses.
5. Route
5.1 routing Path
The configuration is as follows: yaml
server: port: 8888 spring: application: name: gateway cloud: gateway: routes: - id: client01-7777 # Routing Id, unique uri: http://localhost:7777 / # destination URI address to route to microservice predicates: # Assertion, judgment condition - Path=/wql01/** # Match the request of the corresponding Url and append the matching request after the target URI
When entering in the browser: http://localhost:8888/wql01/01 If it is found that it starts with wql01, it will be asserted as true, and / wql01 / 01 will be spliced after the uri,
That is, I will go eventually http://localhost:7777/wql01/01 This is the Path routing rule
5.2 routing Query
The configuration is as follows: yaml
server: port: 8888 spring: application: name: gateway cloud: gateway: routes: - id: client01-7777 # Routing Id, unique uri: http://localhost:7777 / # destination URI address to route to microservice predicates: # Assertion, judgment condition # - Query=token # Match the request with token in the request parameter - Query=token, abc. #The matching request parameter contains token and satisfies the regular expression abc Request for
- Query=token eg: http://localhost:8888/wql01/01?token=123
- Query=token, abc. eg: http://localhost:8888/wql01/01?token=abc1
5.3 routing Method
The configuration is as follows: yaml
server: port: 8888 spring: application: name: gateway cloud: gateway: routes: - id: client01-7777 # Routing Id, unique uri: http://localhost:7777 / # destination URI address to route to microservice predicates: # Assertion, judgment condition - Method=GET # Match any Get request
5.4 Datetime of routing
The configuration is as follows: yaml
server: port: 8888 spring: application: name: gateway cloud: gateway: routes: - id: client01-7777 # Routing Id, unique uri: http://localhost:7777 / # destination URI address to route to microservice predicates: # Assertion, judgment condition # Matching requests after 20:20:20, 2021-02-02 Shanghai time - After=2021-02-02T20:20:20.000+08:00[Asia/Shanghai]
After means that after this time, there is also a before after rule, which is the corresponding range;
5.5 RemoteAddr of routing
The configuration is as follows: yaml
server: port: 8888 spring: application: name: gateway cloud: gateway: routes: - id: client01-7777 # Routing Id, unique uri: http://localhost:7777 / # destination URI address to route to microservice predicates: # Assertion, judgment condition - RemoteAddr=192.168.43.156/0 # The matching remote address request is the request of RemoteAddr, and 0 represents the subnet mask
Use ipconfig to see your local ipv4 address, whether it's wireless or Ethernet, such as mine
If I use it now http://localhost:8888/wql01/01 You can't access it because I set the remote address to 192.168.43.156, so you must use it
http://192.168.43.156:8888/wql01/01 To access
5.6 routing Header
The configuration is as follows: yaml
server: port: 8888 spring: application: name: gateway cloud: gateway: routes: - id: client01-7777 # Routing Id, unique uri: http://localhost:7777 / # destination URI address to route to microservice predicates: # Assertion, judgment condition - Header=x-Request-Id, \d+ # A request whose matching request header contains X-Request-Id and matches the regular expression \ d +
The usage is similar to Query, except that the Header is judged according to the Header;
5.7 dynamic routing
Dynamic routing, that is, service discovery routing, that is, service-oriented routing. Spring cloudgateway is integrated and developed with Eureka. It automatically obtains the service address from the registry according to the serviceId and forwards the request. This has the advantage that it can not only access all the services of the application through a single endpoint, but also does not need to modify the routing configuration of the Gateway when adding or removing service instances
The service name of the service provider is CLIENT01, which can be viewed on the registry page
Test:
5.8 service name forwarding of routing
The problem solved by dynamic routing is that the uri is dead. If there are 100 services now, should we write 100 groups of routes in the configuration file, as shown in the figure below? This is a group of configurations representing Client01 services. 100 different services write 100 groups, which is troublesome to maintain. However, Spring actually helps us do a lot of agreed operations. If we abide by the agreement, Can be very simple to solve our troubles
As shown in the figure below, if we abide by the agreement, it will become particularly concise
Two services are prepared, one client01 and one client02
If we want to call the service of client01, we just need to send http://localhost:8888/client01/wql01/01 The difference is that we need to add the service name of the caller
If we want to call the service of client02, we just need to send http://localhost:8888/client02/wql01/01 Just
This saves a lot of configuration writing;
6. Filter
Spring Cloud Gateway is divided into GatewayFilter and GlobalFilter according to the scope of action. The differences between the two are as follows:
- Gateway filter: gateway filter
You need to go through spring cloud. routes. Filters are configured under specific routes and only work on the current route or through spring cloud. Default filters are configured globally and act on all routes. - GlobalFilter: a global filter, which does not need to be configured in the configuration file and acts on all routes. It is finally packaged into a filter recognizable by GatewayFilterChain through the GatewayFilterAdapter. It is the core filter that converts the URI of the request service and route into the request address of the real service. It does not need to be loaded during system initialization, And act on each route.
6.1 gateway filter
The gateway filter is used to intercept and chain process Web requests, and can crosscut application independent requirements, such as security, access timeout settings, etc. Modify incoming HTTP requests or outgoing HTTP responses.
Spring Cloud Gateway includes many built-in gateway filter factories, with a total of 22, including Header filter, Path class filter, Hystrix filter and filter for rewriting request URL, as well as other types of filters such as parameters and status codes. According to the purpose of the filter factory, it can be divided into the following categories: Header, Parameter, Path, Body, Status,Session,Redirect, Retry, ratelimit, etc
Gateway filter official website address
Here are some examples:
- Header header filter
- AddRequestHeader GatewayFilter Factory
- RemoveRequestHeader GatewayFilter Factory
- AddResponseHeader GatewayFilter Factory
- RemoveResponseHeader GatewayFilter Factory
- SetRequestHeader GatewayFilter Factory
- SetResponseHeader GatewayFilter Factory
- PreserveHostHeader GatewayFilter Factory
- SecureHeaders GatewayFilter Factory
- Parameter request parameter filter
- AddRequestParameter GatewayFilter Factory
- Path filter
- PrefixPath GatewayFilter Factory
- StripPrefix GatewayFilter Factory
- SetPath GatewayFilter Factory
- RewritePath GatewayFilter Factory
- Body request (response) body filter
- ModifyRequestBody GatewayFilter Factory
- ModifyResponseBody GatewayFilter Factory
- Status filter
- SetStatus GatewayFilter Factory
- Session filter
- SaveSession GatewayFilter Factory
- Redirect redirect filter
- RedirectTo GatewayFilter Factory
- Retry retry filter
- Retry GatewayFilter Factory
- RateLimiter current limiting filter
- RequestRateLimiter GatewayFilter Factory
Each filter corresponds to a factory class. In fact, the official website of each filter provides corresponding case teaching. Here are several common filters, and the materials are extracted from the official website
6.1.1 RewritePath GatewayFilter Factory
Override Url:
server: port: 8888 spring: application: name: gateway cloud: gateway: routes: - id: CLIENT01 # Routing Id, unique uri: lb://CLIENT01 # destination URI address to route to microservice predicates: # Assertion, judgment condition - Path=/api-gateway/** # Matching the request of the corresponding Url filters: # Gateway filter - RewritePath=/api-gateway/?(?<segment>.*), /$\{segment}
Filters in the figure represent gateway filters
For example, we send http://localhost:8888/api-gateway/wql01/01 requests that / API gateway / wql01/01 be replaced with / wql01/01, as shown in the following figure
6.1.2 PrefixPathGatewayFilterFactory
The PrefixPath gateway filter factory adds the specified prefix to the matching URI
server: port: 8888 spring: application: name: gateway cloud: gateway: routes: - id: CLIENT01 # Routing Id, unique uri: lb://CLIENT01 # destination URI address to route to microservice predicates: # Assertion, judgment condition - Path=/** # Matching request Url filters: # Gateway filter # Rewrite / 01 to / wql01/01 - PrefixPath=/wql01
For example, / 01 will be automatically prefixed with / wql01 and become / wql01/01
6.1.3 StripPrefixGatewayFilterFactory
StripPrefixGatewayFilterFactory the gateway filter factory adopts a parameter StripPrefix, which indicates the number of paths stripped from the request before the request is sent to the downstream
server: port: 8888 spring: application: name: gateway cloud: gateway: routes: - id: CLIENT01 # Routing Id, unique uri: lb://CLIENT01 # destination URI address to route to microservice predicates: # Assertion, judgment condition - Path=/** # Matching the request of the corresponding Url filters: # Gateway filter # Rewrite / api/wql01/01 to / wql01/01 - StripPrefix=1 # 1 indicates 1 / symbol
For example, / api/wql01/01 will automatically intercept the previous / symbol and become / wql01/01
6.1.4 SetPathGatewayFilterFactory
The SetPath gateway filter factory adopts the path template parameter. It provides a simple method to operate the request path by allowing templated path segments. It uses the Uri template in the spring framework and allows multiple matching segments
server: port: 8888 spring: application: name: gateway cloud: gateway: routes: - id: CLIENT01 # Routing Id, unique uri: lb://CLIENT01 # destination URI address to route to microservice predicates: # Assertion, judgment condition - Path=/api/wql01/{segment} # Matching the request of the corresponding Url filters: # Gateway filter # Rewrite / api/wql01/01 to / wql01/01 - SetPath=/product/{segment}
/api/wql01/01 changed to / wql01/01 after resetting the path
6.1.5 Parameter filter
AddRequestParameter the gateway filter factory will add the specified parameter to the matching downstream request
server: port: 8888 spring: application: name: gateway cloud: gateway: routes: - id: CLIENT01 # Routing Id, unique uri: lb://CLIENT01 # destination URI address to route to microservice predicates: # Assertion, judgment condition - Path=/api/wql01/{segment} # Matching the request of the corresponding Url filters: # Gateway filter # Rewrite / api/wql01/01 to / wql01/01 - RewritePath=/api(?<segment>/?.*),$\{segment} # Filters can be used in combination. The following indicates adding flag=1 to the request parameters - AddRequestParameter=flag,1
When executed http://localhost:8888/api/wql01/01 When requesting, change / API / wql01 / 01 to / wql01 / 01, and then add the flag parameter
6.1.6 Status filter
The SetStatus gateway filter factory adopts a single status parameter. It must be a valid Spring HttpStatus, which can be an integer 404 or an enumeration not_ String representation of found;
server: port: 8888 spring: application: name: gateway cloud: gateway: routes: - id: CLIENT01 # Routing Id, unique uri: lb://CLIENT01 # destination URI address to route to microservice predicates: # Assertion, judgment condition - Path=/api/wql01/{segment} # Matching the request of the corresponding Url filters: # Gateway filter # Rewrite / api/wql01/01 to / wql01/01 - RewritePath=/api(?<segment>/?.*),$\{segment} # Filters can be used in combination, indicating that in any case, the Http status of the response will be set to 404 - SetStatus=404 # 404 or the corresponding enumeration NOT_FOUND
It's actually changing the status code
6.2 global filter
The global filter does not need to be configured in the configuration file and acts on all routes. Finally, it is packaged into a filter recognizable by the GatewayfilterChain through the GatewayfilterAdapter. It is the core filter that converts the URI of the requested service and route into the real service request address. It does not need to be loaded during system initialization and acts on each route.
6.3 custom filters
6.3.1 custom gateway filter
If the gateway filter provided by spring cannot meet your needs, you can customize the gateway filter
It mainly implements two interfaces: gatewayfilter and ordered
Ordered is used for sorting. The smaller the number, the higher the number
- Create a custom gateway filter class
package com.wql.gatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.core.Ordered; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * Custom gateway filter * * @author wql * @date 2021/12/28 23:23 */ public class DiyGatewayFilter implements GatewayFilter, Ordered { /** * Filter business logic */ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { System.out.println("Custom gateway filter executed"); // Continue return chain.filter(exchange); } /** * Filter execution order: the smaller the value, the higher the priority */ @Override public int getOrder() { return 0; } }
- Register the custom gateway filter through the configuration class
package com.wql.config; import com.wql.gatewayFilter.DiyGatewayFilter; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * Gateway routing configuration class * * @author wql * @date 2021/12/28 23:30 */ @Configuration public class GatewayRouteConfiguration { @Bean public RouteLocator routeLocator(RouteLocatorBuilder builder) { return builder.routes().route(r ->r //Assertion, judgment condition .path("/wql01/**") // Destination uri, the address of the route to the microservice .uri("lb://CLIENT01") // Custom gateway filter .filter(new DiyGatewayFilter()) // Route Id unique identifier .id("CLIENT01") ).build(); } }
6.3.2 user defined global filter
To customize the global filter, you need to implement the following two interfaces: globalfilter and ordered. Through the global filter, you can realize permission verification, security verification and other functions;
- Create a custom global filter class
package com.wql.gatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * Custom global filter * * @author wql * @date 2021/12/28 23:23 */ @Component public class DiyGlobalFilter implements GlobalFilter, Ordered { /** * Filter business logic */ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { System.out.println("The custom global filter is executed"); // Continue return chain.filter(exchange); } /** * Filter execution order: the smaller the value, the higher the priority */ @Override public int getOrder() { return 0; } }
The global filter does not need to register routes. It can take effect by directly adding a @ Component annotation
7. User defined global filter permission verification method
package com.wql.gatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * Custom global filter * * @author wql * @date 2021/12/28 23:23 */ @Component public class DiyGlobalFilter implements GlobalFilter, Ordered { /** * Filter business logic */ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // Getting the request parameter getFirst does not mean getting the first parameter. It is just a way to get parameters String token = exchange.getRequest().getQueryParams().getFirst("token"); //Business logic processing if (null==token) { System.out.println("token Empty"); ServerHttpResponse response = exchange.getResponse(); //Response type response.getHeaders().add("Content-Type","application/json;charset=utf-8"); //The response status code Http 401 indicates that the user does not have access rights response.setStatusCode(HttpStatus.UNAUTHORIZED); //Response content String message = "{\"message\":\"" + HttpStatus.UNAUTHORIZED.getReasonPhrase() + "\"}"; DataBuffer buffer = response.bufferFactory().wrap(message.getBytes()); //Request ended, no further downward request return response.writeWith(Mono.just(buffer)); } System.out.println("token is ok"); return chain.filter(exchange); } /** * Filter execution order: the smaller the value, the higher the priority */ @Override public int getOrder() { return 0; } }
8. Gateway current limiting method
Why is current limiting required?
For example, Web services and external AP1, the following types of services may cause the machine to collapse:
- User growth is too fast (good)
- Because of a hot event (fine tuning hot ironing)
- Competing crawler
- Malicious request
These situations are unpredictable. I don't know when there will be 10 to 20 times of traffic. If this happens, it's too late to expand the capacity.
8.1 common current limiting algorithms
-
Calculator algorithm
The counter algorithm is the most simple and easy to implement algorithm in the current limiting algorithm. For example, we stipulate that for the A interface, we can't access more than 100 times in one minute, so we can do this: at the beginning, we can set A counter at the counting end. Whenever A request comes, the counter will increase by 1, If the value of counter is greater than 100 and the interval between the request and the first request is still within 1 minute, trigger current limiting; If the interval between the request and the first request is greater than 1 minute, reset the counter and count again. The schematic diagram of the specific algorithm is as follows:
Although this algorithm is simple, it has a fatal flaw, which is the critical problem:
We can see from the above figure that if a malicious user sends 100 requests instantaneously at 0:59 and 100 requests instantaneously at 1:00, in fact, the user sends 200 requests instantaneously in one second. We just stipulated that there are up to 100 requests in one minute, that is, up to 1.7 requests per second, Users can instantly exceed our speed limit by suddenly clearing at the reset node of the time window. Users may crush our application in an instant through this loophole in the algorithm.
There is also the problem of data waste. Our expectation is that 100 requests can be evenly distributed in this minute. Assuming that we have the upper request limit within 305, the server will be idle for the remaining half a minute, as shown in the figure below:
-
Leaky bucket algorithm
The leaky bucket algorithm is also very simple. It can be roughly regarded as the process of water injection and leakage. Water flows into the bucket at any rate and out at a certain rate. When the water exceeds the bucket flow, it will be discarded, because the bucket capacity is unchanged to ensure the overall rate
Leaky bucket algorithm is implemented based on queue
The disadvantage of the leaky bucket algorithm is that the outflow rate of added water is one second. If 10000 requests come in one second, I need 10000 seconds to process them. If the requests are stacked in the bucket, a large number of requests will be lost beyond the capacity of the bucket, resulting in resource loss and waste, and great pressure on the gateway
- Token bucket algorithm
Token bucket algorithm is an improvement of leaky bucket algorithm. Leaky bucket algorithm can limit the rate of request calls, and token bucket algorithm can not only limit the average rate of calls, but also allow a certain degree of burst calls. In the token bucket algorithm, there is a bucket used to store a fixed number of tokens. In the algorithm, there is a mechanism to put tokens into the bucket at a certain rate. Each request call needs to obtain the token first. Only when you get the token can you have the opportunity to continue execution. Otherwise, you can choose to wait for the available token or refuse directly. The action of putting tokens is continuous. If the token in the bucket reaches the upper limit, the token will be discarded.
The scenario is like this: there are always a large number of available tokens in the bucket. At this time, incoming requests can be directly executed with tokens. For example, if the QPS is set to 100/s, there will be 100 tokens in the bucket one second after the initialization of the current limiter. 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 will wait, and finally it is equivalent to executing at a certain rate.
The Spring Cloud Gateway uses this algorithm internally, which is roughly described as follows:
- All requests need to get an available token before they can be processed;
- Add tokens to the bucket at a certain rate according to the current limit;
- The bucket sets the maximum limit for placing tokens. When the bucket is full, the newly added tokens are discarded or rejected;
- After the request arrives, first obtain the token in the token bucket, and then carry out other business logic with the token. After processing the business logic, delete the token 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, so as to ensure sufficient current limit.
The leaky bucket algorithm is mainly used to protect others, while the token bucket algorithm is mainly used to protect itself and hand over the request pressure to the target service. Suppose that many requests come in suddenly. As long as you get this card, these requests will be processed instantaneously to call the target service.
8.2 Gateway current limiting
Spring Cloud Gateway officially provides a RequestRateLimiterGatewayFilterFactory filter factory, which uses Redis and Lua scripts to implement the token bucket
8.2.1 Url current limiting rules
server: port: 8888 spring: application: name: gateway cloud: gateway: routes: - id: CLIENT01 # Routing Id, unique uri: lb://CLIENT01 # destination URI address to route to microservice predicates: # Assertion, judgment condition - Path=/api/wql01/{segment} # Matching the request of the corresponding Url filters: # Gateway filter # Current limiting filter - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 1 # Token population rate per second redis-rate-limiter.burstCapacity: 2 # Total token bucket capacity key-resolver: "#{@pathKeyResolver}" # Use a SpEL expression to reference beans by name, where pathKeyResolver is the bean object Id
pathKeyResolver in yaml corresponds to the name of the following bean:
package com.wql.config; import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import reactor.core.publisher.Mono; /** * @author wql * @date 2021/12/29 23:47 */ @Configuration public class KeyResolverConfiguration { @Bean public KeyResolver pathKeyResolver(){ return exchange -> Mono.just(exchange.getRequest().getURI().getPath()); } }
8.2.2 params parameter current limiting rules
server: port: 8888 spring: application: name: gateway cloud: gateway: routes: - id: CLIENT01 # Routing Id, unique uri: lb://CLIENT01 # destination URI address to route to microservice predicates: # Assertion, judgment condition - Path=/api/wql01/{segment} # Matching the request of the corresponding Url filters: # Gateway filter # Current limiting filter - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 1 # Token population rate per second redis-rate-limiter.burstCapacity: 2 # Total token bucket capacity key-resolver: "#{@paramsKeyResolver}" # Use a SpEL expression to reference beans by name, where paramsKeyResolver is the bean object Id
In fact, you can change the bean name to params, but you cannot configure multiple flow restriction rules at the same time, otherwise an error will be reported
package com.wql.config; import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import reactor.core.publisher.Mono; /** * @author wql * @date 2021/12/29 23:47 */ @Configuration public class KeyResolverConfiguration { // @Bean // public KeyResolver pathKeyResolver(){ // return exchange -> Mono.just(exchange.getRequest().getURI().getPath()); // } @Bean public KeyResolver paramsKeyResolver(){ return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userName")); } }
8.2.3 ip current limiting rules
server: port: 8888 spring: application: name: gateway cloud: gateway: routes: - id: CLIENT01 # Routing Id, unique uri: lb://CLIENT01 # destination URI address to route to microservice predicates: # Assertion, judgment condition - Path=/api/wql01/{segment} # Matching the request of the corresponding Url filters: # Gateway filter # Current limiting filter - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 1 # Token population rate per second redis-rate-limiter.burstCapacity: 2 # Total token bucket capacity key-resolver: "#{@ipKeyResolver}" # Use a SpEL expression to reference beans by name, where ipKeyResolver is the bean object Id
Similarly, it is actually changing the object of the bean
package com.wql.config; import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import reactor.core.publisher.Mono; /** * @author wql * @date 2021/12/29 23:47 */ @Configuration public class KeyResolverConfiguration { // @Bean // public KeyResolver pathKeyResolver(){ // return exchange -> Mono.just(exchange.getRequest().getURI().getPath()); // } // @Bean // public KeyResolver paramsKeyResolver(){ // return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userName")); // } @Bean public KeyResolver paramsKeyResolver(){ return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName()); } }
9. Realize current limiting in combination with Sentinel
Official address: https://github.com/alibaba/Sentinel/wiki/%E7%BD%91%E5%85%B3%E9%99%90%E6%B5%81
At the same time, the official website also provides a demo for reference
Official website link: https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel#spring-cloud-gateway-%E6%94%AF%E6%8C%81
- maven dependency:
<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> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
- Configure it in yaml
Then write the configuration class:
package com.wql.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.sc.SentinelGatewayFilter; import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler; import org.springframework.beans.factory.ObjectProvider; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.reactive.result.view.ViewResolver; import javax.annotation.PostConstruct; import java.util.Collections; import java.util.HashSet; import java.util.List; @Configuration public class GatewayConfiguration { private final List<ViewResolver> viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; /** * constructor * * @param viewResolversProvider * @param serverCodecConfigurer */ public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; } /** * Current limiting exception handler * * @return */ @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { // Register the block exception handler for Spring Cloud Gateway. return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer); } /** * Current limiting filter * * @return */ @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public GlobalFilter sentinelGatewayFilter() { return new SentinelGatewayFilter(); } /*--------------------------These are on the official website------------------------------*/ @PostConstruct public void doInit() { initGatewayRules(); } /** * Gateway current limiting rules */ private void initGatewayRules() { HashSet<GatewayFlowRule> rules = new HashSet<>(); /** * resource: The resource name can be the route name in the gateway or the user-defined API group name * count: Current limiting threshold * intervalSec: Statistics time window, unit: seconds, default: 1s */ rules.add(new GatewayFlowRule("order-service").setCount(3).setIntervalSec(60)); //Load gateway current limiting rules GatewayRuleManager.loadRules(rules); } }
The configuration class demo of the official website is as follows:
/* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wql.config.guanwang; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.annotation.PostConstruct; import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayParamFlowItem; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter; import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import org.springframework.beans.factory.ObjectProvider; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.reactive.result.view.ViewResolver; /** * @author Eric Zhao */ @Configuration public class GatewayConfiguration { private final List<ViewResolver> viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; } @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { // Register the block exception handler for Spring Cloud Gateway. return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer); } @Bean @Order(-1) public GlobalFilter sentinelGatewayFilter() { return new SentinelGatewayFilter(); } @PostConstruct public void doInit() { initCustomizedApis(); initGatewayRules(); } private void initCustomizedApis() { Set<ApiDefinition> definitions = new HashSet<>(); ApiDefinition api1 = new ApiDefinition("some_customized_api") .setPredicateItems(new HashSet<ApiPredicateItem>() {{ add(new ApiPathPredicateItem().setPattern("/ahas")); add(new ApiPathPredicateItem().setPattern("/product/**") .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); }}); ApiDefinition api2 = new ApiDefinition("another_customized_api") .setPredicateItems(new HashSet<ApiPredicateItem>() {{ add(new ApiPathPredicateItem().setPattern("/**") .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); }}); definitions.add(api1); definitions.add(api2); GatewayApiDefinitionManager.loadApiDefinitions(definitions); } private void initGatewayRules() { Set<GatewayFlowRule> rules = new HashSet<>(); rules.add(new GatewayFlowRule("aliyun_route") .setCount(10) .setIntervalSec(1) ); rules.add(new GatewayFlowRule("aliyun_route") .setCount(2) .setIntervalSec(2) .setBurst(2) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP) ) ); rules.add(new GatewayFlowRule("httpbin_route") .setCount(10) .setIntervalSec(1) .setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER) .setMaxQueueingTimeoutMs(600) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER) .setFieldName("X-Sentinel-Flag") ) ); rules.add(new GatewayFlowRule("httpbin_route") .setCount(1) .setIntervalSec(1) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM) .setFieldName("pa") ) ); rules.add(new GatewayFlowRule("httpbin_route") .setCount(2) .setIntervalSec(30) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM) .setFieldName("type") .setPattern("warn") .setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_CONTAINS) ) ); rules.add(new GatewayFlowRule("some_customized_api") .setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME) .setCount(5) .setIntervalSec(1) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM) .setFieldName("pn") ) ); GatewayRuleManager.loadRules(rules); } }
9.1 custom current limiting exception handler
package com.wql.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.sc.SentinelGatewayFilter; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager; import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler; import org.springframework.beans.factory.ObjectProvider; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import javax.annotation.PostConstruct; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @Configuration public class GatewayConfiguration { private final List<ViewResolver> viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; /** * constructor * * @param viewResolversProvider * @param serverCodecConfigurer */ public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; } /** * Current limiting exception handler * * @return */ @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { // Register the block exception handler for Spring Cloud Gateway. return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer); } /** * Current limiting filter * * @return */ @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public GlobalFilter sentinelGatewayFilter() { return new SentinelGatewayFilter(); } /*--------------------------These are on the official website------------------------------*/ @PostConstruct public void doInit() { initGatewayRules(); initBlockHandle(); } /** * Gateway current limiting rules */ private void initGatewayRules() { HashSet<GatewayFlowRule> rules = new HashSet<>(); /** * resource: The resource name can be the route name in the gateway or the user-defined API group name * count: Current limiting threshold * intervalSec: Statistics time window, unit: seconds, default: 1s */ rules.add(new GatewayFlowRule("order-service").setCount(3).setIntervalSec(60)); //Load gateway current limiting rules GatewayRuleManager.loadRules(rules); } /** * Custom current limiting exception handler */ private void initBlockHandle() { BlockRequestHandler blockRequestHandler = new BlockRequestHandler() { @Override public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) { HashMap<String, String> result = new HashMap<>(); result.put("code", String.valueOf(HttpStatus.TOO_MANY_REQUESTS.value())); result.put("message", HttpStatus.TOO_MANY_REQUESTS.getReasonPhrase()); result.put("route", "order-service"); return ServerResponse .status(HttpStatus.TOO_MANY_REQUESTS) .contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(result)); } }; GatewayCallbackManager.setBlockHandler(blockRequestHandler); } }
9.2 current limiting grouping
package com.wql.config; import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager; 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.sc.SentinelGatewayFilter; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager; import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler; import org.springframework.beans.factory.ObjectProvider; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import javax.annotation.PostConstruct; import java.util.*; @Configuration public class GatewayConfiguration { private final List<ViewResolver> viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; /** * constructor * * @param viewResolversProvider * @param serverCodecConfigurer */ public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; } /** * Current limiting exception handler * * @return */ @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { // Register the block exception handler for Spring Cloud Gateway. return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer); } /** * Current limiting filter * * @return */ @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public GlobalFilter sentinelGatewayFilter() { return new SentinelGatewayFilter(); } /*--------------------------These are on the official website------------------------------*/ @PostConstruct public void doInit() { initGatewayRules(); initBlockHandle(); } /** * Gateway current limiting rules */ private void initGatewayRules() { HashSet<GatewayFlowRule> rules = new HashSet<>(); /** * resource: The resource name can be the route name in the gateway or the user-defined API group name * count: Current limiting threshold * intervalSec: Statistics time window, unit: seconds, default: 1s */ /*------------Current limiting grouping---------------------*/ rules.add(new GatewayFlowRule("product-api").setCount(3).setIntervalSec(60)); rules.add(new GatewayFlowRule("order-api").setCount(6).setIntervalSec(60)); /*---------------------------------------*/ //Load gateway current limiting rules GatewayRuleManager.loadRules(rules); //Load current limiting packet initCustomizedApis(); } private void initCustomizedApis() { Set<ApiDefinition> definitions = new HashSet<>(); //Product API Group ApiDefinition api1 = new ApiDefinition("product-api") .setPredicateItems(new HashSet<ApiPredicateItem>() {{ // All requests matching / product service / product and other subpaths add(new ApiPathPredicateItem().setPattern("/product-service/product/**") .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); }}); //order-api group ApiDefinition api2 = new ApiDefinition("order-api") .setPredicateItems(new HashSet<ApiPredicateItem>() {{ // Match only / order service / order / index add(new ApiPathPredicateItem().setPattern("/order-service/order/index")); }}); definitions.add(api1); definitions.add(api2); //Load current limiting packet GatewayApiDefinitionManager.loadApiDefinitions(definitions); } /** * Custom current limiting exception handler */ private void initBlockHandle() { BlockRequestHandler blockRequestHandler = new BlockRequestHandler() { @Override public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) { HashMap<String, String> result = new HashMap<>(); result.put("code", String.valueOf(HttpStatus.TOO_MANY_REQUESTS.value())); result.put("message", HttpStatus.TOO_MANY_REQUESTS.getReasonPhrase()); result.put("route", "order-service"); return ServerResponse .status(HttpStatus.TOO_MANY_REQUESTS) .contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(result)); } }; GatewayCallbackManager.setBlockHandler(blockRequestHandler); } }