In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-03-28 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Internet Technology >
Share
Shulou(Shulou.com)06/02 Report--
It is believed that many inexperienced people don't know what to do about how to realize gateway current limit in SpringCloud. Therefore, this paper summarizes the causes and solutions of the problem. Through this article, I hope you can solve this problem.
Route filters allow you to modify incoming HTTP requests or outgoing HTTP responses in some way, the scope of path filters is limited to specific paths, and Spring Cloud Gateway contains many built-in GatewayFilter factories.
Spring Cloud Gateway current limiting is achieved through the built-in RequestRateLimiterGateWayFilterFactory factory.
Of course, the official certainly can not meet some of our business needs, so we can customize the current-limiting filter.
# # if yml is configured as follows, you can add this interceptor to the route:
Spring: cloud: gateway: routes:-id: test_route uri: localhost predicates:-Path=/host/address filters:-name: RequestRateLimiter args: # # allows users to execute how many requests per second without dropping any requests. This is the rate at which token buckets are filled. Redis-rate-limiter.replenishRate: 1 # # is the maximum number of requests that a user is allowed to execute in a second. This is the number of tokens that the token bucket can hold. Setting this value to zero blocks all requests. Redis-rate-limiter.burstCapacity: 3 # # KeyResolver is a simple way to obtain user request parameters. Here, I use the host address as key to limit the current key-resolver: "# {@ hostAddrKeyResolver}"
# # RequestRateLimiterGateWayFilterFactory Code:
/ / AbstractGatewayFilterFactory implements GatewayFilterFactory interface, custom filter factory can inherit / / AbstractGatewayFilterFactory and write apply method public class RequestRateLimiterGatewayFilterFactory extends AbstractGatewayFilterFactory {public static final String KEY_RESOLVER_KEY = "keyResolver"; private final RateLimiter defaultRateLimiter; private final KeyResolver defaultKeyResolver; public RequestRateLimiterGatewayFilterFactory (RateLimiter defaultRateLimiter,KeyResolver defaultKeyResolver) {super (Config.class); this.defaultRateLimiter = defaultRateLimiter; this.defaultKeyResolver = defaultKeyResolver } public KeyResolver getDefaultKeyResolver () {return defaultKeyResolver;} public RateLimiter getDefaultRateLimiter () {return defaultRateLimiter;} @ SuppressWarnings ("unchecked") @ Override public GatewayFilter apply (Config config) {/ / yml hostAddrKeyResolver KeyResolver resolver = (config.keyResolver = = null)? DefaultKeyResolver: config.keyResolver; / / this is the specific implementation of current limit, using RedisRateLimiter RateLimiter limiter = (config.rateLimiter = = null) by default? DefaultRateLimiter: config.rateLimiter; return (exchange, chain)-> {Route route = exchange.getAttribute (ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR) Return resolver.resolve (exchange) .flatMap (key-> / / isAllowed here is the specific implementation. The input parameters are routing id and current limiting key (here is the host address hostAddress) / / TODO: if key is empty? Limiter.isAllowed (route.getId (), key) .flatMap (response-> {for (Map.Entry header: response.getHeaders (). EntrySet ()) {exchange.getResponse () .getHeaders () .add (header.getKey (), header.getValue () } / / if true, intercept if (response.isAllowed ()) {return chain.filter (exchange) } / / otherwise set the http code to 429 too many request exchange.getResponse (). SetStatusCode (config.getStatusCode ()); return exchange.getResponse (). SetComplete () });}
Analysis:
1. Load the KeyResolver, load it from the configuration file, where I configured hostAddrKeyResolver, that is, limit the flow according to the host address. If empty, use the default PrincipalNameKeyResolver
two。 Load RateLimiter, using RedisRateLimiter by default.
3. Execute the isAllowed method of RedisRateLimiter to get response, and intercept if isAllowed is true, otherwise return 429 (the isAllowed method is described below).
# # HostAddrKeyResolver:
@ Slf4jpublic class HostAddrKeyResolver implements KeyResolver {@ Override public Mono resolve (ServerWebExchange exchange) {log.info ("HostAddrKeyResolver current limit"); return Mono.just (exchange.getRequest () .getRemoteAddress () .getHostName ());}}
Inject bean into the startup class
@ Beanpublic HostAddrKeyResolver hostAddrKeyResolver () {return new HostAddrKeyResolver ();}
# # RedisRateLimiter:
@ Override @ SuppressWarnings ("unchecked") public Mono isAllowed (String routeId, String id) {/ / determine whether to initialize if (! this.initialized.get ()) {throw new IllegalStateException ("RedisRateLimiter is not initialized");} / / get the configuration Config routeConfig = getConfig (). GetOrDefault (routeId, defaultConfig) If (routeConfig = = null) {throw new IllegalArgumentException ("No Configuration found for route" + routeId);} / / token bucket filling rate int replenishRate = routeConfig.getReplenishRate (); / / token bucket can hold tokens int burstCapacity = routeConfig.getBurstCapacity () Try {/ / get the key of redis, pass List keys = getKeys (id) when executing lua script; / / get parameters, and pass List scriptArgs = Arrays.asList (replenishRate + ", burstCapacity +", Instant.now (). GetEpochSecond () + "," 1 ") Flux flux = this.redisTemplate.execute (this.script, keys, scriptArgs); / / .log ("redisratelimiter", Level.FINER); return flux.onErrorResume (throwable-> Flux.just (Arrays.asList (1L,-1L) .reduce (new ArrayList (), (longs, l)-> {longs.addAll (l)) Return longs;}) .map (results-> {boolean allowed = results.get (0) = = 1L; Long tokensLeft = results.get (1); Response response = new Response (allowed, getHeaders (routeConfig, tokensLeft)) If (log.isDebugEnabled ()) {log.debug ("response:" + response);} return response;}) } catch (Exception e) {/ * * We don't want a hard dependency on Redis to allow traffic. Make sure to set * an alert so you know if this is happening too much. Stripe's observed * failure rate is 0.01%. * / log.error ("Error determining if user allowed from redis", e);} return Mono.just (new Response (true, getHeaders (routeConfig,-1L));} @ NotNull public HashMap getHeaders (Config config, Long tokensLeft) {HashMap headers = new HashMap (); headers.put (this.remainingHeader, tokensLeft.toString ()); headers.put (this.replenishRateHeader, String.valueOf (config.getReplenishRate () Headers.put (this.burstCapacityHeader, String.valueOf (config.getBurstCapacity (); return headers;} static List getKeys (String id) {/ / use {} around keys to use Redis Key hash tags / / this allows for using redis cluster / / Make a unique key per user. String prefix = "request_rate_limiter. {" + id; / / number of tokens remaining in the token bucket String tokenKey = prefix + "} .tokens"; / / token last filling time String timestampKey = prefix + "} .timestamp"; return Arrays.asList (tokenKey, timestampKey);}
Analysis:
1. Determine whether to initialize, load configuration, get token filling rate and token bucket size
two。 Pass in the lua script according to the key values in the routing id combined into two redis
Number of tokens remaining in request_rate_limiter. {id} .tokens token bucket
Request_rate_limiter. {id} .timestamp token bucket last fill token time
3. Pass the combination of token filling rate, token bucket size, current time (in seconds), consumed tokens (default is 1) to the lua script
4. Execute lua script
5.flux.onErrorResume (throwable-> Flux.just (Arrays.asList (1L,-1L) this is the handling of the exception that occurs during the execution of the lua script. It ignores the exception and returns the token. In this way, you can decouple from redis and not rely heavily on it.
The core of this implementation is mainly reflected in the lua script, which uses the token bucket algorithm
For more information, please see request_rate_limiter.lua under spring-cloud-gateway-core
# # get the redis keylocal tokens_key of remaining tokens = KEYS [1] # # get the time of the last token filling local timestamp_key = KEYS [2]-- redis.log (redis.LOG_WARNING, "tokens_key".. Tokens_key) # # token filling rate local rate = tonumber (ARGV [1]) # # token bucket size local capacity = tonumber (ARGV [2]) # # current seconds local now = tonumber (ARGV [3]) # # token consumption Default 1local requested = tonumber (ARGV [4]) # # calculate the time the token bucket needs to be filled local fill_time = capacity/rate## calculate the key's survival time local ttl = math.floor (fill_time2)-redis.log (redis.LOG_WARNING, "rate".. ARGV [1])-redis.log (redis.LOG_WARNING, "capacity".. ARGV [2])-redis.log (redis.LOG_WARNING, "now".. ARGV [3])-redis.log (redis.LOG_WARNING, "requested".. ARGV [4])-redis.log (redis.LOG_WARNING, "filltime".. Fill_time)-redis.log (redis.LOG_WARNING, "ttl".. Ttl) # # get the remaining number of tokens local last_tokens = tonumber (redis.call ("get", tokens_key)) if last_tokens = = nil then last_tokens = capacityend--redis.log (redis.LOG_WARNING, "last_tokens".. Last_tokens) # # get token last fill time local last_refreshed = tonumber (redis.call ("get", timestamp_key)) if last_refreshed = = nil then last_refreshed = 0end--redis.log (redis.LOG_WARNING, "last_refreshed".. Last_refreshed) local delta = math.max (0, now-last_refreshed) # # calculate the remaining number of tokens local filled_tokens = math.min (capacity, last_tokens+ (deltarate)) # # greater than the request consumption token allowed set to truelocal allowed = filled_tokens > = requestedlocal new_tokens = filled_tokenslocal allowed_num = filled_tokens-requested allowed_num = 1end-- redis.log (redis.LOG_WARNING, "delta".. Delta)-redis.log (redis.LOG_WARNING, "filled_tokens".. Filled_tokens)-redis.log (redis.LOG_WARNING, "allowed_num".. Allowed_num)-redis.log (redis.LOG_WARNING, "new_tokens".. New_tokens) redis.call ("setex", tokens_key, ttl, new_tokens) redis.call ("setex", timestamp_key, ttl, now) return {allowed_num, new_tokens} after reading the above, have you mastered how to achieve gateway current limit in SpringCloud? If you want to learn more skills or want to know more about it, you are welcome to follow the industry information channel, thank you for reading!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.