www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當(dāng)前位置:首頁 > 單片機(jī) > 架構(gòu)師社區(qū)
[導(dǎo)讀]來源:https://www.aneasystone.com/archives/2020/08/spring-cloud-gateway-current-limiting.html話說在SpringCloudGateway問世之前,SpringCloud的微服務(wù)世界里,網(wǎng)關(guān)一定非...

來源:https://www.aneasystone.com/archives/2020/08/spring-cloud-gateway-current-limiting.html

話說在 Spring Cloud Gateway 問世之前,Spring Cloud 的微服務(wù)世界里,網(wǎng)關(guān)一定非 Netflix Zuul 莫屬。但是由于 Zuul 1.x 存在的一些問題,比如阻塞式的 API,不支持 WebSocket 等,一直被人所詬病,而且 Zuul 升級(jí)新版本依賴于 Netflix 公司,經(jīng)過幾次跳票之后,Spring 開源社區(qū)決定推出自己的網(wǎng)關(guān)組件,替代 Netflix Zuul。
從 18 年 6 月 Spring Cloud 發(fā)布的 Finchley 版本開始,Spring Cloud Gateway 逐漸嶄露頭角,它基于 Spring 5.0、Spring Boot 2.0 和 Project Reactor 等技術(shù)開發(fā),不僅支持響應(yīng)式和無阻塞式的 API,而且支持 WebSocket,和 Spring 框架緊密集成。盡管 Zuul 后來也推出了 2.x 版本,在底層使用了異步無阻塞式的 API,大大改善了其性能,但是目前看來 Spring 并沒有打算繼續(xù)集成它的計(jì)劃。根據(jù)官網(wǎng)的描述,Spring Cloud Gateway 的主要特性如下:
  • Built on Spring Framework 5, Project Reactor and Spring Boot 2.0
  • Able to match routes on any request attribute
  • Predicates and filters are specific to routes
  • Hystrix Circuit Breaker integration
  • Spring Cloud DiscoveryClient integration
  • Easy to write Predicates and Filters
  • Request Rate Limiting
  • Path Rewriting
可以看出 Spring Cloud Gateway 可以很方便的和 Spring Cloud 生態(tài)中的其他組件進(jìn)行集成(比如:斷路器和服務(wù)發(fā)現(xiàn)),而且提供了一套簡(jiǎn)單易寫的?斷言Predicates,有的地方也翻譯成?謂詞)和?過濾器Filters)機(jī)制,可以對(duì)每個(gè)?路由Routes)進(jìn)行特殊請(qǐng)求處理。最近在項(xiàng)目中使用了 Spring Cloud Gateway,并在它的基礎(chǔ)上實(shí)現(xiàn)了一些高級(jí)特性,如限流和留痕,在網(wǎng)關(guān)的使用過程中遇到了不少的挑戰(zhàn),于是趁著項(xiàng)目結(jié)束,抽點(diǎn)時(shí)間系統(tǒng)地學(xué)習(xí)并總結(jié)下。這篇文章主要學(xué)習(xí)限流技術(shù),首先我會(huì)介紹一些常見的限流場(chǎng)景和限流算法,然后介紹一些關(guān)于限流的開源項(xiàng)目,學(xué)習(xí)別人是如何實(shí)現(xiàn)限流的,最后介紹我是如何在網(wǎng)關(guān)中實(shí)現(xiàn)限流的,并分享一些實(shí)現(xiàn)過程中的經(jīng)驗(yàn)和遇到的坑。

一、常見的限流場(chǎng)景

緩存、降級(jí)?和?限流?被稱為高并發(fā)、分布式系統(tǒng)的三駕馬車,網(wǎng)關(guān)作為整個(gè)分布式系統(tǒng)中的第一道關(guān)卡,限流功能自然必不可少。通過限流,可以控制服務(wù)請(qǐng)求的速率,從而提高系統(tǒng)應(yīng)對(duì)突發(fā)大流量的能力,讓系統(tǒng)更具彈性。限流有著很多實(shí)際的應(yīng)用場(chǎng)景,比如雙十一的秒殺活動(dòng), 12306 的搶票等。

1.1 限流的對(duì)象

通過上面的介紹,我們對(duì)限流的概念可能感覺還是比較模糊,到底限流限的是什么?顧名思義,限流就是限制流量,但這里的流量是一個(gè)比較籠統(tǒng)的概念。如果考慮各種不同的場(chǎng)景,限流是非常復(fù)雜的,而且和具體的業(yè)務(wù)規(guī)則密切相關(guān),可以考慮如下幾種常見的場(chǎng)景:
  • 限制某個(gè)接口一分鐘內(nèi)最多請(qǐng)求 100 次
  • 限制某個(gè)用戶的下載速度最多 100KB/S
  • 限制某個(gè)用戶同時(shí)只能對(duì)某個(gè)接口發(fā)起 5 路請(qǐng)求
  • 限制某個(gè) IP 來源禁止訪問任何請(qǐng)求
從上面的例子可以看出,根據(jù)不同的請(qǐng)求者和請(qǐng)求資源,可以組合出不同的限流規(guī)則。可以根據(jù)請(qǐng)求者的 IP 來進(jìn)行限流,或者根據(jù)請(qǐng)求對(duì)應(yīng)的用戶來限流,又或者根據(jù)某個(gè)特定的請(qǐng)求參數(shù)來限流。而限流的對(duì)象可以是請(qǐng)求的頻率,傳輸?shù)乃俾?,或者并發(fā)量等,其中最常見的兩個(gè)限流對(duì)象是請(qǐng)求頻率和并發(fā)量,他們對(duì)應(yīng)的限流被稱為?請(qǐng)求頻率限流(Request rate limiting)和?并發(fā)量限流(Concurrent requests limiting)。傳輸速率限流?在下載場(chǎng)景下比較常用,比如一些資源下載站會(huì)限制普通用戶的下載速度,只有購買會(huì)員才能提速,這種限流的做法實(shí)際上和請(qǐng)求頻率限流類似,只不過一個(gè)限制的是請(qǐng)求量的多少,一個(gè)限制的是請(qǐng)求數(shù)據(jù)報(bào)文的大小。這篇文章主要介紹請(qǐng)求頻率限流和并發(fā)量限流。

1.2 限流的處理方式

在系統(tǒng)中設(shè)計(jì)限流方案時(shí),有一個(gè)問題值得設(shè)計(jì)者去仔細(xì)考慮,當(dāng)請(qǐng)求者被限流規(guī)則攔截之后,我們?cè)撊绾畏祷亟Y(jié)果。一般我們有下面三種限流的處理方式:
  • 拒絕服務(wù)
  • 排隊(duì)等待
  • 服務(wù)降級(jí)
最簡(jiǎn)單的做法是拒絕服務(wù),直接拋出異常,返回錯(cuò)誤信息(比如返回 HTTP 狀態(tài)碼 429 Too Many Requests),或者給前端返回 302 重定向到一個(gè)錯(cuò)誤頁面,提示用戶資源沒有了或稍后再試。但是對(duì)于一些比較重要的接口不能直接拒絕,比如秒殺、下單等接口,我們既不希望用戶請(qǐng)求太快,也不希望請(qǐng)求失敗,這種情況一般會(huì)將請(qǐng)求放到一個(gè)消息隊(duì)列中排隊(duì)等待,消息隊(duì)列可以起到削峰和限流的作用。第三種處理方式是服務(wù)降級(jí),當(dāng)觸發(fā)限流條件時(shí),直接返回兜底數(shù)據(jù),比如查詢商品庫存的接口,可以默認(rèn)返回有貨。

1.3 限流的架構(gòu)

針對(duì)不同的系統(tǒng)架構(gòu),需要使用不同的限流方案。如下圖所示,服務(wù)部署的方式一般可以分為單機(jī)模式和集群模式:實(shí)戰(zhàn)?Spring?Cloud?Gateway?之限流篇單機(jī)模式的限流非常簡(jiǎn)單,可以直接基于內(nèi)存就可以實(shí)現(xiàn),而集群模式的限流必須依賴于某個(gè)“中心化”的組件,比如網(wǎng)關(guān)或 Redis,從而引出兩種不同的限流架構(gòu):網(wǎng)關(guān)層限流?和?中間件限流。實(shí)戰(zhàn)?Spring?Cloud?Gateway?之限流篇網(wǎng)關(guān)作為整個(gè)分布式系統(tǒng)的入口,承擔(dān)了所有的用戶請(qǐng)求,所以在網(wǎng)關(guān)中進(jìn)行限流是最合適不過的。網(wǎng)關(guān)層限流有時(shí)也被稱為?接入層限流。除了我們使用的 Spring Cloud Gateway,最常用的網(wǎng)關(guān)層組件還有 Nginx,可以通過它的 ngx_http_limit_req_module 模塊,使用 limit_conn_zone、limit_req_zone、limit_rate 等指令很容易的實(shí)現(xiàn)并發(fā)量限流、請(qǐng)求頻率限流和傳輸速率限流。這里不對(duì) Nginx 作過多的說明,關(guān)于這幾個(gè)指令的詳細(xì)信息可以?參考 Nginx 的官方文檔。另一種限流架構(gòu)是中間件限流,可以將限流的邏輯下沉到服務(wù)層。但是集群中的每個(gè)服務(wù)必須將自己的流量信息統(tǒng)一匯總到某個(gè)地方供其他服務(wù)讀取,一般來說用 Redis 的比較多,Redis 提供的過期特性和 lua 腳本執(zhí)行非常適合做限流。除了 Redis 這種中間件,還有很多類似的分布式緩存系統(tǒng)都可以使用,如 Hazelcast、Apache Ignite、Infinispan 等。我們可以更進(jìn)一步擴(kuò)展上面的架構(gòu),將網(wǎng)關(guān)改為集群模式,雖然這還是網(wǎng)關(guān)層限流架構(gòu),但是由于網(wǎng)關(guān)變成了集群模式,所以網(wǎng)關(guān)必須依賴于中間件進(jìn)行限流,這和上面討論的中間件限流沒有區(qū)別。實(shí)戰(zhàn)?Spring?Cloud?Gateway?之限流篇

