設計一個限速器

限速器 (Rate Limiter) 相信大家都不會陌生,在網絡系統中,限速器可以控制客戶端發送流量的速度,比如 TCP, QUIC 等協議。而在 HTTP 的世界中, 限速器可以限制客戶端在一段時間內發送請求的次數,如果超過設定的閾值,多余的請求就會被丟棄。

生活中也有很多這樣的例子,比如

? 用戶一分鐘最多能發 5 條微博? 用戶一天最多能投 3 次票? 用戶一小時登錄超過5次后,需要等待一段時間才能重試。

5e893d3a5f0074a23489b130b2060b34.png

限速器(Rate Limiter)有很多好處,可以防止一定程度的 Dos 攻擊,也可以屏蔽掉一些網絡爬蟲的請求,避免系統資源被耗盡,導致服務不可用。

? ?設計要求???

讓我們從一個面試開始吧!

f3a3af838af3aa6a5da7cace1168cd03.png

面試官:你好,我想考察一下你的設計能力,如果讓你設計一個限速器 (Rate Limiter),你會怎么做?

面試者:我們需要什么樣的限速器?它是在客戶端限速還是在服務端限速?

面試官:這個問題很好,沒有固定要求,取決于你的設計。

面試者:我想了解一下限速的規則,我們是根據 IP、UserId,或者手機號碼進行限速嗎?

面試官:這個不固定,限速器應該是靈活的,要能很方便的支持不同的規則。

面試者:如果請求被限制了,需要提示給用戶嗎?

面試官:需要提示,要給用戶提供良好的體驗。

面試者:好吧,那系統的規模是多大的?是單機還是分布式場景?

面試官:我們是 TOC 的產品, 系統流量很大,并且是分布式的環境, 所以限速器要支持海量請求。

面試者:(小聲嘀咕)你這是造火箭呢?

我們總結一下限速器的設計要求:

? 低延遲,性能要好? 需要適用于分布式場景。? 用戶的請求受到限制時,需要提示具體的原因。? 高容錯,如果限速器故障,不應該影響整個系統。


? ?限速器應該放在哪里????

從系統整體的角度上來看,我們的限速器應該放在哪里?通常有三種選擇,如下

客戶端

是的,我們可以在客戶端設置限速器。但是有個問題是,我們都知道在 Web 前端做一些限制實際上是不安全的,同樣客戶端也是一樣的, 限速客戶端可以做,但是遠遠不夠。

服務端

在服務端設置限速器是很常見且安全的,如下

84e8ab85a388872b33c367ffdb8490d9.png

中間件

還有一種做法是,我們可以提供一個單獨的限速中間件,如下

b4aae799c8167f48ed76c9b6517dc644.png

假如限速器設置了每秒最大允許2個請求,那么客戶端發出的多余的請求就會被拒絕,并返回 HTTP 狀態碼 429, 表示用戶發送了太多的請求。

94b15c69726a36fa68b4fc57a14796dd.png

實際上,很多網關都有限速的實現,包括認證、IP 白名單功能。

限速器應該放在哪里?沒有固定的答案,它取決于公司的技術棧,系統規模。


? ?限速算法???

實際上,我們可以用算法實現限速器,下面是幾種經典的限速算法,每種算法都有自己的優點和缺點,了解這些可以幫助我們選擇更適合的算法。

? 令牌桶 (Token bucket)? 漏桶(Leaking bucket)? 固定窗口計數器(Fixed window counter)? 滑動窗口日志(Sliding window log)? 滑動窗口計數器(Sliding window counter)


? ?令牌桶算法???

令牌桶算法是實現限速使用很廣泛的算法,它很簡單也很好理解。

令牌桶是固定容量的容器。

一方面,按照一定的速率,向桶中添加令牌,桶裝滿后,多余的令牌就會被丟棄。

如下圖,桶的容量為4,每次填充2個令牌。

1515780183fa9b6da3f3f1316ed12864.png

另一方面,一個請求消耗一個令牌,如果桶中沒有令牌了,則拒絕請求。直到下個時間段,繼續向桶中填充新的令牌。

78c14af4f364a9642cc0710d44287cd2.png

