欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺析Spring?Cloud?Gateway中的令牌桶限流算法

 更新時(shí)間:2022年02月24日 17:30:48   作者:kl  
這篇文章主要為大家淺析了Spring?Cloud?Gateway中的令牌桶限流算法原理,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步

前言

在一個(gè)分布式高并發(fā)的系統(tǒng)設(shè)計(jì)中,限流是一個(gè)不可忽視的功能點(diǎn)。如果不對(duì)系統(tǒng)進(jìn)行有效的流量訪問(wèn)限制,在雙十一和搶票這種流量洪峰的場(chǎng)景下,很容易就會(huì)把我們的系統(tǒng)打垮。而作為系統(tǒng)服務(wù)的衛(wèi)兵的網(wǎng)關(guān)組件,作為系統(tǒng)服務(wù)的統(tǒng)一入口,更需要考慮流量的限制,直接在網(wǎng)關(guān)層阻斷流量比在各個(gè)系統(tǒng)中實(shí)現(xiàn)更合適。Spring Cloud Gateway的實(shí)現(xiàn)中,就提供了限流的功能,下面主要分析下Spring Cloud Gateway中是如何實(shí)現(xiàn)限流功能的。

回顧限流算法

限流的實(shí)現(xiàn)方式有多種,下面先回顧下幾種常見(jiàn)的實(shí)現(xiàn)算法

計(jì)數(shù)器/時(shí)間窗口法

這種限流算法最簡(jiǎn)單,也是最容易實(shí)現(xiàn)的,通過(guò)在單位時(shí)間內(nèi)設(shè)置最大訪問(wèn)數(shù)就可以達(dá)到限流的目的。比如某個(gè)系統(tǒng)能夠承載的一般qps為60,那我們就可以使用計(jì)算器法,在單位時(shí)間一秒內(nèi),限制接口只能被訪問(wèn)60次即可。但是這個(gè)算法實(shí)現(xiàn),正如其功能描述一樣,有個(gè)缺陷,假如在時(shí)間窗的前1%的時(shí)間內(nèi)流量就達(dá)到頂峰了,那么在時(shí)間窗內(nèi)還有99%的時(shí)間系統(tǒng)即使能夠繼續(xù)提供服務(wù),還是會(huì)被限流算法的這種缺陷阻斷在門外,這種缺陷也被稱為“突刺效應(yīng)“

漏桶法

漏桶法不同于計(jì)算器法,它有效的避免了計(jì)數(shù)器法限流的“突刺效應(yīng)”缺陷,實(shí)現(xiàn)也不復(fù)雜,通過(guò)固定大小的隊(duì)列+定時(shí)取隊(duì)列元素的方式即可實(shí)現(xiàn)。如其名漏桶,就像一個(gè)盛水的容器,漏桶法只限制容器出水的速率,當(dāng)進(jìn)水的速率過(guò)大時(shí),將會(huì)填滿容器造成溢出,溢出部分的流量也就是拒絕的流量。比如,容器大小為100,出水速率為每秒10/s,當(dāng)桶為空時(shí),最大的流量可以到達(dá)100/s,但是即使這樣,受限于固定的流出速率,后端處理的也只能是最大每秒10個(gè),其余的流量都會(huì)被緩沖在漏桶中。這個(gè)也這是漏桶法的缺陷,沒(méi)法真正處理突發(fā)的流量洪峰,效率不高。

令牌桶法

令牌桶法也是基于桶的原型,但是和漏桶算法截然不同的時(shí),沒(méi)有出水口。令牌桶通過(guò)令牌的產(chǎn)生速率+令牌桶的容積來(lái)控制流量,有效的解決了漏桶效率不高的問(wèn)題。如,容積為100的桶,令牌產(chǎn)生速率為50/s,那么就代表當(dāng)桶中令牌已滿的時(shí)候,最大能夠承載100的流量,后面如果流量一直居高不下,也會(huì)以每秒50個(gè)流量的速度恒速處理請(qǐng)求。令牌桶的這種特性有效的處理了洪峰流量也能做到不被洪峰壓垮,是目前限流比較常見(jiàn)的實(shí)現(xiàn)方法。比較著名的實(shí)現(xiàn)有谷歌guava中的RateLimiter。然后下面將要分析的Spring Cloud Gateway中也是使用的令牌桶算法實(shí)現(xiàn)的限流

guava的文檔:https://github.com/google/guava/wiki

Spring Cloud Gateway中的令牌桶

Spring網(wǎng)關(guān)中是基于令牌桶+redis實(shí)現(xiàn)的網(wǎng)關(guān)分布式限流,具體的實(shí)現(xiàn)見(jiàn)下面兩個(gè)代碼:

lua腳本地址:resources/META-INF/scripts/request_rate_limiter.lua

RedisRateLimiter:gateway/filter/ratelimit/RedisRateLimiter.java