二、常見的限流算法

通過上面的學(xué)習(xí),我們知道限流可以分為請(qǐng)求頻率限流和并發(fā)量限流,根據(jù)系統(tǒng)架構(gòu)的不同,又可以分為網(wǎng)關(guān)層限流和分布式限流。在不同的應(yīng)用場(chǎng)景下,我們需要采用不同的限流算法。這一節(jié)將介紹一些主流的限流算法。有一點(diǎn)要注意的是,利用池化技術(shù)也可以達(dá)到限流的目的,比如線程池或連接池,但這不是本文的重點(diǎn)。

2.1 固定窗口算法(Fixed Window)

固定窗口算法是一種最簡(jiǎn)單的限流算法,它根據(jù)限流的條件,將請(qǐng)求時(shí)間映射到一個(gè)時(shí)間窗口,再使用計(jì)數(shù)器累加訪問次數(shù)。譬如限流條件為每分鐘 5 次,那么就按照分鐘為單位映射時(shí)間窗口,假設(shè)一個(gè)請(qǐng)求時(shí)間為 11:00:45,時(shí)間窗口就是 11:00:00 ~ 11:00:59,在這個(gè)時(shí)間窗口內(nèi)設(shè)定一個(gè)計(jì)數(shù)器,每來一個(gè)請(qǐng)求計(jì)數(shù)器加一,當(dāng)這個(gè)時(shí)間窗口的計(jì)數(shù)器超過 5 時(shí),就觸發(fā)限流條件。當(dāng)請(qǐng)求時(shí)間落在下一個(gè)時(shí)間窗口內(nèi)時(shí)(11:01:00 ~ 11:01:59),上一個(gè)窗口的計(jì)數(shù)器失效,當(dāng)前的計(jì)數(shù)器清零,重新開始計(jì)數(shù)。計(jì)數(shù)器算法非常容易實(shí)現(xiàn),在單機(jī)場(chǎng)景下可以使用 AtomicLong、LongAdderSemaphore 來實(shí)現(xiàn)計(jì)數(shù),而在分布式場(chǎng)景下可以通過 Redis 的 INCR EXPIRE 等命令并結(jié)合 EVAL 或 lua 腳本來實(shí)現(xiàn),Redis 官網(wǎng)提供了幾種簡(jiǎn)單的實(shí)現(xiàn)方式。無論是請(qǐng)求頻率限流還是并發(fā)量限流都可以使用這個(gè)算法。不過這個(gè)算法的缺陷也比較明顯,那就是存在嚴(yán)重的臨界問題。由于每過一個(gè)時(shí)間窗口,計(jì)數(shù)器就會(huì)清零,這使得限流效果不夠平滑,惡意用戶可以利用這個(gè)特點(diǎn)繞過我們的限流規(guī)則。如下圖所示,我們的限流條件本來是每分鐘 5 次,但是惡意用戶在 11:00:00 ~ 11:00:59 這個(gè)時(shí)間窗口的后半分鐘發(fā)起 5 次請(qǐng)求,接下來又在 11:01:00 ~ 11:01:59 這個(gè)時(shí)間窗口的前半分鐘發(fā)起 5 次請(qǐng)求,這樣我們的系統(tǒng)就在 1 分鐘內(nèi)承受了 10 次請(qǐng)求。實(shí)戰(zhàn)?Spring?Cloud?Gateway?之限流篇

2.2 滑動(dòng)窗口算法(Rolling Window 或 Sliding Window)

為了解決固定窗口算法的臨界問題,可以將時(shí)間窗口劃分成更小的時(shí)間窗口,然后隨著時(shí)間的滑動(dòng)刪除相應(yīng)的小窗口,而不是直接滑過一個(gè)大窗口,這就是滑動(dòng)窗口算法。我們?yōu)槊總€(gè)小時(shí)間窗口都設(shè)置一個(gè)計(jì)數(shù)器,大時(shí)間窗口的總請(qǐng)求次數(shù)就是每個(gè)小時(shí)間窗口的計(jì)數(shù)器的和。如下圖所示,我們的時(shí)間窗口是 5 秒,可以按秒進(jìn)行劃分,將其劃分成 5 個(gè)小窗口,時(shí)間每過一秒,時(shí)間窗口就滑過一秒:實(shí)戰(zhàn)?Spring?Cloud?Gateway?之限流篇每次處理請(qǐng)求時(shí),都需要計(jì)算所有小時(shí)間窗口的計(jì)數(shù)器的和,考慮到性能問題,劃分的小時(shí)間窗口不宜過多,譬如限流條件是每小時(shí) N 個(gè),可以按分鐘劃分為 60 個(gè)窗口,而不是按秒劃分成 3600 個(gè)。當(dāng)然如果不考慮性能問題,劃分粒度越細(xì),限流效果就越平滑。相反,如果劃分粒度越粗,限流效果就越不精確,出現(xiàn)臨界問題的可能性也就越大,當(dāng)劃分粒度為 1 時(shí),滑動(dòng)窗口算法就退化成了固定窗口算法。由于這兩種算法都使用了計(jì)數(shù)器,所以也被稱為?計(jì)數(shù)器算法(Counters)。進(jìn)一步思考我們發(fā)現(xiàn),如果劃分粒度最粗,也就是只有一個(gè)時(shí)間窗口時(shí),滑動(dòng)窗口算法退化成了固定窗口算法;那如果我們把劃分粒度調(diào)到最細(xì),又會(huì)如何呢?那么怎樣才能讓劃分的時(shí)間窗口最細(xì)呢?時(shí)間窗口細(xì)到一定地步時(shí),意味著每個(gè)時(shí)間窗口中只能容納一個(gè)請(qǐng)求,這樣我們可以省略計(jì)數(shù)器,只記錄每個(gè)請(qǐng)求的時(shí)間,然后統(tǒng)計(jì)一段時(shí)間內(nèi)的請(qǐng)求數(shù)有多少個(gè)即可。具體的實(shí)現(xiàn)可以參考Redis sorted set 技巧?和Sliding window log 算法。

2.3 漏桶算法(Leaky Bucket)

除了計(jì)數(shù)器算法,另一個(gè)很自然的限流思路是將所有的請(qǐng)求緩存到一個(gè)隊(duì)列中,然后按某個(gè)固定的速度慢慢處理,這其實(shí)就是漏桶算法(Leaky Bucket)。漏桶算法假設(shè)將請(qǐng)求裝到一個(gè)桶中,桶的容量為 M,當(dāng)桶滿時(shí),請(qǐng)求被丟棄。在桶的底部有一個(gè)洞,桶中的請(qǐng)求像水一樣按固定的速度(每秒 r 個(gè))漏出來。我們用下面這個(gè)形象的圖來表示漏桶算法:實(shí)戰(zhàn)?Spring?Cloud?Gateway?之限流篇桶的上面是個(gè)水龍頭,我們的請(qǐng)求從水龍頭流到桶中,水龍頭流出的水速不定,有時(shí)快有時(shí)慢,這種忽快忽慢的流量叫做 Bursty flow。如果桶中的水滿了,多余的水就會(huì)溢出去,相當(dāng)于請(qǐng)求被丟棄。從桶底部漏出的水速是固定不變的,可以看出漏桶算法可以平滑請(qǐng)求的速率。漏桶算法可以通過一個(gè)隊(duì)列來實(shí)現(xiàn),如下圖所示:實(shí)戰(zhàn)?Spring?Cloud?Gateway?之限流篇當(dāng)請(qǐng)求到達(dá)時(shí),不直接處理請(qǐng)求,而是將其放入一個(gè)隊(duì)列,然后另一個(gè)線程以固定的速率從隊(duì)列中讀取請(qǐng)求并處理,從而達(dá)到限流的目的。注意的是這個(gè)隊(duì)列可以有不同的實(shí)現(xiàn)方式,比如設(shè)置請(qǐng)求的存活時(shí)間,或?qū)㈥?duì)列改造成 PriorityQueue,根據(jù)請(qǐng)求的優(yōu)先級(jí)排序而不是先進(jìn)先出。當(dāng)然隊(duì)列也有滿的時(shí)候,如果隊(duì)列已經(jīng)滿了,那么請(qǐng)求只能被丟棄了。漏桶算法有一個(gè)缺陷,在處理突發(fā)流量時(shí)效率很低,于是人們又想出了下面的令牌桶算法。

2.4 令牌桶算法(Token Bucket)

令牌桶算法(Token Bucket)是目前應(yīng)用最廣泛的一種限流算法,它的基本思想由兩部分組成:生成令牌?和?消費(fèi)令牌。
  • 生成令牌:假設(shè)有一個(gè)裝令牌的桶,最多能裝 M 個(gè),然后按某個(gè)固定的速度(每秒 r 個(gè))往桶中放入令牌,桶滿時(shí)不再放入;
  • 消費(fèi)令牌:我們的每次請(qǐng)求都需要從桶中拿一個(gè)令牌才能放行,當(dāng)桶中沒有令牌時(shí)即觸發(fā)限流,這時(shí)可以將請(qǐng)求放入一個(gè)緩沖隊(duì)列中排隊(duì)等待,或者直接拒絕;