令牌桶算法有兩個重要的參數,分別是桶大小和填充率,另外有時候可能需要多個桶,比如多個 api 限速的規則是不一樣的。

令牌桶算法的優點是簡單易實現,并且允許短時間的流量并發。缺點是,在應對流量變化時,正確地調整桶大小和填充率,會比較有挑戰性。


? ?漏桶算法???

漏桶算法和令牌桶算法是類似的,同樣有一個固定容量的桶。

一方面,當一個請求進來時,會被填充到桶里,如果桶滿了,就拒絕這個請求。

另一方面,想象桶下面有一個漏洞,桶里的元素以固定的速率流出。

通常可以用先入先出的隊列實現,如下圖

996314d799e221fe02dcd027f92f955c.png

漏桶算法有兩個參數,分別是桶大小和流出率,優點是使用隊列易實現,缺點是,面對突發流量時,雖然有的請求已經推到隊列中了,但是由于消費的速率是固定的,存在效率問題。


? ?固定窗口計數器算法???

固定窗口計數器算法的工作原理是,把時間劃分成固定大小的時間窗口,每個窗口分配一個計數器,接收到一個請求,計數器就加一,一旦計數器達到設定的閾值,新的請求就會被丟棄,直到新的時間窗口,新的計數器。

讓我們通過下面的例子,來看看它是如何工作的。一個時間窗口是1秒,每秒最多允許3個請求,超出的請求就會被丟棄。

f30e04a36203ca11792d642eac45ab7a.png

不過這個算法有一個問題是,如果在時間窗口的邊緣出現突發流量時,可能會導致通過的請求數超過閾值,什么意思呢?我們看看下面的情況

06c8ab714f9f162b5059d42af6d52fa6.png

一個時間窗口是1分鐘,每分鐘最多允許5個請求。如果前一個時間窗口的后半段有5個請求,后一個時間窗口的前半段有5個請求,也就是 2:00:30 到 2:01:30 的一分鐘內,是可以通過10個請求的,這明顯超過了我們設置的閾值。

固定窗口計數器的優點是,簡單易于理解,缺點是,時間窗口的邊緣應對流量高峰時,可能會讓通過的請求數超過閾值。


? ?滑動窗口日志算法???

我們上面看到了,固定窗口計數器算法有一個問題,在窗口邊緣可能會突破限制,而滑動窗口日志算法可以解決這個問題。

它的工作原理是,假如設定1分鐘內最多允許2個請求,每個請求都需要記錄請求時間,比如保存在 Redis 的 sorted sets 中,保存之后還需要刪除掉過時的日志,過時日志是如何定義的?比如某次請求的時間是 1:01:36,那么往前推1分鐘,1:00:36 之前的日志都算過時的,需要從集合中刪掉。同時,判斷集合中的數量是否大于閾值,如果大于2則丟棄請求,如果小于或等于2,則處理這個請求。

讓我們看看下面的例子

8154e87323937d0a43adeb47c00ddb4b.png


1.在 1:00:01 來了一個請求,第一步,記錄請求時間到日志中,第二步,判斷是否有過時的日志, 也就是 0:59:01 之前的日志,明顯沒有,第三步判斷日志中的數量,沒有大于2,處理這次請求。

2.在 1:00:30 來了一個請求,執行上面的三個步驟,最后處理這次請求。

3.在 1:00:50 的新請求,沒有過時的日志,然后發現日志的數量為3,拒絕這次請求。

4.在 1:01:40 的新請求,清除2條過時的日志,也就是 1:00:40 之前的日志,此時,日志中的數量為2,處理這次請求。

這個算法實現的限速非常準確,但是它可能會消耗較多的內存。


? ?滑動窗口計數器算法???

滑動窗口計數器可以說是固定窗口計數器的升級版,上面提到了,固定窗口計數器存在窗口邊緣可能會有超出限制的情況,如下

4a182bc32d94be7f652e8c63efa1d47c.png

而滑動窗口把固定的窗口又分成了很多小的窗口單位,比如下圖,每個固定窗口的大小為1分鐘,又拆分成了6份,每次移動一個小的單位,保證總和不超過閾值。

43a1dbe7f785670dea0a39a290bb5ee1.png

這樣就可以避免上面的窗口邊緣超出限制的問題。

