這篇文章將為大家詳細(xì)講解有關(guān)Spring Cloud Gateway中一段腳本如何實現(xiàn)令牌桶,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。
成都創(chuàng)新互聯(lián)堅持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:成都網(wǎng)站制作、成都做網(wǎng)站、企業(yè)官網(wǎng)、英文網(wǎng)站、手機端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時代的洛陽網(wǎng)站設(shè)計、移動媒體設(shè)計的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!
在一個分布式高并發(fā)的系統(tǒng)設(shè)計中,限流是一個不可忽視的功能點。如果不對系統(tǒng)進(jìn)行有效的流量訪問限制,在雙十一和搶票這種流量洪峰的場景下,很容易就會把我們的系統(tǒng)打垮。而作為系統(tǒng)服務(wù)的衛(wèi)兵的網(wǎng)關(guān)組件,作為系統(tǒng)服務(wù)的統(tǒng)一入口,更需要考慮流量的限制,直接在網(wǎng)關(guān)層阻斷流量比在各個系統(tǒng)中實現(xiàn)更合適。Spring Cloud Gateway的實現(xiàn)中,就提供了限流的功能,下面主要分析下Spring Cloud Gateway中是如何通過一段lua腳本實現(xiàn)限流功能的。
限流的實現(xiàn)方式有多種,下面先回顧下幾種常見的實現(xiàn)算法
這種限流算法最簡單,也是最容易實現(xiàn)的,通過在單位時間內(nèi)設(shè)置最大訪問數(shù)就可以達(dá)到限流的目的。比如某個系統(tǒng)能夠承載的一般qps為60,那我們就可以使用計算器法,在單位時間一秒內(nèi),限制接口只能被訪問60次即可。但是這個算法實現(xiàn),正如其功能描述一樣,有個缺陷,假如在時間窗的前1%的時間內(nèi)流量就達(dá)到頂峰了,那么在時間窗內(nèi)還有99%的時間系統(tǒng)即使能夠繼續(xù)提供服務(wù),還是會被限流算法的這種缺陷阻斷在門外,這種缺陷也被稱為“突刺效應(yīng)“
漏桶法不同于計算器法,它有效的避免了計數(shù)器法限流的“突刺效應(yīng)”缺陷,實現(xiàn)也不復(fù)雜,通過固定大小的隊列+定時取隊列元素的方式即可實現(xiàn)。如其名漏桶,就像一個盛水的容器,漏桶法只限制容器出水的速率,當(dāng)進(jìn)水的速率過大時,將會填滿容器造成溢出,溢出部分的流量也就是拒絕的流量。比如,容器大小為100,出水速率為每秒10/s,當(dāng)桶為空時,最大的流量可以到達(dá)100/s,但是即使這樣,受限于固定的流出速率,后端處理的也只能是最大每秒10個,其余的流量都會被緩沖在漏桶中。這個也這是漏桶法的缺陷,沒法真正處理突發(fā)的流量洪峰,效率不高。
令牌桶法也是基于桶的原型,但是和漏桶算法截然不同的時,沒有出水口。令牌桶通過令牌的產(chǎn)生速率+令牌桶的容積來控制流量,有效的解決了漏桶效率不高的問題。如,容積為100的桶,令牌產(chǎn)生速率為50/s,那么就代表當(dāng)桶中令牌已滿的時候,最大能夠承載100的流量,后面如果流量一直居高不下,也會以每秒50個流量的速度恒速處理請求。令牌桶的這種特性有效的處理了洪峰流量也能做到不被洪峰壓垮,是目前限流比較常見的實現(xiàn)方法。比較著名的實現(xiàn)有谷歌guava中的RateLimiter。然后下面將要分析的Spring Cloud Gateway中也是使用的令牌桶算法實現(xiàn)的限流
guava的文檔:https://github.com/google/guava/wiki
Spring網(wǎng)關(guān)中是基于令牌桶+redis實現(xiàn)的網(wǎng)關(guān)分布式限流,具體的實現(xiàn)見下面兩個代碼:
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: ">
上面博主截取了Spring網(wǎng)關(guān)限流部分的關(guān)鍵代碼,可以看到,最關(guān)鍵的地方在于,使用reids執(zhí)行了一段lua腳本,然后通過返回值【0】是否等于1來判斷本次流量是否通過,返回值【1】為令牌桶中剩余的令牌數(shù)。就上面這段代碼沒有看到任何令牌桶算法的影子對吧,所有的精華實現(xiàn)都在request_rate_limiter.lua腳本里面,這個腳本最初是由Paul Tarjan分享出來的,源碼地址戳我。腳本如下:
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)來的這幾個屬性的具體含義:
tokens_key:當(dāng)前限流的標(biāo)識,可以是ip,或者在spring cloud系統(tǒng)中,可以是一個服務(wù)的serviceID
timestamp_key:令牌桶刷新的時間戳,后面會被用來計算當(dāng)前產(chǎn)生的令牌數(shù)
rate :令牌生產(chǎn)的速率,如每秒產(chǎn)生50個令牌
capacity :令牌桶的容積大小,比如最大100個,那么系統(tǒng)最大可承載100個并發(fā)請求
now :當(dāng)前時間戳
requested:當(dāng)前請求的令牌數(shù)量,Spring Cloud Gateway中默認(rèn)是1,也就是當(dāng)前請求
下面是主要邏輯分析:
-- 計算填滿桶需要多長時間
-- 得到填滿桶的2倍時間作為redis中key時效的時間,避免冗余太多無用的key
-- 這里和令牌桶的實現(xiàn)沒有太大的關(guān)系
-- 獲取桶中剩余的令牌,如果桶是空的,就將他填滿
-- 獲取當(dāng)前令牌桶最后的刷新時間,如果為空,則設(shè)置為0
-- 計算最后一次刷新令牌到當(dāng)前時間的時間差
-- 計算當(dāng)前令牌數(shù)量,這個地方是最關(guān)鍵的地方,通過剩余令牌數(shù) + 時間差內(nèi)產(chǎn)生的令牌得到當(dāng)前總令牌數(shù)量
-- 設(shè)置標(biāo)識allowad接收當(dāng)前令牌桶中的令牌數(shù)是否大于請求的令牌結(jié)果
-- 設(shè)置當(dāng)前令牌數(shù)量
-- 如果allowed為true,則將當(dāng)前令牌數(shù)量重置為通中的令牌數(shù) - 請求的令牌數(shù),并且設(shè)置allowed_num標(biāo)識為1
-- 將當(dāng)前令牌數(shù)量寫回到redis中,并重置令牌桶的最后刷新時間
-- 返回當(dāng)前是否申請到了令牌,以及當(dāng)前桶中剩余多少令牌
關(guān)于Spring Cloud Gateway中一段腳本如何實現(xiàn)令牌桶就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
分享名稱:SpringCloudGateway中一段腳本如何實現(xiàn)令牌桶
網(wǎng)頁地址:http://chinadenli.net/article16/jgjcdg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供軟件開發(fā)、做網(wǎng)站、靜態(tài)網(wǎng)站、網(wǎng)站導(dǎo)航、網(wǎng)頁設(shè)計公司、Google
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)