SpringCloud large-scale series of courses are under production, welcome your attention and comments.
Programmers’ daily CV and bricks, but also know why, this series of courses can help beginners learn SpringBooot project development and SpringCloud microservice series project development
This article is one in a series
- 1. Basic engineering construction of SpringCloud project [SpringCloud Series 1]
- 2. SpringCloud integrates Nacos registration center [SpringCloud Series 2]
- 3. SpringCloud Feign remote call [SpringCloud Series 3]
- 4. SpringCloud Feign remote calls public class extraction [SpringCloud Series 4]
- 5. SpringCloud integrates Gateway service gateway [SpringCloud Series 5]
- 6. Spring Cloud integrates Spring Security authentication [Spring Cloud Series 6]
- Spring Cloud Gateway Gateway Authentication [Spring Cloud Series 7]
In a system with high concurrency, it is often necessary to limit the current in the system. Common current limiting methods:
- Hystrix applies thread pool isolation, exceeds the load of the thread pool, and follows the logic of fuse
- The tomcat container limits its number of threads by
- Control the flow by the average speed of the time window
Common Current Limiting Algorithms
- The counter algorithm generally limits the number of requests that can pass in one second
- Leaky bucket algorithm, there is a container inside the algorithm, which is similar to a funnel used in daily life. When a request comes in, it is equivalent to pouring water into the funnel, and then slowly and uniformly flow out from the lower end. Regardless of the flow rate above, the flow rate below remains constant
- Token bucket algorithm, the token bucket algorithm is an improvement to the leaky bucket algorithm, the bucket algorithm can limit the rate of request calls, and the token bucket algorithm can limit the average rate of calls while allowing a certain degree of burst calls
This article implements the current limit of redis without fuse function
1 Configure in the gateway project
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency>
Implement the KeyResolver interface. UriKeyResolver limits the flow of URI.
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @Component public class UriKeyResolver implements KeyResolver {<!-- --> @Override public Mono<String> resolve(ServerWebExchange exchange) {<!-- --> final String path = exchange. getRequest(). getURI(). getPath(); System.out.println(path); return Mono. just(path); } }
2 Configure the limit of the specified interface
For example, I configure the interface access limit of order-service here
server: port: 10001 spring: application: name: '@project.name@' redis: database: 0 # Redis database index (default is 0) host: localhost # Redis server address port: 6379 # Redis server connection port password: 12345678 # Redis server connection password (default is empty) cloud: nacos: server-addr: localhost:8848 # nacos address gateway: routes: # Gateway routing configuration - id: order-service # Routing id, custom, as long as it is unique # uri: http://127.0.0.1:8081 # The destination address of the route http is a fixed address uri: lb://order-service predicates: -Path=/order/** filters: - name: RequestRateLimiter args: # How many average requests are processed per second redis-rate-limiter.replenishRate: 1 # Order the maximum number of requests allowed to complete in one second redis-rate-limiter.burstCapacity: 1 # Get the Bean object, the ID of @Bean, the default bean name is the same as the method name. key-resolver: "#{@uriKeyResolver}"
-
“#{@uriKeyResolver}” in the key-resolver field corresponds to the UriKeyResolver configured above.
Then the current limiting rule I configured here is to process 1 average number of requests per second, and then I use postman to access the order interface to query order details -
The name field must be RequestRateLimiter
Call twice in a row, the first time it can be accessed normally, and the second time a 429 error occurs
3 custom 429 return error
The current limit filter is written in RequestRateLimiterGatewayFilterFactory, and the RequestRateLimiter configured in Gateway is the result of removing the suffix of this filter, so you only need to rewrite this filter.
import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory; import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter; import org.springframework.cloud.gateway.route.Route; import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; 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 reactor.core.publisher.Mono; import java.nio.charset.StandardCharsets; import java.util.Map; @Slf4j @Component public class GatewayRequestRateLimiterGatewayFilterFactory extends RequestRateLimiterGatewayFilterFactory {<!-- --> private final RateLimiter defaultRateLimiter; private final KeyResolver defaultKeyResolver; public GatewayRequestRateLimiterGatewayFilterFactory(RateLimiter defaultRateLimiter, KeyResolver defaultKeyResolver) {<!-- --> super(defaultRateLimiter, defaultKeyResolver); this.defaultRateLimiter = defaultRateLimiter; this.defaultKeyResolver = defaultKeyResolver; } @Override public GatewayFilter apply(Config config) {<!-- --> KeyResolver resolver = getOrDefault(config. getKeyResolver(), defaultKeyResolver); RateLimiter<Object> limiter = getOrDefault(config. getRateLimiter(), defaultRateLimiter); return (exchange, chain) -> resolver. resolve(exchange). flatMap(key -> {<!-- --> String routeId = config. getRouteId(); if (routeId == null) {<!-- --> Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR); routeId = route. getId(); } String finalRouteId = routeId; return limiter.isAllowed(routeId, key).flatMap(response -> {<!-- --> for (Map.Entry<String, String> header : response.getHeaders().entrySet()) {<!-- --> exchange.getResponse().getHeaders().add(header.getKey(), header.getValue()); } if (response. isAllowed()) {<!-- --> return chain. filter(exchange); } log.warn("Limited flow: {}", finalRouteId); ServerHttpResponse httpResponse = exchange. getResponse(); //Modify the code to 500 httpResponse.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); if (!httpResponse.getHeaders().containsKey("Content-Type")) {<!-- --> httpResponse.getHeaders().add("Content-Type", "application/json"); } //The global exception handling cannot be triggered here, return manually DataBuffer buffer = httpResponse.bufferFactory().wrap(("{\\ " + " "code": "1414"," + " "message": "Server current limit"," + " "data": "Server throttling"," + " "success": false" + "}").getBytes(StandardCharsets.UTF_8)); return httpResponse.writeWith(Mono.just(buffer)); }); }); } private <T> T getOrDefault(T configValue, T defaultValue) {<!-- --> return (configValue != null) ? configValue : defaultValue; } }
Then modify the current limit configuration, modify name: GatewayRequestRateLimiter
server: port: 10001 spring: application: name: '@project.name@' redis: database: 0 # Redis database index (default is 0) host: localhost # Redis server address port: 6379 # Redis server connection port password: 12345678 # Redis server connection password (default is empty) cloud: nacos: server-addr: localhost:8848 # nacos address gateway: routes: # Gateway routing configuration - id: order-service # Routing id, custom, as long as it is unique # uri: http://127.0.0.1:8081 # The destination address of the route http is a fixed address uri: lb://order-service predicates: -Path=/order/** filters: - name: GatewayRequestRateLimiter args: # How many average requests are processed per second redis-rate-limiter.replenishRate: 1 # Order the maximum number of requests allowed to complete in one second redis-rate-limiter.burstCapacity: 1 # Get the Bean object, the ID of @Bean, the default bean name is the same as the method name. key-resolver: "#{@uriKeyResolver}"
Then test the current limit again
Source code of this project https://gitee.com/android.long/spring-cloud-biglead/tree/master/biglead-api-08-redis-reactive
If you are interested, you can pay attention to the public account biglead, there will be java, Flutter, applet, js, English-related content sharing every week