開發訪問量比較大的系統是,爬蟲的目的就是解決訪問量大的問題;緩存穿透是為了保護后端數據庫查詢服務;計數服務解決了接近真實訪問量以及數據庫服務的壓力。
架構圖
限流
就拿十萬博客來說,如果存在熱點文章,可能會有數十萬級別的並發用戶參與閱讀。如果想讓這些用戶正常訪問,無非就是加機器橫向擴展各種服務,但凡事都有一個利益平衡點,有時候只需要少量的機器保證大部分用戶在大部分時間可以正常訪問即可。
亦或是,如果存在大量爬蟲或者惡意攻擊,我們必須采取一定的措施來保證服務的正常運行。這時候我們就要考慮限流來保證服務的可用性,以防止非預期的請求對系統壓力過大而引起的系統癱瘓。通常的策略就是拒絕多余的訪問,或者讓多余的訪問排隊等待服務。
限流算法
任何限流都不是漫無目的的,也不是一個開關就可以解決的問題,常用的限流算法有:令牌桶,漏桶。
令牌桶
令牌桶算法是網絡流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一種算法。典型情況下,令牌桶算法用來控制發送到網絡上的數據的數目,並允許突發數據的發送(百科)。
用戶的請求速率是不固定的,這里我們假定為10r/s,令牌按照5個每秒的速率放入令牌桶,桶中最多存放20個令牌。仔細想想,是不是總有那么一部分請求被丟棄。
漏桶
漏桶算法的主要目的是控制數據注入到網絡的速率,平滑網絡上的突發流量。漏桶算法提供了一種機制,通過它,突發流量可以被整形以便為網絡提供一個穩定的流量(百科)。
令牌桶是無論你流入速率多大,我都按照既定的速率去處理,如果桶滿則拒絕服務。
應用限流
Tomcat
在Tomcat容器中,我們可以通過自定義線程池,配置最大連接數,請求處理隊列等參數來達到限流的目的。
Tomcat默認使用自帶的連接池,這里我們也可以自定義實現,打開/conf/server.xml文件,在Connector之前配置一個線程池:
-
name:共享線程池的名字。這是Connector為了共享線程池要引用的名字,該名字必須唯一。默認值:None;
-
namePrefix:在JVM上,每個運行線程都可以有一個name 字符串。這一屬性為線程池中每個線程的name字符串設置了一個前綴,Tomcat將把線程號追加到這一前綴的后面。默認值:tomcat-exec-;
-
maxThreads:該線程池可以容納的最大線程數。默認值:200;
-
maxIdleTime:在Tomcat關閉一個空閑線程之前,允許空閑線程持續的時間(以毫秒為單位)。只有當前活躍的線程數大於minSpareThread的值,才會關閉空閑線程。默認值:60000(一分鍾)。
-
minSpareThreads:Tomcat應該始終打開的最小不活躍線程數。默認值:25。
配置Connector
-
executor:表示使用該參數值對應的線程池;
-
minProcessors:服務器啟動時創建的處理請求的線程數;
-
maxProcessors:最大可以創建的處理請求的線程數;
-
acceptCount:指定當所有可以使用的處理請求的線程數都被使用時,可以放到處理隊列中的請求數,超過這個數的請求將不予處理。
API限流
這里我們采用開源工具包guava提供的限流工具類RateLimiter進行API限流,該類基於"令牌桶算法",開箱即用。
自定義定義注解
自定義切面
業務實現:
分布式限流
Nginx
如何使用Nginx實現基本的限流,比如單個IP限制每秒訪問50次。通過Nginx限流模塊,我們可以設置一旦並發連接數超過我們的設置,將返回503錯誤給客戶端。
配置nginx.conf
配置說明
imitconnzone
是針對每個IP定義一個存儲session狀態的容器。這個示例中定義了一個100m的容器,按照32bytes/session,可以處理3200000個session。
limit_rate 300k;
對每個連接限速300k. 注意,這里是對連接限速,而不是對IP限速。如果一個IP允許兩個並發連接,那么這個IP就是限速limit_rate×2。
burst=5;
這相當於桶的大小,如果某個請求超過了系統處理速度,會被放入桶中,等待被處理。如果桶滿了,那么抱歉,請求直接返回503,客戶端得到一個服務器忙的響應。如果系統處理請求的速度比較慢,桶里的請求也不能一直待在里面,如果超過一定時間,也是會被直接退回,返回服務器忙的響應。
OpenResty
這里我們使用 OpenResty 開源的限流方案,測試案例使用OpenResty1.15.8.1最新版本,自帶lua-resty-limit-traffic模塊以及案例 ,實現起來更為方便。
限制接口總並發數/請求數
熱點博文,由於突發流量暴增,有可能會影響整個系統的穩定性從而造成崩潰,這時候我們就要限制熱點博文的總並發數/請求數。
這里我們采用 lua-resty-limit-traffic中的resty.limit.count模塊實現:
限制接口時間窗請求數
現在網絡爬蟲泛濫,有時候並不是人為的去點擊,亦或是存在惡意攻擊的情況。此時我們就要對客戶端單位時間內的請求數進行限制,以至於黑客不是那么猖獗。當然了道高一尺魔高一丈,攻擊者總是會有辦法繞開你的防線,從另一方面講也促進了技術的進步。
這里我們采用 lua-resty-limit-traffic中的resty.limit.conn模塊實現:
平滑限制接口請求數
之前的限流方式允許突發流量,也就是說瞬時流量都會被允許。突然流量如果不加以限制會影響整個系統的穩定性,因此在秒殺場景中需要對請求整形為平均速率處理,即20r/s。
這里我們采用 lua-resty-limit-traffic 中的resty.limit.req 模塊實現漏桶限流和令牌桶限流。
其實漏桶和令牌桶根本的區別就是,如何處理超過請求速率的請求。漏桶會把請求放入隊列中去等待均速處理,隊列滿則拒絕服務;令牌桶在桶容量允許的情況下直接處理這些突發請求。
漏桶
桶容量大於零,並且是延遲模式。如果桶沒滿,則進入請求隊列以固定速率等待處理,否則請求被拒絕。
令牌桶
桶容量大於零,並且是非延遲模式。如果桶中存在令牌,則允許突發流量,否則請求被拒絕。
壓測
為了測試以上配置效果,我們采用AB壓測,Linux下執行以下命令即可:
測試命令:
測試結果:
總結
以上限流方案,只是針對此次大訪問量項目做一個簡單的小結,大家也不要刻意區分那種方案的好壞,只要適合業務場景就是最好的。