ff9b9f7562416af2588c2a9192995607.png


? ?使用 Redis 實現高效計數器? ?

限速器算法的思想其實很簡單,我們需要使用計數器記錄用戶的請求,如果超過閾值,服務這個請求,否則,拒絕這個請求。

一個很重要的問題是,我們應該把計數器放在哪里?我們知道,磁盤速度比較慢,使用數據庫明顯是不太現實的方案,想要更快的話,可以使用內存緩存,最常見的就是 Redis,是的,我們可以使用 Redis 實現高效計數器,如下

55e34e0bc713103cebcc2a96440c094b.png


? ?規則引擎? ?

Lyft 是一個開源的限速組件,可以供我們參考,它通過 Yaml 配置文件實現靈活的限速規則,看下面的示例

9fd250a82f89048b3405f298be1c1fd8.png

這個配置表示系統每天只能發送 5 條營銷信息。

dee94c82e24413ffd4a160a52531f3cd.png

這個配置表示 1分鐘的登錄次數不能超過 5 次。

可以看到,基于配置文件,聲明式的限速規則是非常靈活的,我們可以把配置文件保存到磁盤中。


? ?限速提示? ?

當請求超過限制時,限速器會拒絕掉其他的請求,這樣其實不夠,為了更好的用戶體驗,我們需要返回友好的錯誤信息給用戶,并提示。

首先,限速器拒絕請求后,可以返回 HTTP 狀態碼 429,表示請求過多。

其次,我們可以返回更詳細的信息,比如,剩余請求次數、等待時間等。一種很常見的做法時,把這些信息放到 Http 響應的 Header 中返回,如下

? X-Ratelimit-Remaining:表示剩余次數?? X-Ratelimit-Limit:表示客戶端每個時間窗口可以進行多少次調用?? X-Ratelimit-Retry-After:表示等待多長時間可以進行重試

看起來不錯!讓我們看看現在的架構設計

5be58bfce807878f7d0bce6aacad3860.png

首先,限速規則存儲在磁盤上,因為要經常訪問,可以添加到緩存中。當客戶端向服務器發送請求時,會先發送到限速中間件。限速中間件從緩存中拉取限速規則,同時把請求數據寫入到 Redis 的計數器,然后判斷是否超出限制。如果沒有超出限制,把請求轉發給我們的后端服務器。如果超出了限制,第一種方案,丟棄多余的請求,返回 429,第二種方案,把多余的請求推送到消息隊列中,后續再進行處理。使用哪種方案,取決于您的實際場景。


? ?分布式環境? ?

構建一個在單服務器運行的限速器是很簡單的,但是在分布式環境中,支持多臺服務器,那就完全是另外一回事了。

我們主要要考慮兩個問題:

? 并發問題? 數據同步問題

并發問題,我們的限速器的工作原理是,當接收到新的請求時,從 Redis 中讀取計數器 counter,然后做加一的操作,在高并發場景中,可能存在多個線程讀到了舊的值,比如,兩個線程同時讀取到 counter 的值為3,然后都把 counter 改成了4,這樣是有問題的,其實最終應該是 5。

有朋友說,我們可以用鎖,但實際上,鎖的效率是不高的。解決這個問題通常有兩個方案,第一個是使用 Lua 腳本,第二個是使用 Redis 的 sorted sets 數據結構,具體的細節本文不做過多介紹。

數據同步問題,在流量比較大的情況下,一個限速器是難以支撐的,我們需要多個限速器。由于 Web 層通常是無狀態的,客戶端的請求會隨機發送給不同的限速器,如下

f6a0cc1c110daff735b76783e7dd524d.png

這種情況下,如果沒有數據同步,我們的限速器肯定是沒辦法正常工作的。

我們可以使用像 Redis 這樣的集中式數據存儲,如下

2e428ffd3f3fd383ecdce7f6c97a84ea.png


? ?性能優化? ?

當我們的系統是面向全球用戶時,為了讓各個地區的用戶都能有一個不錯的體驗,通常會在不同的地區設置多個數據中心。另外,現在很多云服務商在全球各地都有邊緣服務器,流量會自動路由到最近的邊緣服務器,來減少網絡的延遲。

