隨着應用的訪問量愈來愈高,瞬時流量不可預估,爲了保證服務對外的穩定性,限流成爲每一個應用必備的一道安全防火牆,即便普通的用戶也會常常遇到,如微博的限流,抖音的限流,小米搶購的限流......若是沒有這道安全防火牆,請求的流量超過服務的負載能力,很容易形成整個服務的癱瘓。
限流須要提早評估好,若是用的不當,可能會致使有些該限制的流量沒有被限流,服務被這些過載流量打垮。有些不應限制流量的被限制,被用戶抱怨。例如,總體服務的QPS是400/s,若是限流閥值是300,就會致使每秒有100個請求本該接受服務,卻被限制訪問,若是閥值是500,就會致使每秒有100個請求負載,時間越長累積越多,這些過載的流量就有可能致使整個服務的癱瘓。node
常見的限流算法有令牌同、漏桶,還有一種計數器。web
令牌算法的過程以下算法
若是令牌桶中少於n個令牌,那麼不會刪除令牌,而且認爲這個數據包在流量限制以外,要不丟棄要不緩衝區等待
後端
兩個算法實現同樣,方向相反,令牌是勻速流入,流通是勻速流出。安全
#### 計數器
計數器比較簡單,沒有什麼算法和描述。知足必定的條件的流量計數加1,達到閥值了限制,顧名思義叫計數限流。服務器
使用最多見的就是Nginx自帶兩個限流模塊:鏈接數限流模塊ngx_http_limit_conn_module 和請求數限流模塊ngx_http_limit_req_module;還有openresty的限流模塊lua-resty-limit-traffic;還可能須要應對複雜的業務需求而自研的計數限流。咱們一一介紹下這些限流方法的使用網絡
從名字就能夠看出是Nginx的鏈接數限流。大多都是按照IP來源進行鏈接數限流,也能夠按照域名對總的鏈接數進行限流。
咱們看下鏈接數限流的配置性能
http { limit_conn_zone $binary_remote_addr zone=addr:10m; limit_conn_log_level error; limit_conn_status 503; ... server { ... location /download/ { limit_conn addr 1; }
limit_conn_zone: 配置限流的key以及存儲這些key共用的共享內存的大小;
樣例中的key 是$binary_remote_addr,表示IP地址,若是若是須要對總域名進行限流,key就應該使用 $server_name $host等等,能惟一表示該域名的Nginx變量;
zone=addr:10m中,addr表示鏈接數限流的區域名稱,10m表示能夠分配的共享空間的大小。
binary_remote_addr變量在64位平臺中佔用64字節。1M共享空間能夠保存1.6萬個64位的,10m就能夠保存16萬個。若是超過16萬個,共享空間被用完,服務器將會對後續全部的請求返回 503。
limit_conn:配置指定key的最大鏈接數。樣例中指定的最大鏈接數是1,表示Nginx最多同時容許1個鏈接進行location /limit 的行爲操做。
limit_conn_status:配置被限流後返回的狀態碼,樣例中設置的是503.
limit_conn_log_level:配置被限流後的日誌級別,設置的是error級別
看下測試代碼測試
limit_conn_zone $server_name zone=addr:10m; limit_conn_log_level error; limit_conn_status 503; server{ listen 80; server_name test.test.com; access_log /var/log/openresty/web_test.test.com_access.log test; error_log /var/log/openresty/web_test.test.com_error.log; location /test/ { limit_conn addr 2; content_by_lua ' ngx.sleep(1) ngx.say("helloworld") '; } } ab 命令 ab -n10 -c3 http://test.test.com/test/ access_log 127.0.0.1|1553438999.158|200 127.0.0.1|1553438999.160|503 127.0.0.1|1553438999.160|503 127.0.0.1|1553438999.161|503 127.0.0.1|1553438999.162|503 127.0.0.1|1553438999.163|503 127.0.0.1|1553438999.163|503 127.0.0.1|1553438999.164|503 127.0.0.1|1553439000.160|200 127.0.0.1|1553439000.160|200 error_log 2019/03/24 22:49:59 [error] 700#0: *63 limiting connections by zone "addr", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com" 2019/03/24 22:49:59 [error] 700#0: *64 limiting connections by zone "addr", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com" 2019/03/24 22:49:59 [error] 700#0: *65 limiting connections by zone "addr", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com" 2019/03/24 22:49:59 [error] 700#0: *66 limiting connections by zone "addr", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com" 2019/03/24 22:49:59 [error] 700#0: *67 limiting connections by zone "addr", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com" 2019/03/24 22:49:59 [error] 700#0: *68 limiting connections by zone "addr", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com" 2019/03/24 22:49:59 [error] 700#0: *69 limiting connections by zone "addr", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com"
能夠看到是符合咱們配置語氣的。若是咱們將
limit_conn_log_level info;
limit_conn_status 500;
能夠看到,error_log裏面記錄的日誌就是info的,固然error_log的級別要調到info級別。返回的HTTP狀態碼也會變爲500.能夠動手試下。lua
limit_conn 的執行過程
請求進入首先判判定義的key的鏈接數是否超過limit_conn配置的閥值,若是超過直接返回limit_conn_status定義的錯誤碼;若是沒有超過鏈接數+1
請求處理
請求處理完成以後鏈接數-1
這就是爲何要作下sleep操做,不然在測試環境下沒有任何壓力,兩個鏈接數徹底能夠在一秒以內處理完10個請求。爲了測試出效果,就須要在一秒以內讓Nginx沒法完成10個請求。
Nginx的請求數限流,請求數限流是漏桶算法實現的。經過定義的key來限制請求處理的頻率,能夠限制來自單個IP地址的請求處理頻率。
http { limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s; limit_req_log_level error; limit_req_status 503; ... server { ... location /search/ { limit_req zone=one burst=5; }
limit_req_zone:配置限流的key,存放key對應的共享區域空間大小,固定的請求速率。樣例中的key binary_remote_addr 表示IP地址。one 表示共享區域空間的名稱,10m表示共享區域空間的大小,跟limit_conn的定義一致,10m就能夠保存16萬個IP地址。rate=1r/s 固定請求速率設置,每秒1個請求。
limit_req:配置限流區域,桶容量,是否延遲模式。樣例中桶容量是5,延遲模式默認是延遲。
limit_req_status:配置被限流後返回的狀態。樣例中是503
limit_req_log_level:配置被限流後的日誌級別,樣例中是error
測試下上面的代碼
看下測試代碼
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s; limit_req_log_level error; limit_req_status 503; server{ listen 80; server_name test.test.com; access_log /var/log/openresty/web_test.test.com_access.log test; error_log /var/log/openresty/web_test.test.com_error.log info; location /test/ { limit_req zone=one burst=5; content_by_lua ' ngx.say("helloworld") '; } } ab 命令 ab -n10 -c10 http://test.test.com/test/ access_log 127.0.0.1|1553525058.469|200 127.0.0.1|1553525058.470|503 127.0.0.1|1553525058.470|503 127.0.0.1|1553525058.470|503 127.0.0.1|1553525058.470|503 127.0.0.1|1553525059.471|200 127.0.0.1|1553525060.470|200 127.0.0.1|1553525061.470|200 127.0.0.1|1553525062.471|200 127.0.0.1|1553525063.471|200 error_log 2019/03/25 22:44:18 [warn] 833#0: *144 delaying request, excess: 0.999, by zone "one", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com" 2019/03/25 22:44:18 [warn] 833#0: *145 delaying request, excess: 1.999, by zone "one", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com" 2019/03/25 22:44:18 [warn] 833#0: *146 delaying request, excess: 2.999, by zone "one", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com" 2019/03/25 22:44:18 [warn] 833#0: *147 delaying request, excess: 3.999, by zone "one", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com" 2019/03/25 22:44:18 [warn] 833#0: *148 delaying request, excess: 4.999, by zone "one", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com" 2019/03/25 22:44:18 [error] 833#0: *149 limiting requests, excess: 5.999 by zone "one", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com" 2019/03/25 22:44:18 [error] 833#0: *150 limiting requests, excess: 5.999 by zone "one", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com" 2019/03/25 22:44:18 [error] 833#0: *151 limiting requests, excess: 5.999 by zone "one", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com" 2019/03/25 22:44:18 [error] 833#0: *152 limiting requests, excess: 5.999 by zone "one", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com"
測試代碼中桶容量是5,按照1r/s的速度處理。能夠看到在,因爲默認是延遲模式,因此1553525059.471到1553525063.471這個時間段最多存儲5個請求,而後按照1r/s的速度處理,因爲延遲模式,error_log能夠看到這五條記錄都是延遲執行的(delaying request)。大於五條的記錄都限流503 了。
那爲何第一條記錄執行成功了?這應該是計算算法的問題,第一條記錄沒有參考值,因此第一秒沒有計算在內,這以後的都是按照第一條記錄參考的時間,因此後面的基本上都是精確的。
咱們將延遲模式改成不延遲模式看下。
location /test/ { limit_req zone=one burst=5 nodelay; content_by_lua ' ngx.say("helloworld") '; } ab 測試 ab -n7 -c7 http://test.test.com/test/ ab -n7 -c7 http://test.test.com/test/ ab -n7 -c7 http://test.test.com/test/ access_log 127.0.0.1|1554385661.861|200 127.0.0.1|1554385661.862|200 127.0.0.1|1554385661.862|200 127.0.0.1|1554385661.862|200 127.0.0.1|1554385661.862|200 127.0.0.1|1554385661.862|200 127.0.0.1|1554385661.862|503 127.0.0.1|1554385665.513|200 127.0.0.1|1554385665.514|200 127.0.0.1|1554385665.514|200 127.0.0.1|1554385665.514|503 127.0.0.1|1554385665.514|503 127.0.0.1|1554385665.514|503 127.0.0.1|1554385665.514|503 127.0.0.1|1554385667.361|200 127.0.0.1|1554385667.361|200 127.0.0.1|1554385667.362|503 127.0.0.1|1554385667.362|503 127.0.0.1|1554385667.362|503 127.0.0.1|1554385667.362|503 127.0.0.1|1554385667.362|503
咱們爲了跨時間窗口測試,咱們測試三組。先看下第一組,7個請求6個成功,一個503,其實理論上桶容量是5,至多隻可能成功5個,有個503纔對。咱們說了第一組計算算法問題基本上忽略的。
咱們看下第二組,跟第一組相差4秒,處理速度是1r/s.4秒以後按按理應該桶裏有4個位置,應該成功處理4個,3個503,怎麼如今是4個503,成功處理三個,此處仍是要強調下limit_req的實現算法不是特別精確
咱們看下第三組,比第二組晚了2秒,因此桶裏會有2個位置,應該有2個請求成功,5個請求503.這個跟預想的基本吻合。
因此總體上和理解是一致的。就是算法上不是特別的精確。咱們生產上限流也是至少幾千幾萬的限流,算法上的精確差別實際上是能夠忽略不計的。
這一部分主要是聊了下限流的原理和常見的Nginx的兩個限流模塊。下一部分咱們聊下生產中比較常見的lua限流。
------------------------------------end
一塊兒關注高性能WEB後端技術,關注公衆號