令牌桶算法的圖示如下:實(shí)戰(zhàn)?Spring?Cloud?Gateway?之限流篇在上面的圖中,我們將請(qǐng)求放在一個(gè)緩沖隊(duì)列中,可以看出這一部分的邏輯和漏桶算法幾乎一模一樣,只不過在處理請(qǐng)求上,一個(gè)是以固定速率處理,一個(gè)是從桶中獲取令牌后才處理。仔細(xì)思考就會(huì)發(fā)現(xiàn),令牌桶算法有一個(gè)很關(guān)鍵的問題,就是桶大小的設(shè)置,正是這個(gè)參數(shù)可以讓令牌桶算法具備處理突發(fā)流量的能力。譬如將桶大小設(shè)置為 100,生成令牌的速度設(shè)置為每秒 10 個(gè),那么在系統(tǒng)空閑一段時(shí)間的之后(桶中令牌一直沒有消費(fèi),慢慢的會(huì)被裝滿),突然來了 50 個(gè)請(qǐng)求,這時(shí)系統(tǒng)可以直接按每秒 50 個(gè)的速度處理,隨著桶中的令牌很快用完,處理速度又會(huì)慢慢降下來,和生成令牌速度趨于一致。這是令牌桶算法和漏桶算法最大的區(qū)別,漏桶算法無論來了多少請(qǐng)求,只會(huì)一直以每秒 10 個(gè)的速度進(jìn)行處理。當(dāng)然,處理突發(fā)流量雖然提高了系統(tǒng)性能,但也給系統(tǒng)帶來了一定的壓力,如果桶大小設(shè)置不合理,突發(fā)的大流量可能會(huì)直接壓垮系統(tǒng)。通過上面對(duì)令牌桶的原理分析,一般會(huì)有兩種不同的實(shí)現(xiàn)方式。第一種方式是啟動(dòng)一個(gè)內(nèi)部線程,不斷的往桶中添加令牌,處理請(qǐng)求時(shí)從桶中獲取令牌,和上面圖中的處理邏輯一樣。第二種方式不依賴于內(nèi)部線程,而是在每次處理請(qǐng)求之前先實(shí)時(shí)計(jì)算出要填充的令牌數(shù)并填充,然后再從桶中獲取令牌。下面是第二種方式的一種經(jīng)典實(shí)現(xiàn),其中 capacity 表示令牌桶大小,refillTokensPerOneMillis 表示填充速度,每毫秒填充多少個(gè),availableTokens 表示令牌桶中還剩多少個(gè)令牌,lastRefillTimestamp 表示上一次填充時(shí)間。 1public?class?TokenBucket?{
2
3????private?final?long?capacity;
4????private?final?double?refillTokensPerOneMillis;
5????private?double?availableTokens;
6????private?long?lastRefillTimestamp;
7
8????public?TokenBucket(long?capacity,?long?refillTokens,?long?refillPeriodMillis)?{
9????????this.capacity?=?capacity;
10????????this.refillTokensPerOneMillis?=?(double)?refillTokens?/?(double)?refillPeriodMillis;
11????????this.availableTokens?=?capacity;
12????????this.lastRefillTimestamp?=?System.currentTimeMillis();
13????}
14
15????synchronized?public?boolean?tryConsume(int?numberTokens)?{
16????????refill();
17????????if?(availableTokens?18????????????return?false;
19????????}?else?{
20????????????availableTokens?-=?numberTokens;
21????????????return?true;
22????????}
23????}
24
25????private?void?refill()?{
26????????long?currentTimeMillis?=?System.currentTimeMillis();
27????????if?(currentTimeMillis?>?lastRefillTimestamp)?{
28????????????long?millisSinceLastRefill?=?currentTimeMillis?-?lastRefillTimestamp;
29????????????double?refill?=?millisSinceLastRefill?*?refillTokensPerOneMillis;
30????????????this.availableTokens?=?Math.min(capacity,?availableTokens? ?refill);
31????????????this.lastRefillTimestamp?=?currentTimeMillis;
32????????}
33????}
34}
可以像下面這樣創(chuàng)建一個(gè)令牌桶(桶大小為 100,且每秒生成 100 個(gè)令牌):1TokenBucket?limiter?=?new?TokenBucket(100,?100,?1000);
從上面的代碼片段可以看出,令牌桶算法的實(shí)現(xiàn)非常簡(jiǎn)單也非常高效,僅僅通過幾個(gè)變量的運(yùn)算就實(shí)現(xiàn)了完整的限流功能。核心邏輯在于 refill()?這個(gè)方法,在每次消費(fèi)令牌時(shí),計(jì)算當(dāng)前時(shí)間和上一次填充的時(shí)間差,并根據(jù)填充速度計(jì)算出應(yīng)該填充多少令牌。在重新填充令牌后,再判斷請(qǐng)求的令牌數(shù)是否足夠,如果不夠,返回 false,如果足夠,則減去令牌數(shù),并返回 true。在實(shí)際的應(yīng)用中,往往不會(huì)直接使用這種原始的令牌桶算法,一般會(huì)在它的基礎(chǔ)上作一些改進(jìn),比如,填充速率支持動(dòng)態(tài)調(diào)整,令牌總數(shù)支持透支,基于 Redis 支持分布式限流等,不過總體來說還是符合令牌桶算法的整體框架,我們?cè)诤竺鎸W(xué)習(xí)一些開源項(xiàng)目時(shí)對(duì)此會(huì)有更深的體會(huì)。

三、一些開源項(xiàng)目

有很多開源項(xiàng)目中都實(shí)現(xiàn)了限流的功能,這一節(jié)通過一些開源項(xiàng)目的學(xué)習(xí),了解限流是如何實(shí)現(xiàn)的。

3.1 Guava 的 RateLimiter

Google Guava 是一個(gè)強(qiáng)大的核心庫,包含了很多有用的工具類,例如:集合、緩存、并發(fā)庫、字符串處理、I/O 等等。其中在并發(fā)庫中,Guava 提供了兩個(gè)和限流相關(guān)的類:RateLimiter 和 SmoothRateLimiter。Guava 的 RateLimiter 基于令牌桶算法實(shí)現(xiàn),不過在傳統(tǒng)的令牌桶算法基礎(chǔ)上做了點(diǎn)改進(jìn),支持兩種不同的限流方式:平滑突發(fā)限流(SmoothBursty)?和?平滑預(yù)熱限流(SmoothWarmingUp)。下面的方法可以創(chuàng)建一個(gè)平滑突發(fā)限流器(SmoothBursty):1RateLimiter?limiter?=?RateLimiter.create(5);
RateLimiter.create(5)?表示這個(gè)限流器容量為 5,并且每秒生成 5 個(gè)令牌,也就是每隔 200 毫秒生成一個(gè)。我們可以使用 limiter.acquire()?消費(fèi)令牌,如果桶中令牌足夠,返回 0,如果令牌不足,則阻塞等待,并返回等待的時(shí)間。我們連續(xù)請(qǐng)求幾次:1System.out.println(limiter.acquire());
2System.out.println(limiter.acquire());
3System.out.println(limiter.acquire());
4System.out.println(limiter.acquire());
輸出結(jié)果如下:
10.0
20.198239
30.196083
40.200609
可以看出限流器創(chuàng)建之后,初始會(huì)有一個(gè)令牌,然后每隔 200 毫秒生成一個(gè)令牌,所以第一次請(qǐng)求直接返回 0,后面的請(qǐng)求都會(huì)阻塞大約 200 毫秒。另外,SmoothBursty 還具有應(yīng)對(duì)突發(fā)的能力,而且?還允許消費(fèi)未來的令牌,比如下面的例子:
1RateLimiter?limiter?=?RateLimiter.create(5);
2System.out.println(limiter.acquire(10));
3System.out.println(limiter.acquire(1));
4System.out.println(limiter.acquire(1));
會(huì)得到類似下面的輸出:
10.0
21.997428
30.192273
40.200616
限流器創(chuàng)建之后,初始令牌只有一個(gè),但是我們請(qǐng)求 10 個(gè)令牌竟然也通過了,只不過看后面請(qǐng)求發(fā)現(xiàn),第二次請(qǐng)求花了 2 秒左右的時(shí)間把前面的透支的令牌給補(bǔ)上了。
Guava 支持的另一種限流方式是平滑預(yù)熱限流器(SmoothWarmingUp),可以通過下面的方法創(chuàng)建:1RateLimiter?limiter?=?RateLimiter.create(2,?3,?TimeUnit.SECONDS);
2System.out.println(limiter.acquire(1));
3System.out.println(limiter.acquire(1));
4System.out.println(limiter.acquire(1));
5System.out.println(limiter.acquire(1));
6System.out.println(limiter.acquire(1));
第一個(gè)參數(shù)還是每秒創(chuàng)建的令牌數(shù)量,這里是每秒 2 個(gè),也就是每 500 毫秒生成一個(gè),后面的參數(shù)表示從冷啟動(dòng)速率過渡到平均速率的時(shí)間間隔,也就是所謂的熱身時(shí)間間隔(warm up period)。我們看下輸出結(jié)果:
10.0
21.329289
30.994375
40.662888
50.501287
第一個(gè)請(qǐng)求還是立即得到令牌,但是后面的請(qǐng)求和上面平滑突發(fā)限流就完全不一樣了,按理來說 500 毫秒就會(huì)生成一個(gè)令牌,但是我們發(fā)現(xiàn)第二個(gè)請(qǐng)求卻等了 1.3s,而不是 0.5s,后面第三個(gè)和第四個(gè)請(qǐng)求也等了一段時(shí)間。不過可以看出,等待時(shí)間在慢慢的接近 0.5s,直到第五個(gè)請(qǐng)求等待時(shí)間才開始變得正常。從第一個(gè)請(qǐng)求到第五個(gè)請(qǐng)求,這中間的時(shí)間間隔就是熱身階段,可以算出熱身的時(shí)間就是我們?cè)O(shè)置的 3 秒。