25654e5302b322a891cea707b67acfa7.png

當然,存在多個數據中心時,可能還要考慮到數據的最終一致性。


? ?總結? ?

在本文中,我們討論了不同的限速算法,以及它們有優缺點,算法包括:

? 令牌桶? 漏桶? 固定窗口計數器? 滑動窗口日志? 滑動窗口計數器

然后,我們討論了分布式環境中的系統架構,并發問題和數據同步問題,和靈活配置的限速規則,最后你會發現,實現一個限速器,其實沒有那么難!

希望對您有用!

? 譯:等天黑

? 作者:Alex Xu

? 來源:System Design Interview

7418f3b6e8ddfdbf3def2acd6b9ae830.png

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/287691.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/287691.shtml
英文地址,請注明出處:http://en.pswp.cn/news/287691.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C語言新手的100個報錯解法 已更新11個錯誤

學習目標 收藏文章報錯可以過來查 [更新數據] 此文將會持續更新,收錄錯誤信息,若本文沒有收錄記得聯系我~ CSDN 1_bit 持續更新中… [發布日期:2020年11月16日 14:55:00] 更新: 暫無 C語言教程 C語言真的很難嗎?那…

【遙感數字圖像處理】實驗:遙感圖像顯示與數據輸入/輸出(Erdas版)

一、實驗平臺:Erdas 9.1 二、實驗內容:視窗功能簡介、圖形和圖像顯示操作、實用菜單操作、顯示操作、AOI菜單操作、矢量和柵格菜單、數據的輸入輸出等。 三、實驗目的:初步了解Erdas的主要功能模塊,在此基礎上,掌握視…

在Windows Server2016中安裝SQL Server2016(轉)

在Windows Server2016中安裝SQL Server2016(轉) 轉自: http://blog.csdn.net/yenange/article/details/52980135 參考: SQL Server2016企業版 附全版本key - moonpure的專欄 - CSDN博客 http://blog.csdn.net/moonpure/article/d…

mysql的復雜查詢_mysql復雜查詢

所謂復雜查詢,指涉及多個表、具有嵌套等復雜結構的查詢。這里簡要介紹典型的幾種復雜查詢格式。一、連接查詢連接是區別關系與非關系系統的最重要的標志。通過連接運算符可以實現多個表查詢。連接查詢主要包括內連接、外連接等。假設有Student和Grade兩個表如下&…

數據庫調優要點紀要

數據庫瓶頸一般在IO和CPU 1、少用group by, order by 2、通過索引來排序(不要所有字段都用索引,因為insert、update要重構索引很耗時) 3、避免select * 4、少用join 5、join和子查詢,還是用join來代替子查詢吧 6、少用or 7、用uni…

Unity3D 之UGUI 滑動條(Slider)

這里來講解下UGUI 滑動條(Slider)的用法 控件下面有三個游戲對象 Background -->背景 Fill Area --> 前景區域 Handle Slide Area --> 滑動條 Slider的屬性 其他幾個設置和其他控件都差不多,這里來講解幾個特有的屬性。 Direction -->方向 Whole Number…

Android Studio導入別人的module提示錯誤Plugin with id ‘com.jfrog.bintray‘ not found.