try {
			Listkeys = getKeys(id);

			// The arguments to the LUA script. time() returns unixtime in seconds.
			ListscriptArgs = Arrays.asList(replenishRate + "",
					burstCapacity + "", Instant.now().getEpochSecond() + "", "1");
			// allowed, tokens_left = redis.eval(SCRIPT, keys, args)
			Fluxflux = 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;
					});
		}

上面博主截取了Spring網(wǎng)關(guān)限流部分的關(guān)鍵代碼,可以看到,最關(guān)鍵的地方在于,使用reids執(zhí)行了一段lua腳本,然后通過(guò)返回值【0】是否等于1來(lái)判斷本次流量是否通過(guò),返回值【1】為令牌桶中剩余的令牌數(shù)。就上面這段代碼沒(méi)有看到任何令牌桶算法的影子對(duì)吧,所有的精華實(shí)現(xiàn)都在lua腳本里面,這個(gè)腳本最初是由Paul Tarjan分享出來(lái)的,腳本如下:

local tokens_key = KEYS[1]
local timestamp_key = KEYS[2]
local rate = tonumber(ARGV[1])
local capacity = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])
local fill_time = capacity/rate
local ttl = math.floor(fill_time*2)
local last_tokens = tonumber(redis.call("get", tokens_key))
if last_tokens == nil then
  last_tokens = capacity
end
local last_refreshed = tonumber(redis.call("get", timestamp_key))
if last_refreshed == nil then
  last_refreshed = 0
end
local delta = math.max(0, now-last_refreshed)
local filled_tokens = math.min(capacity, last_tokens+(delta*rate))
local allowed = filled_tokens >= requested
local new_tokens = filled_tokens
local allowed_num = 0
if allowed then
  new_tokens = filled_tokens - requested
  allowed_num = 1
end
redis.call("setex", tokens_key, ttl, new_tokens)
redis.call("setex", timestamp_key, ttl, now)
return { allowed_num, new_tokens }

下面逐行分析下這段腳本。首先解釋下,從應(yīng)用中入?yún)⑦M(jìn)來(lái)的這幾個(gè)屬性的具體含義:

tokens_key:當(dāng)前限流的標(biāo)識(shí),可以是ip,或者在spring cloud系統(tǒng)中,可以是一個(gè)服務(wù)的serviceID

timestamp_key:令牌桶刷新的時(shí)間戳,后面會(huì)被用來(lái)計(jì)算當(dāng)前產(chǎn)生的令牌數(shù) 

rate :令牌生產(chǎn)的速率,如每秒產(chǎn)生50個(gè)令牌

capacity :令牌桶的容積大小,比如最大100個(gè),那么系統(tǒng)最大可承載100個(gè)并發(fā)請(qǐng)求

now :當(dāng)前時(shí)間戳

requested:當(dāng)前請(qǐng)求的令牌數(shù)量,Spring Cloud Gateway中默認(rèn)是1,也就是當(dāng)前請(qǐng)求

主要邏輯分析

-- 計(jì)算填滿桶需要多長(zhǎng)時(shí)間
-- 得到填滿桶的2倍時(shí)間作為redis中key時(shí)效的時(shí)間,避免冗余太多無(wú)用的key
-- 這里和令牌桶的實(shí)現(xiàn)沒(méi)有太大的關(guān)系
-- 獲取桶中剩余的令牌,如果桶是空的,就將他填滿
-- 獲取當(dāng)前令牌桶最后的刷新時(shí)間,如果為空,則設(shè)置為0
-- 計(jì)算最后一次刷新令牌到當(dāng)前時(shí)間的時(shí)間差
-- 計(jì)算當(dāng)前令牌數(shù)量,這個(gè)地方是最關(guān)鍵的地方,通過(guò)剩余令牌數(shù) + 時(shí)間差內(nèi)產(chǎn)生的令牌得到當(dāng)前總令牌數(shù)量
-- 設(shè)置標(biāo)識(shí)allowad接收當(dāng)前令牌桶中的令牌數(shù)是否大于請(qǐng)求的令牌結(jié)果
-- 設(shè)置當(dāng)前令牌數(shù)量
-- 如果allowed為true,則將當(dāng)前令牌數(shù)量重置為通中的令牌數(shù) - 請(qǐng)求的令牌數(shù),并且設(shè)置allowed_num標(biāo)識(shí)為1
-- 將當(dāng)前令牌數(shù)量寫回到redis中,并重置令牌桶的最后刷新時(shí)間
-- 返回當(dāng)前是否申請(qǐng)到了令牌,以及當(dāng)前桶中剩余多少令牌

以上就是淺析Spring Cloud Gateway中的令牌桶限流算法的詳細(xì)內(nèi)容,更多關(guān)于pring Cloud Gateway令牌桶限流算法的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論