3.2 Bucket4j

Bucket4j是一個(gè)基于令牌桶算法實(shí)現(xiàn)的強(qiáng)大的限流庫,它不僅支持單機(jī)限流,還支持通過諸如 Hazelcast、Ignite、Coherence、Infinispan 或其他兼容 JCache API (JSR 107)?規(guī)范的分布式緩存實(shí)現(xiàn)分布式限流。在使用 Bucket4j 之前,我們有必要先了解 Bucket4j 中的幾個(gè)核心概念:
  • Bucket
  • Bandwidth
  • Refill
Bucket 接口代表了令牌桶的具體實(shí)現(xiàn),也是我們操作的入口。它提供了諸如 tryConsume tryConsumeAndReturnRemaining 這樣的方法供我們消費(fèi)令牌??梢酝ㄟ^下面的構(gòu)造方法來創(chuàng)建Bucket:1Bucket?bucket?=?Bucket4j.builder().addLimit(limit).build();
2if(bucket.tryConsume(1))?{
3????System.out.println("ok");
4}?else?{
5????System.out.println("error");
6}
Bandwidth 的意思是帶寬, 可以理解為限流的規(guī)則。Bucket4j 提供了兩種方法來創(chuàng)建 Bandwidth:simple classic。下面是 simple 方式創(chuàng)建的 Bandwidth,表示桶大小為 10,填充速度為每分鐘 10 個(gè)令牌:
1Bandwidth?limit?=?Bandwidth.simple(10,?Duration.ofMinutes(1));
simple方式桶大小和填充速度是一樣的,classic 方式更靈活一點(diǎn),可以自定義填充速度,下面的例子表示桶大小為 10,填充速度為每分鐘 5 個(gè)令牌:
1Refill?filler?=?Refill.greedy(5,?Duration.ofMinutes(1));
2Bandwidth?limit?=?Bandwidth.classic(10,?filler);
其中,Refill 用于填充令牌桶,可以通過它定義填充速度,Bucket4j 有兩種填充令牌的策略:間隔策略(intervally)?和?貪婪策略(greedy)。在上面的例子中我們使用的是貪婪策略,如果使用間隔策略可以像下面這樣創(chuàng)建 Refill
1Refill?filler?=?Refill.intervally(5,?Duration.ofMinutes(1));
所謂間隔策略指的是每隔一段時(shí)間,一次性的填充所有令牌,比如上面的例子,會(huì)每隔一分鐘,填充 5 個(gè)令牌,如下所示:
實(shí)戰(zhàn)?Spring?Cloud?Gateway?之限流篇而貪婪策略會(huì)盡可能貪婪的填充令牌,同樣是上面的例子,會(huì)將一分鐘劃分成 5 個(gè)更小的時(shí)間單元,每隔 12 秒,填充 1 個(gè)令牌,如下所示:實(shí)戰(zhàn)?Spring?Cloud?Gateway?之限流篇在了解了 Bucket4j 中的幾個(gè)核心概念之后,我們?cè)賮砜纯垂倬W(wǎng)介紹的一些特性:
  • 基于令牌桶算法
  • 高性能,無鎖實(shí)現(xiàn)
  • 不存在精度問題,所有計(jì)算都是基于整型的
  • 支持通過符合 JCache API 規(guī)范的分布式緩存系統(tǒng)實(shí)現(xiàn)分布式限流
  • 支持為每個(gè) Bucket 設(shè)置多個(gè) Bandwidth
  • 支持同步和異步 API
  • 支持可插拔的監(jiān)聽 API,用于集成監(jiān)控和日志
  • 不僅可以用于限流,還可以用于簡(jiǎn)單的調(diào)度
Bucket4j 提供了豐富的文檔,推薦在使用 Bucket4j 之前,先把官方文檔中的?基本用法?和?高級(jí)特性?仔細(xì)閱讀一遍。另外,關(guān)于 Bucket4j 的使用,推薦這篇文章 Rate limiting Spring MVC endpoints with bucket4j,這篇文章詳細(xì)的講解了如何在 Spring MVC 中使用攔截器和 Bucket4j 打造業(yè)務(wù)無侵入的限流方案,另外還講解了如何使用 Hazelcast 實(shí)現(xiàn)分布式限流;另外,Rate Limiting a Spring API Using Bucket4j 這篇文章也是一份很好的入門教程,介紹了 Bucket4j 的基礎(chǔ)知識(shí),在文章的最后還提供了 Spring Boot Starter 的集成方式,結(jié)合 Spring Boot Actuator 很容易將限流指標(biāo)集成到監(jiān)控系統(tǒng)中。和 Guava 的限流器相比,Bucket4j 的功能顯然要更勝一籌,畢竟 Guava 的目的只是用作通用工具類,而不是用于限流的。使用 Bucket4j 基本上可以滿足我們的大多數(shù)要求,不僅支持單機(jī)限流和分布式限流,而且可以很好的集成監(jiān)控,搭配 Prometheus 和 Grafana 簡(jiǎn)直完美。值得一提的是,有很多開源項(xiàng)目譬如 JHipster API Gateway 就是使用 Bucket4j 來實(shí)現(xiàn)限流的。Bucket4j 唯一不足的地方是它只支持請(qǐng)求頻率限流,不支持并發(fā)量限流,另外還有一點(diǎn),雖然 Bucket4j 支持分布式限流,但它是基于 Hazelcast 這樣的分布式緩存系統(tǒng)實(shí)現(xiàn)的,不能使用 Redis,這在很多使用 Redis 作緩存的項(xiàng)目中就很不爽,所以我們還需要在開源的世界里繼續(xù)探索。

3.3 Resilience4j

Resilience4j 是一款輕量級(jí)、易使用的高可用框架。用過 Spring Cloud 早期版本的同學(xué)肯定都聽過 Netflix Hystrix,Resilience4j 的設(shè)計(jì)靈感就來自于它。自從 Hystrix 停止維護(hù)之后,官方也推薦大家使用 Resilience4j 來代替 Hystrix。實(shí)戰(zhàn)?Spring?Cloud?Gateway?之限流篇Resilience4j 的底層采用 Vavr,這是一個(gè)非常輕量級(jí)的 Java 函數(shù)式庫,使得 Resilience4j 非常適合函數(shù)式編程。Resilience4j 以裝飾器模式提供對(duì)函數(shù)式接口或 lambda 表達(dá)式的封裝,提供了一波高可用機(jī)制:重試(Retry)熔斷(Circuit Breaker)、限流(Rate Limiter)、限時(shí)(Timer Limiter)、隔離(Bulkhead)緩存(Caceh)?和?降級(jí)(Fallback)。我們重點(diǎn)關(guān)注這里的兩個(gè)功能:限流(Rate Limiter)?和?隔離(Bulkhead),Rate Limiter 是請(qǐng)求頻率限流,Bulkhead 是并發(fā)量限流。Resilience4j 提供了兩種限流的實(shí)現(xiàn):SemaphoreBasedRateLimiter AtomicRateLimiter。SemaphoreBasedRateLimiter 基于信號(hào)量實(shí)現(xiàn),用戶的每次請(qǐng)求都會(huì)申請(qǐng)一個(gè)信號(hào)量,并記錄申請(qǐng)的時(shí)間,申請(qǐng)通過則允許請(qǐng)求,申請(qǐng)失敗則限流,另外有一個(gè)內(nèi)部線程會(huì)定期掃描過期的信號(hào)量并釋放,很顯然這是令牌桶的算法。AtomicRateLimiter 和上面的經(jīng)典實(shí)現(xiàn)類似,不需要額外的線程,在處理每次請(qǐng)求時(shí),根據(jù)距離上次請(qǐng)求的時(shí)間和生成令牌的速度自動(dòng)填充。關(guān)于這二者的區(qū)別可以參考文章 Rate Limiter Internals in Resilience4j。Resilience4j 也提供了兩種隔離的實(shí)現(xiàn):SemaphoreBulkheadThreadPoolBulkhead,通過信號(hào)量或線程池控制請(qǐng)求的并發(fā)數(shù),具體的用法參考官方文檔,這里不再贅述。下面是一個(gè)同時(shí)使用限流和隔離的例子: 1//?創(chuàng)建一個(gè)?Bulkhead,最大并發(fā)量為?150
2BulkheadConfig?bulkheadConfig?=?BulkheadConfig.custom()
3????.maxConcurrentCalls(150)
4????.maxWaitTime(100)
5????.build();
6Bulkhead?bulkhead?=?Bulkhead.of("backendName",?bulkheadConfig);
7
8//?創(chuàng)建一個(gè)?RateLimiter,每秒允許一次請(qǐng)求
9RateLimiterConfig?rateLimiterConfig?=?RateLimiterConfig.custom()
10????.timeoutDuration(Duration.ofMillis(100))
11????.limitRefreshPeriod(Duration.ofSeconds(1))
12????.limitForPeriod(1)
13????.build();
14RateLimiter?rateLimiter?=?RateLimiter.of("backendName",?rateLimiterConfig);
15
16//?使用?Bulkhead?和?RateLimiter?裝飾業(yè)務(wù)邏輯
17Supplier?supplier?=?()?->?backendService.doSomething();
18Supplier?decoratedSupplier?=?Decorators.ofSupplier(supplier)
19??.withBulkhead(bulkhead)
20??.withRateLimiter(rateLimiter)
21??.decorate();
22
23//?調(diào)用業(yè)務(wù)邏輯
24Try?try?=?Try.ofSupplier(decoratedSupplier);
25assertThat(try.isSuccess()).isTrue();
Resilience4j 在功能特性上比 Bucket4j 強(qiáng)大不少,而且還支持并發(fā)量限流。不過最大的遺憾是,Resilience4j 不支持分布式限流。