1 問題 Android Studio導入別人的module提示錯誤如下 Plugin with id com.jfrog.bintray not found. Plugin with id com.github.dcendents.android-maven not found 2 解決辦法 在我們的項目的build.gradle添加如下配置 buildscript {repositories {google()jcenter()}dep…

C語言真的很難嗎?那是你沒看這張圖,化整為零輕松學習C語言。

真不難 C語言難不難?這個問題是相對的,對于找到合適方法學習C語言的同學想必是覺得很簡單;但對于一部分同學來說,沒有眾觀全局就會誤以為剛入門就需要學習龐大的知識,學著學著開始看不懂,由于心理作怪&…

【中間件】.net Core中使用HttpReports進行接口統計,分析, 可視化, 監控,追蹤等...

HttpReports 基于.Net Core 開發的APM監控系統,使用MIT開源協議,主要功能包括,統計, 分析, 可視化, 監控,追蹤等,適合在微服務環境中使用。官方地址:https://www.yuque.com/httpreports/docs/u…

【遙感數字圖像處理】實驗:遙感影像輻射糾正(大氣糾正)完整操作圖文教程(Erdas版)

一、實驗平臺:Erdas 9.1 二、實驗數據:dmtm.img 三、實驗內容:利用回歸分析法校正影像 四、實驗原理:大氣散射只影響短波波段,長短波進行對比,找出影響短波的程輻射值,將其減去 五、實驗目的:掌握回歸分析法校正影像的方法及步驟,能熟練地對影像進行校正 六、實…

Android之開源視頻壓縮框架RxFFmpeg的commands設置

1 Android視頻壓縮框架 地址:https://github.com/microshow/RxFFmpeg 2 問題 用ffmpeg進行壓縮的時候,我們需要采用ffmpeg命令壓縮官網給的命令如下 String text = "ffmpeg -y -i /storage/emulated/0/1/input.mp4 -vf boxblur=25:5 -preset superfast /storage/emul…

Acitivty生命周期

為什么80%的碼農都做不了架構師?>>> Acitivty 有七個生命周期: onCreate:當第一次調用一個Activity就會執行onCreate方法 onStart:當Activity處于可見狀態的時候就會調用onStart方法 onResume:當Activity可…

listview嵌套gridview

1.首先要自定義一個繼承gridview的類 public class MyGridView extends GridView {public boolean hasScrollBar true;public MyGridView(Context context) {super(context);}public MyGridView(Context context, AttributeSet attrs) {super(context, attrs);}Overrideprotec…

還不懂你現在學習的編程語言能做什么?還不懂如何進階?過來看圖

前言說七說八 本篇文章的配圖標注、內容并不代表僅有;本篇僅以個人經驗及當前大學(大專、本科)相關課程作對比,列出比較常規的語言發展走向及相關技術;再次重申,本圖及本文所涉及的技術發展走向并不代表著…

IT新起之秀

辭職以后自己比較迷茫,不知道自己能干什么,09年畢業到現在雖然工作經驗有7、8年,但是感覺自己什么都不會,除了自己能下車間別的好像也做不成,沒有一技之長。我更像是一個經驗用了7、8年而不是有7、8年的經驗 在齊魯人才…

【遙感數字圖像處理】實驗:遙感影像幾何糾正完整操作流程(Erdas版)

☆☆☆ 幾何糾正預備知識 ☆☆☆ 1、幾何變形誤差的影響因素 遙感器本身引起的畸變外部因素引起的畸變處理過程中引起的畸變2、需要做精糾正的情況 景與景之間作比較GIS建模之前監督分類時提取樣本創建高精度比例尺的影像地圖與矢量數據疊加源于不同比例尺的地圖之間比較提取精…

openid 釘釘_釘釘開發入門,微應用識別用戶身份,獲取用戶免登授權碼code,獲取用戶userid,獲取用戶詳細信息...

最近有個需求,在釘釘內,點擊微應用,獲取用戶身份,根據獲取到的用戶身份去企業內部的用戶中心做校驗,校驗通過,相關子系統直接登陸;就是在獲取這個用戶身份的時候,網上的資料七零八落的,找的人煩躁的很,所以自己記錄一下;實現這個要求,有好幾種方式,使用ISV方式相對來說比較簡單…

趣味二維碼生成

1背景介紹 最近在 Github 看到了一個有趣的項目 amazing-qr,它支持生成普通二維碼,帶圖片的藝術二維碼,動態二維碼。項目是用 python 編寫的,以命令行的方式運行生成,不太方便調用,因此,我…

學習進度博客十二

本周學習軟件工程所花時間為:4小時 代碼:200行 博客發表篇數:3 了解到的知識點:這周我們開始了第二次沖刺階段 轉載于:https://www.cnblogs.com/wulun/p/5610433.html

Android Studio提示No virtual method asBitmap()Lcom/bumptech/glide/RequestBuilder

1 問題 android studio導入別人項目的module,運行點擊app,程序奔潰,錯誤日志如下 Process: com.example.chenyu, PID: 6302java.lang.NoSuchMethodError: No virtual method asBitmap()Lcom/bumptech/glide/RequestBuilder; in class Lcom/bumptech/glide/RequestM…