3.4 其他

網(wǎng)上還有很多限流相關(guān)的開源項(xiàng)目,不可能一一介紹,這里列出來的只是冰山之一角:
  • https://github.com/mokies/ratelimitj
  • https://github.com/wangzheng0822/ratelimiter4j
  • https://github.com/wukq/rate-limiter
  • https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit
  • https://github.com/onblog/SnowJena
  • https://gitee.com/zhanghaiyang/spring-boot-starter-current-limiting
  • https://github.com/Netflix/concurrency-limits
可以看出,限流技術(shù)在實(shí)際項(xiàng)目中應(yīng)用非常廣泛,大家對(duì)實(shí)現(xiàn)自己的限流算法樂此不疲,新算法和新實(shí)現(xiàn)層出不窮。但是找來找去,目前還沒有找到一款開源項(xiàng)目完全滿足我的需求。我的需求其實(shí)很簡(jiǎn)單,需要同時(shí)滿足兩種不同的限流場(chǎng)景:請(qǐng)求頻率限流和并發(fā)量限流,并且能同時(shí)滿足兩種不同的限流架構(gòu):?jiǎn)螜C(jī)限流和分布式限流。下面我們就開始在 Spring Cloud Gateway 中實(shí)現(xiàn)這幾種限流,通過前面介紹的那些項(xiàng)目,我們?nèi)¢L(zhǎng)補(bǔ)短,基本上都能用比較成熟的技術(shù)實(shí)現(xiàn),只不過對(duì)于最后一種情況,分布式并發(fā)量限流,網(wǎng)上沒有搜到現(xiàn)成的解決方案,在和同事討論了幾個(gè)晚上之后,想出一種新型的基于雙窗口滑動(dòng)的限流算法,我在這里拋磚引玉,歡迎大家批評(píng)指正,如果大家有更好的方法,也歡迎討論。

四、在網(wǎng)關(guān)中實(shí)現(xiàn)限流

在文章一開始介紹 Spring Cloud Gateway 的特性時(shí),我們注意到其中有一條 Request Rate Limiting,說明網(wǎng)關(guān)自帶了限流的功能,但是 Spring Cloud Gateway 自帶的限流有很多限制,譬如不支持單機(jī)限流,不支持并發(fā)量限流,而且它的請(qǐng)求頻率限流也是不盡人意,這些都需要我們自己動(dòng)手來解決。

4.1 實(shí)現(xiàn)單機(jī)請(qǐng)求頻率限流

Spring Cloud Gateway 中定義了關(guān)于限流的一個(gè)接口 RateLimiter,如下:

1public?interface?RateLimiter<C>?extends?StatefulConfigurable<C>?{
2????Mono?isAllowed(String?routeId,?String?id);
3}
這個(gè)接口就一個(gè)方法 isAllowed,第一個(gè)參數(shù) routeId 表示請(qǐng)求路由的 ID,根據(jù) routeId 可以獲取限流相關(guān)的配置,第二個(gè)參數(shù) id 表示要限流的對(duì)象的唯一標(biāo)識(shí),可以是用戶名,也可以是 IP,或者其他的可以從 ServerWebExchange 中得到的信息。我們看下 RequestRateLimiterGatewayFilterFactory 中對(duì) isAllowed 的調(diào)用邏輯: 1@Override
2public?GatewayFilter?apply(Config?config)?{
3????//?從配置中得到?KeyResolver
4????KeyResolver?resolver?=?getOrDefault(config.keyResolver,?defaultKeyResolver);
5????//?從配置中得到?RateLimiter
6????RateLimiter?limiter?=?getOrDefault(config.rateLimiter,
7????????????defaultRateLimiter);
8????boolean?denyEmpty?=?getOrDefault(config.denyEmptyKey,?this.denyEmptyKey);
9????HttpStatusHolder?emptyKeyStatus?=?HttpStatusHolder
10????????????.parse(getOrDefault(config.emptyKeyStatus,?this.emptyKeyStatusCode));
11
12????return?(exchange,?chain)?->?resolver.resolve(exchange).defaultIfEmpty(EMPTY_KEY)
13????????????.flatMap(key?->?{
14????????????????//?通過?KeyResolver?得到?key,作為唯一標(biāo)識(shí)?id?傳入?isAllowed()?方法
15????????????????if?(EMPTY_KEY.equals(key))?{
16????????????????????if?(denyEmpty)?{
17????????????????????????setResponseStatus(exchange,?emptyKeyStatus);
18????????????????????????return?exchange.getResponse().setComplete();
19????????????????????}
20????????????????????return?chain.filter(exchange);
21????????????????}
22????????????????//?獲取當(dāng)前路由?ID,作為?routeId?參數(shù)傳入?isAllowed()?方法
23????????????????String?routeId?=?config.getRouteId();
24????????????????if?(routeId?==?null)?{
25????????????????????Route?route?=?exchange
26????????????????????????????.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
27????????????????????routeId?=?route.getId();
28????????????????}
29????????????????return?limiter.isAllowed(routeId,?key).flatMap(response?->?{
30
31????????????????????for?(Map.Entry?header?:?response.getHeaders()
32????????????????????????????.entrySet())?{
33????????????????????????exchange.getResponse().getHeaders().add(header.getKey(),
34????????????????????????????????header.getValue());
35????????????????????}
36????????????????????//?請(qǐng)求允許,直接走到下一個(gè)?filter
37????????????????????if?(response.isAllowed())?{
38????????????????????????return?chain.filter(exchange);
39????????????????????}
40????????????????????//?請(qǐng)求被限流,返回設(shè)置的?HTTP?狀態(tài)碼(默認(rèn)是?429)
41????????????????????setResponseStatus(exchange,?config.getStatusCode());
42????????????????????return?exchange.getResponse().setComplete();
43????????????????});
44????????????});
45}
從上面的的邏輯可以看出,通過實(shí)現(xiàn) KeyResolver 接口的 resolve 方法就可以自定義要限流的對(duì)象了。1public?interface?KeyResolver?{
2????Mono?resolve(ServerWebExchange?exchange);
3}
比如下面的?HostAddrKeyResolver?可以根據(jù) IP 來限流:
1public?interface?KeyResolver?{
2????Mono?resolve(ServerWebExchange?exchange);
3}
4比如下面的 HostAddrKeyResolver 可以根據(jù) IP 來限流:
5public?class?HostAddrKeyResolver?implements?KeyResolver?{
6????@Override
7????public?Mono?resolve(ServerWebExchange?exchange)?{
8????????return?Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
9????}
10}
我們繼續(xù)看 Spring Cloud Gateway 的代碼發(fā)現(xiàn),RateLimiter 接口只提供了一個(gè)實(shí)現(xiàn)類 RedisRateLimiter

實(shí)戰(zhàn)?Spring?Cloud?Gateway?之限流篇

很顯然是基于 Redis 實(shí)現(xiàn)的限流,雖說通過 Redis 也可以實(shí)現(xiàn)單機(jī)限流,但是總感覺有些大材小用,而且對(duì)于那些沒有 Redis 的環(huán)境很不友好。所以,我們要實(shí)現(xiàn)真正的本地限流。

我們從 Spring Cloud Gateway 的 pull request 中發(fā)現(xiàn)了一個(gè)新特性 Feature/local-rate-limiter,而且看提交記錄,這個(gè)新特性很有可能會(huì)合并到 3.0.0 版本中。我們不妨來看下這個(gè) local-rate-limiter 的實(shí)現(xiàn):LocalRateLimiter.java,可以看出它是基于 Resilience4有意思的是,這個(gè)類?還有一個(gè)早期版本,是基于 Bucket4j 實(shí)現(xiàn)的: 1public?Mono?isAllowed(String?routeId,?String?id)?{
2????Config?routeConfig?=?loadConfiguration(routeId);
3
4????//?How?many?requests?per?second?do?you?want?a?user?to?be?allowed?to?do?
5????int?replenishRate?=?routeConfig.getReplenishRate();
6
7????//?How?many?seconds?for?a?token?refresh?
8????int?refreshPeriod?=?routeConfig.getRefreshPeriod();
9
10????//?How?many?tokens?are?requested?per?request?
11????int?requestedTokens?=?routeConfig.getRequestedTokens();
12
13????final?io.github.resilience4j.ratelimiter.RateLimiter?rateLimiter?=?RateLimiterRegistry
14????????????.ofDefaults()
15????????????.rateLimiter(id,?createRateLimiterConfig(refreshPeriod,?replenishRate));
16
17????final?boolean?allowed?=?rateLimiter.acquirePermission(requestedTokens);
18????final?Long?tokensLeft?=?(long)?rateLimiter.getMetrics().getAvailablePermissions();
19
20????Response?response?=?new?Response(allowed,?getHeaders(routeConfig,?tokensLeft));
21????return?Mono.just(response);
22}
有意思的是,這個(gè)類?還有一個(gè)早期版本,是基于 Bucket4j 實(shí)現(xiàn)的:

1public?Mono?isAllowed(String?routeId,?String?id)?{
2
3????Config?routeConfig?=?loadConfiguration(routeId);
4
5????//?How?many?requests?per?second?do?you?want?a?user?to?be?allowed?to?do?
6????int?replenishRate?=?routeConfig.getReplenishRate();
7
8????//?How?much?bursting?do?you?want?to?allow?
9????int?burstCapacity?=?routeConfig.getBurstCapacity();
10
11????//?How?many?tokens?are?requested?per?request?
12????int?requestedTokens?=?routeConfig.getRequestedTokens();
13
14????final?Bucket?bucket?=?bucketMap.computeIfAbsent(id,
15????????????(key)?->?createBucket(replenishRate,?burstCapacity));
16
17????final?boolean?allowed?=?bucket.tryConsume(requestedTokens);
18
19????Response?response?=?new?Response(allowed,
20????????????getHeaders(routeConfig,?bucket.getAvailableTokens()));
21????return?Mono.just(response);
22}
實(shí)現(xiàn)方式都是類似的,在上面對(duì) Bucket4j 和 Resilience4j 已經(jīng)作了比較詳細(xì)的介紹,這里不再贅述。不過從這里也可以看出 Spring 生態(tài)圈對(duì) Resilience4j 是比較看好的,我們也可以將其引入到我們的項(xiàng)目中。

4.2 實(shí)現(xiàn)分布式請(qǐng)求頻率限流

上面介紹了如何實(shí)現(xiàn)單機(jī)請(qǐng)求頻率限流,接下來再看下分布式請(qǐng)求頻率限流。這個(gè)就比較簡(jiǎn)單了,因?yàn)樯厦嬲f了,Spring Cloud Gateway 自帶了一個(gè)限流實(shí)現(xiàn),就是 RedisRateLimiter,可以用于分布式限流。它的實(shí)現(xiàn)原理依然是基于令牌桶算法的,不過實(shí)現(xiàn)邏輯是放在一段 lua 腳本中的,我們可以在 src/main/resources/META-INF/scripts 目錄下找到該腳本文件 request_rate_limiter.lua 1local?tokens_key?=?KEYS[1]
2local?timestamp_key?=?KEYS[2]
3
4local?rate?=?tonumber(ARGV[1])
5local?capacity?=?tonumber(ARGV[2])
6local?now?=?tonumber(ARGV[3])
7local?requested?=?tonumber(ARGV[4])
8
9local?fill_time?=?capacity/rate
10local?ttl?=?math.floor(fill_time*2)
11
12local?last_tokens?=?tonumber(redis.call("get",?tokens_key))
13if?last_tokens?==?nil?then
14??last_tokens?=?capacity
15end
16
17local?last_refreshed?=?tonumber(redis.call("get",?timestamp_key))
18if?last_refreshed?==?nil?then
19??last_refreshed?=?0
20end
21
22local?delta?=?math.max(0,?now-last_refreshed)
23local?filled_tokens?=?math.min(capacity,?last_tokens (delta*rate))
24local?allowed?=?filled_tokens?>=?requested
25local?new_tokens?=?filled_tokens
26local?allowed_num?=?0
27if?allowed?then
28??new_tokens?=?filled_tokens?-?requested
29??allowed_num?=?1
30end
31
32if?ttl?>?0?then
33??redis.call("setex",?tokens_key,?ttl,?new_tokens)
34??redis.call("setex",?timestamp_key,?ttl,?now)
35end
36
37return?{?allowed_num,?new_tokens?}
這段代碼和上面介紹令牌桶算法時(shí)用 Java 實(shí)現(xiàn)的那段經(jīng)典代碼幾乎是一樣的。這里使用 lua 腳本,主要是利用了 Redis 的單線程特性,以及執(zhí)行 lua 腳本的原子性,避免了并發(fā)訪問時(shí)可能出現(xiàn)請(qǐng)求量超出上限的現(xiàn)象。想象目前令牌桶中還剩 1 個(gè)令牌,此時(shí)有兩個(gè)請(qǐng)求同時(shí)到來,判斷令牌是否足夠也是同時(shí)的,兩個(gè)請(qǐng)求都認(rèn)為還剩 1 個(gè)令牌,于是兩個(gè)請(qǐng)求都被允許了。有兩種方式來配置 Spring Cloud Gateway 自帶的限流。第一種方式是通過配置文件,比如下面所示的代碼,可以對(duì)某個(gè) route 進(jìn)行限流: 1spring:
2??cloud:
3????gateway:
4??????routes:
5??????-?id:?test
6????????uri:?http://httpbin.org:80/get
7????????filters:
8????????-?name:?RequestRateLimiter
9??????????args:
10????????????key-resolver:?'#{@hostAddrKeyResolver}'
11????????????redis-rate-limiter.replenishRate:?1
12????????????redis-rate-limiter.burstCapacity:?3
其中,key-resolver 使用 SpEL 表達(dá)式?#{@beanName}?從 Spring 容器中獲取 hostAddrKeyResolver 對(duì)象,burstCapacity 表示令牌桶的大小,replenishRate 表示每秒往桶中填充多少個(gè)令牌,也就是填充速度。第二種方式是通過下面的代碼來配置: 1@Bean
2public?RouteLocator?myRoutes(RouteLocatorBuilder?builder)?{
3??return?builder.routes()
4????.route(p?->?p
5??????.path("/get")
6??????.filters(filter?->?filter.requestRateLimiter()
7????????.rateLimiter(RedisRateLimiter.class,?rl?->?rl.setBurstCapacity(3).setReplenishRate(1)).and())
8??????.uri("http://httpbin.org:80"))
9????.build();
10}
這樣就可以對(duì)某個(gè) route 進(jìn)行限流了。但是這里有一點(diǎn)要注意,Spring Cloud Gateway 自帶的限流器有一個(gè)很大的坑,replenishRate 不支持設(shè)置小數(shù),也就是說往桶中填充的 token 的速度最少為每秒 1 個(gè),所以,如果我的限流規(guī)則是每分鐘 10 個(gè)請(qǐng)求(按理說應(yīng)該每 6 秒填充一次,或每秒填充 1/6 個(gè) token),這種情況 Spring Cloud Gateway 就沒法正確的限流。網(wǎng)上也有人提了 issue,support greater than a second resolution for the rate limiter,但還沒有得到解決。

4.3 實(shí)現(xiàn)單機(jī)并發(fā)量限流

上面學(xué)習(xí) Resilience4j 的時(shí)候,我們提到了 Resilience4j 的一個(gè)功能特性,叫?隔離(Bulkhead)。Bulkhead 這個(gè)單詞的意思是船的艙壁,利用艙壁可以將不同的船艙隔離起來,這樣如果一個(gè)船艙破損進(jìn)水,那么只損失這一個(gè)船艙,其它船艙可以不受影響。借鑒造船行業(yè)的經(jīng)驗(yàn),這種模式也被引入到軟件行業(yè),我們把它叫做?艙壁模式(Bulkhead pattern)。艙壁模式一般用于服務(wù)隔離,對(duì)于一些比較重要的系統(tǒng)資源,如 CPU、內(nèi)存、連接數(shù)等,可以為每個(gè)服務(wù)設(shè)置各自的資源限制,防止某個(gè)異常的服務(wù)把系統(tǒng)的所有資源都消耗掉。這種服務(wù)隔離的思想同樣可以用來做并發(fā)量限流。正如前文所述,Resilience4j 提供了兩種 Bulkhead 的實(shí)現(xiàn):SemaphoreBulkhead ThreadPoolBulkhead,這也正是艙壁模式常見的兩種實(shí)現(xiàn)方案:一種是帶計(jì)數(shù)的信號(hào)量,一種是固定大小的線程池??紤]到多線程場(chǎng)景下的線程切換成本,默認(rèn)推薦使用信號(hào)量。在操作系統(tǒng)基礎(chǔ)課程中,我們學(xué)習(xí)過兩個(gè)名詞:互斥量(Mutex)?和?信號(hào)量(Semaphores)?;コ饬坑糜诰€程的互斥,它和臨界區(qū)有點(diǎn)相似,只有擁有互斥對(duì)象的線程才有訪問資源的權(quán)限,由于互斥對(duì)象只有一個(gè),因此任何情況下只會(huì)有一個(gè)線程在訪問此共享資源,從而保證了多線程可以安全的訪問和操作共享資源。而信號(hào)量是用于線程的同步,這是由荷蘭科學(xué)家 E.W.Dijkstra 提出的概念,它和互斥量不同,信號(hào)允許多個(gè)線程同時(shí)使用共享資源,但是它同時(shí)設(shè)定了訪問共享資源的線程最大數(shù)目,從而可以進(jìn)行并發(fā)量控制。下面是使用信號(hào)量限制并發(fā)訪問的一個(gè)簡(jiǎn)單例子: 1public?class?SemaphoreTest?{
2
3????private?static?ExecutorService?threadPool?=?Executors.newFixedThreadPool(100);
4????private?static?Semaphore?semaphore?=?new?Semaphore(10);
5
6????public?static?void?main(String[]?args)?{
7????????for?(int?i?=?0;?i?100;?i )?{
8????????????threadPool.execute(new?Runnable()?{
9????????????????@Override
10????????????????public?void?run()?{
11????????????????????try?{
12????????????????????????semaphore.acquire();
13????????????????????????System.out.println("Request?processing?...");
14????????????????????????semaphore.release();
15????????????????????}?catch?(InterruptedException?e)?{
16????????????????????????e.printStack();
17????????????????????}
18????????????????}
19????????????});
20????????}
21????????threadPool.shutdown();
22????}
23}
這里我們創(chuàng)建了 100 個(gè)線程同時(shí)執(zhí)行,但是由于信號(hào)量計(jì)數(shù)為 10,所以同時(shí)只能有 10 個(gè)線程在處理請(qǐng)求。說到計(jì)數(shù),實(shí)際上,在 Java 里除了 Semaphore 還有很多類也可以用作計(jì)數(shù),比如 AtomicLongLongAdder,這在并發(fā)量限流中非常常見,只是無法提供像信號(hào)量那樣的阻塞能力:
1public?class?AtomicLongTest?{
2
3????private?static?ExecutorService?threadPool?=?Executors.newFixedThreadPool(100);
4????private?static?AtomicLong?atomic?=?new?AtomicLong();
5
6????public?static?void?main(String[]?args)?{
7????????for?(int?i?=?0;?i?100;?i )?{
8????????????threadPool.execute(new?Runnable()?{
9????????????????@Override
10????????????????public?void?run()?{
11????????????????????try?{
12????????????????????????if(atomic.incrementAndGet()?>?10)?{
13????????????????????????????System.out.println("Request?rejected?...");
14????????????????????????????return;
15????????????????????????}
16????????????????????????System.out.println("Request?processing?...");
17????????????????????????atomic.decrementAndGet();
18????????????????????}?catch?(InterruptedException?e)?{
19????????????????????????e.printStack();
20????????????????????}
21????????????????}
22????????????});
23????????}
24????????threadPool.shutdown();
25????}
26}
4.4 實(shí)現(xiàn)分布式并發(fā)量限流通過在單機(jī)實(shí)現(xiàn)并發(fā)量限流,我們掌握了幾種常用的手段:信號(hào)量、線程池、計(jì)數(shù)器,這些都是單機(jī)上的概念。那么稍微拓展下,如果能實(shí)現(xiàn)分布式信號(hào)量、分布式線程池、分布式計(jì)數(shù)器,那么實(shí)現(xiàn)分布式并發(fā)量限流不就易如反掌了嗎?關(guān)于分布式線程池,是我自己杜撰的詞,在網(wǎng)上并沒有找到類似的概念,比較接近的概念是資源調(diào)度和分發(fā),但是又感覺不像,這里直接忽略吧。關(guān)于分布式信號(hào)量,還真有這樣的東西,比如 Apache Ignite 就提供了 IgniteSemaphore 用于創(chuàng)建分布式信號(hào)量,它的使用方式和 Semaphore 非常類似。使用 Redis 的 ZSet 也可以實(shí)現(xiàn)分布式信號(hào)量,比如?這篇博客介紹的方法,還有《Redis in Action》這本電子書中也提到了這樣的例子,教你如何實(shí)現(xiàn) Counting semaphores。另外,Redisson 也實(shí)現(xiàn)了基于 Redis 的分布式信號(hào)量 RSemaphore,用法也和 Semaphore 類似。使用分布式信號(hào)量可以很容易實(shí)現(xiàn)分布式并發(fā)量限流,實(shí)現(xiàn)方式和上面的單機(jī)并發(fā)量限流幾乎是一樣的。最后,關(guān)于分布式計(jì)數(shù)器,實(shí)現(xiàn)方案也是多種多樣。比如使用 Redis 的 INCR 就很容易實(shí)現(xiàn),更有甚者,使用 MySQL 數(shù)據(jù)庫也可以實(shí)現(xiàn)。只不過使用計(jì)數(shù)器要注意操作的原子性,每次請(qǐng)求時(shí)都要經(jīng)過這三步操作:取計(jì)數(shù)器當(dāng)前的值、判斷是否超過閾值,超過則拒絕、將計(jì)數(shù)器的值自增。這其實(shí)和信號(hào)量的 P 操作是一樣的,而釋放就對(duì)應(yīng) V 操作。所以,利用分布式信號(hào)量和計(jì)數(shù)器就可以實(shí)現(xiàn)并發(fā)量限流了嗎?問題當(dāng)然沒有這么簡(jiǎn)單。實(shí)際上,上面通過信號(hào)量和計(jì)數(shù)器實(shí)現(xiàn)單機(jī)并發(fā)量限流的代碼片段有一個(gè)嚴(yán)重 BUG:1semaphore.acquire();
2System.out.println("Request?processing?...");
3semaphore.release();
想象一下如果在處理請(qǐng)求時(shí)出現(xiàn)異常了會(huì)怎么樣?很顯然,信號(hào)量被該線程獲取了,但是卻永遠(yuǎn)不會(huì)釋放,如果請(qǐng)求異常多了,這將導(dǎo)致信號(hào)量被占滿,最后一個(gè)請(qǐng)求也進(jìn)不來。在單機(jī)場(chǎng)景下,這個(gè)問題可以很容易解決,加一個(gè) finally 就行了:1try?{
2????semaphore.acquire();
3????System.out.println("Request?processing?...");
4}?catch?(InterruptedException?e)?{
5????e.printStack();
6}?finally?{
7????semaphore.release();
8}
由于無論出現(xiàn)何種異常,finally 中的代碼一定會(huì)執(zhí)行,這樣就保證了信號(hào)量一定會(huì)被釋放。但是在分布式系統(tǒng)中,就不是加一個(gè) finally 這么簡(jiǎn)單了。這是因?yàn)樵诜植际较到y(tǒng)中可能存在的異常不一定是可被捕獲的代碼異常,還有可能是服務(wù)崩潰或者不可預(yù)知的系統(tǒng)宕機(jī),就算是正常的服務(wù)重啟也可能導(dǎo)致分布式信號(hào)量無法釋放。對(duì)于這個(gè)問題,我和幾個(gè)同事連續(xù)討論了幾個(gè)晚上,想出了兩種解決方法:第一種方法是使用帶 TTL 的計(jì)數(shù)器,第二種方法是基于雙窗口滑動(dòng)的一種比較 tricky 的算法。第一種方法比較容易理解,我們?yōu)槊總€(gè)請(qǐng)求賦予一個(gè)唯一 ID,并在 Redis 里寫入一個(gè)鍵值對(duì),key 為 requests_xxx(xxx 為請(qǐng)求 ID),value 為 1,并給這個(gè) key 設(shè)置一個(gè) TTL(如果你的應(yīng)用中存在耗時(shí)非常長(zhǎng)的請(qǐng)求,譬如對(duì)于一些 WebSockket 請(qǐng)求可能會(huì)持續(xù)幾個(gè)小時(shí),還需要開一個(gè)線程定期去刷新這個(gè) key 的 TTL)。然后在判斷并發(fā)量時(shí),使用 KEYS 命令查詢 requests_*?開頭的 key 的個(gè)數(shù),就可以知道當(dāng)前一共有多少個(gè)請(qǐng)求,如果超過并發(fā)量上限則拒絕請(qǐng)求。這種方法可以很好的應(yīng)對(duì)服務(wù)崩潰或重啟的問題,由于每個(gè) key 都設(shè)置了 TTL,所以經(jīng)過一段時(shí)間后,這些 key 就會(huì)自動(dòng)消失,就不會(huì)出現(xiàn)信號(hào)量占滿不釋放的情況了。但是這里使用 KEYS 命令查詢請(qǐng)求個(gè)數(shù)是一個(gè)非常低效的做法,在請(qǐng)求量比較多的情況下,網(wǎng)關(guān)的性能會(huì)受到嚴(yán)重影響。我們可以把 KEYS 命令換成 SCAN,性能會(huì)得到些許提升,但總體來說效果還是很不理想的。針對(duì)第一種方法,我們可以進(jìn)一步優(yōu)化,不用為每個(gè)請(qǐng)求寫一個(gè)鍵值對(duì),而是為每個(gè)分布式系統(tǒng)中的每個(gè)實(shí)例賦予一個(gè)唯一 ID,并在 Redis 里寫一個(gè)鍵值對(duì),key 為 instances_xxx(xxx 為實(shí)例 ID),value 為這個(gè)實(shí)例當(dāng)前的并發(fā)量。同樣的,我們?yōu)檫@個(gè) key 設(shè)置一個(gè) TTL,并且開啟一個(gè)線程定期去刷新這個(gè) TTL。每接受一個(gè)請(qǐng)求后,計(jì)數(shù)器加一,請(qǐng)求結(jié)束,計(jì)數(shù)器減一,這和單機(jī)場(chǎng)景下的處理方式一樣,只不過在判斷并發(fā)量時(shí),還是需要使用 KEYS SCAN 獲取所有的實(shí)例,并計(jì)算出并發(fā)量的總和。不過由于實(shí)例個(gè)數(shù)是有限的,性能比之前的做法有了明顯的提升。第二種方法我稱之為?雙窗口滑動(dòng)算法,結(jié)合了 TTL 計(jì)數(shù)器和滑動(dòng)窗口算法。我們按分鐘來設(shè)置一個(gè)時(shí)間窗口,在 Redis 里對(duì)應(yīng) 202009051130?這樣的一個(gè) key,value 為計(jì)數(shù)器,表示請(qǐng)求的數(shù)量。當(dāng)接受一個(gè)請(qǐng)求后,在當(dāng)前的時(shí)間窗口中加一,當(dāng)請(qǐng)求結(jié)束,在當(dāng)前的時(shí)間窗口中減一,注意,接受請(qǐng)求和請(qǐng)求結(jié)束的時(shí)間窗口可能不是同一個(gè)。另外,我們還需要一個(gè)本地列表來記錄當(dāng)前實(shí)例正在處理的所有請(qǐng)求和請(qǐng)求對(duì)應(yīng)的時(shí)間窗口,并通過一個(gè)小于時(shí)間窗口的定時(shí)線程(如 30 秒)來遷移過期的請(qǐng)求,所謂過期,指的是請(qǐng)求的時(shí)間窗口和當(dāng)前時(shí)間窗口不一致。那么具體如何遷移呢?我們首先需要統(tǒng)計(jì)列表中一共有多少請(qǐng)求過期了,然后將列表中的過期請(qǐng)求時(shí)間更新為當(dāng)前時(shí)間窗口,并從 Redis 中上一個(gè)時(shí)間窗口移動(dòng)相應(yīng)數(shù)量到當(dāng)前時(shí)間窗口,也就是上一個(gè)時(shí)間窗口減 X,當(dāng)前時(shí)間窗口加 X。由于遷移線程定期執(zhí)行,所以過期的請(qǐng)求總是會(huì)被移動(dòng)到當(dāng)前窗口,最終 Redis 中只有當(dāng)前時(shí)間窗口和上個(gè)時(shí)間窗口這兩個(gè)時(shí)間窗口中有數(shù)據(jù),再早一點(diǎn)的窗口時(shí)間中的數(shù)據(jù)會(huì)被往后遷移,所以可以給這個(gè) key 設(shè)置一個(gè) 3 分鐘或 5 分鐘的 TTL。判斷并發(fā)量時(shí),由于只有兩個(gè) key,只需要使用 MGET 獲取兩個(gè)值相加即可。下面的流程圖詳細(xì)描述了算法的運(yùn)行過程:實(shí)戰(zhàn)?Spring?Cloud?Gateway?之限流篇其中有幾個(gè)需要注意的細(xì)節(jié):
  1. 請(qǐng)求結(jié)束時(shí),直接在 Redis 中當(dāng)前時(shí)間窗口減一即可,就算是負(fù)數(shù)也沒關(guān)系。請(qǐng)求列表中的該請(qǐng)求不用急著刪除,可以打上結(jié)束標(biāo)記,在遷移線程中統(tǒng)一刪除(當(dāng)然,如果請(qǐng)求的開始時(shí)間和結(jié)束時(shí)間在同一個(gè)窗口,可以直接刪除);
  2. 遷移的時(shí)間間隔要小于時(shí)間窗口,一般設(shè)置為 30s;
  3. Redis 中的 key 一定要設(shè)置 TTL,時(shí)間至少為 2 個(gè)時(shí)間窗口,一般設(shè)置為 3 分鐘;
  4. 遷移過程涉及到“從上一個(gè)時(shí)間窗口減”和“在當(dāng)前時(shí)間窗口加”兩個(gè)操作,要注意操作的原子性;
  5. 獲取當(dāng)前并發(fā)量可以通過 MGET 一次性讀取兩個(gè)時(shí)間窗口的值,不用 GET 兩次;
  6. 獲取并發(fā)量和判斷并發(fā)量是否超限,這個(gè)過程也要注意操作的原子性。

總結(jié)

網(wǎng)關(guān)作為微服務(wù)架構(gòu)中的重要一環(huán),充當(dāng)著一夫當(dāng)關(guān)萬夫莫開的角色,所以對(duì)網(wǎng)關(guān)服務(wù)的穩(wěn)定性要求和性能要求都非常高。為保證網(wǎng)關(guān)服務(wù)的穩(wěn)定性,一代又一代的程序員們前仆后繼,想出了十八般武藝:限流、熔斷、隔離、緩存、降級(jí)、等等等等。這篇文章從限流入手,詳細(xì)介紹了限流的場(chǎng)景和算法,以及源碼實(shí)現(xiàn)和可能踩到的坑。盡管限流只是網(wǎng)關(guān)的一個(gè)非常小的功能,但卻影響到網(wǎng)關(guān)的方方面面,在系統(tǒng)架構(gòu)的設(shè)計(jì)中至關(guān)重要。雖然我試著從不同的角度希望把限流介紹的更完全,但終究是管中窺豹,只見一斑,還有很多的內(nèi)容沒有介紹到,比如阿里開源的 Sentinel 組件也可以用于限流,因?yàn)槠邢尬茨苷归_。另外前文提到的 Netflix 不再維護(hù) Hystrix 項(xiàng)目,這是因?yàn)樗麄儼丫Ψ诺搅硪粋€(gè)限流項(xiàng)目 concurrency-limits 上了,這個(gè)項(xiàng)目的目標(biāo)是打造一款自適應(yīng)的,極具彈性的限流組件,它借鑒了 TCP 擁塞控制的算法(TCP congestion control algorithm),實(shí)現(xiàn)系統(tǒng)的自動(dòng)限流,感興趣的同學(xué)可以去它的項(xiàng)目主頁了解更多內(nèi)容。本文篇幅較長(zhǎng),難免疏漏,如有問題,還望不吝賜教。

參考

  1. 微服務(wù)網(wǎng)關(guān)實(shí)戰(zhàn)——Spring Cloud Gateway
  2. 《億級(jí)流量網(wǎng)站架構(gòu)核心技術(shù)》張開濤
  3. 聊聊高并發(fā)系統(tǒng)之限流特技
  4. 架構(gòu)師成長(zhǎng)之路之限流
  5. 微服務(wù)接口限流的設(shè)計(jì)與思考
  6. 常用4種限流算法介紹及比較
  7. 來談?wù)勏蘖?從概念到實(shí)現(xiàn)
  8. 高并發(fā)下的限流分析
  9. 計(jì)數(shù)器算法
  10. 基于Redis的限流系統(tǒng)的設(shè)計(jì)
  11. API 調(diào)用次數(shù)限制實(shí)現(xiàn)
  12. Techniques to Improve QoS
  13. An alternative approach to rate limiting
  14. Scaling your API with rate limiters
  15. Brief overview of token-bucket algorithm
  16. Rate limiting Spring MVC endpoints with bucket4j
  17. Rate Limiter Internals in Resilience4j
  18. 高可用框架Resilience4j使用指南
  19. 阿里巴巴開源限流系統(tǒng) Sentinel 全解析
  20. spring cloud gateway 之限流篇
  21. 服務(wù)容錯(cuò)模式
  22. 你的API會(huì)自適應(yīng)「彈性」限流嗎???

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

LED驅(qū)動(dòng)電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: 驅(qū)動(dòng)電源

在工業(yè)自動(dòng)化蓬勃發(fā)展的當(dāng)下,工業(yè)電機(jī)作為核心動(dòng)力設(shè)備,其驅(qū)動(dòng)電源的性能直接關(guān)系到整個(gè)系統(tǒng)的穩(wěn)定性和可靠性。其中,反電動(dòng)勢(shì)抑制與過流保護(hù)是驅(qū)動(dòng)電源設(shè)計(jì)中至關(guān)重要的兩個(gè)環(huán)節(jié),集成化方案的設(shè)計(jì)成為提升電機(jī)驅(qū)動(dòng)性能的關(guān)鍵。

關(guān)鍵字: 工業(yè)電機(jī) 驅(qū)動(dòng)電源

LED 驅(qū)動(dòng)電源作為 LED 照明系統(tǒng)的 “心臟”,其穩(wěn)定性直接決定了整個(gè)照明設(shè)備的使用壽命。然而,在實(shí)際應(yīng)用中,LED 驅(qū)動(dòng)電源易損壞的問題卻十分常見,不僅增加了維護(hù)成本,還影響了用戶體驗(yàn)。要解決這一問題,需從設(shè)計(jì)、生...

關(guān)鍵字: 驅(qū)動(dòng)電源 照明系統(tǒng) 散熱

根據(jù)LED驅(qū)動(dòng)電源的公式,電感內(nèi)電流波動(dòng)大小和電感值成反比,輸出紋波和輸出電容值成反比。所以加大電感值和輸出電容值可以減小紋波。

關(guān)鍵字: LED 設(shè)計(jì) 驅(qū)動(dòng)電源

電動(dòng)汽車(EV)作為新能源汽車的重要代表,正逐漸成為全球汽車產(chǎn)業(yè)的重要發(fā)展方向。電動(dòng)汽車的核心技術(shù)之一是電機(jī)驅(qū)動(dòng)控制系統(tǒng),而絕緣柵雙極型晶體管(IGBT)作為電機(jī)驅(qū)動(dòng)系統(tǒng)中的關(guān)鍵元件,其性能直接影響到電動(dòng)汽車的動(dòng)力性能和...

關(guān)鍵字: 電動(dòng)汽車 新能源 驅(qū)動(dòng)電源

在現(xiàn)代城市建設(shè)中,街道及停車場(chǎng)照明作為基礎(chǔ)設(shè)施的重要組成部分,其質(zhì)量和效率直接關(guān)系到城市的公共安全、居民生活質(zhì)量和能源利用效率。隨著科技的進(jìn)步,高亮度白光發(fā)光二極管(LED)因其獨(dú)特的優(yōu)勢(shì)逐漸取代傳統(tǒng)光源,成為大功率區(qū)域...

關(guān)鍵字: 發(fā)光二極管 驅(qū)動(dòng)電源 LED

LED通用照明設(shè)計(jì)工程師會(huì)遇到許多挑戰(zhàn),如功率密度、功率因數(shù)校正(PFC)、空間受限和可靠性等。

關(guān)鍵字: LED 驅(qū)動(dòng)電源 功率因數(shù)校正

在LED照明技術(shù)日益普及的今天,LED驅(qū)動(dòng)電源的電磁干擾(EMI)問題成為了一個(gè)不可忽視的挑戰(zhàn)。電磁干擾不僅會(huì)影響LED燈具的正常工作,還可能對(duì)周圍電子設(shè)備造成不利影響,甚至引發(fā)系統(tǒng)故障。因此,采取有效的硬件措施來解決L...

關(guān)鍵字: LED照明技術(shù) 電磁干擾 驅(qū)動(dòng)電源

開關(guān)電源具有效率高的特性,而且開關(guān)電源的變壓器體積比串聯(lián)穩(wěn)壓型電源的要小得多,電源電路比較整潔,整機(jī)重量也有所下降,所以,現(xiàn)在的LED驅(qū)動(dòng)電源

關(guān)鍵字: LED 驅(qū)動(dòng)電源 開關(guān)電源

LED驅(qū)動(dòng)電源是把電源供應(yīng)轉(zhuǎn)換為特定的電壓電流以驅(qū)動(dòng)LED發(fā)光的電壓轉(zhuǎn)換器,通常情況下:LED驅(qū)動(dòng)電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: LED 隧道燈 驅(qū)動(dòng)電源
關(guān)閉