一、 平台介紹
1. 為什麽要做IP限流
在網路世界裏,真實使用者與爬蟲機器人並存,正常與異常請求交織。作為服務提供者,我們希望最大化成本收益,吸引真實使用者。然而,總有人試圖非法獲取數據。為了保護數據,IP限流成為有效的防禦手段。大多數爬蟲機器人都在固定伺服器上操作,透過記錄和跟蹤異常存取量的IP,我們就能辨別真實使用者請求。
2. 要達到的目標
監控IP的存取行為,將其存取量及存取行為進行記錄(完成);
支持手動將單個IP、IP段加入到黑名單中,並對加入到黑名單中的IP進行過濾(完成);
支持手動將單個IP、IP段加入到白名單中,並對加入到白名單中的IP進行過濾(完成);
在基於一定的規則下,能夠自動將異常存取的IP加入的IP黑名單中(完成);
並對異常存取的IP進行告警,並支持套用自訂IP黑名單發現規則及告警實作(待實作);
支持針對集群套用進行匯總統計(完成);
高效的數據統計、儲存及讀取,減少對套用效率的影響,目前存在Redis中(完成);
支持對規則不同儲存的延伸支援,如可將規則持久化到DB中、ZK中等,實作已經抽象化,Redis的已經完成,其它儲存邏輯可根據情況自訂(完成);
能夠支持按分緯度及秒緯度對存取的IP進行統計(完成);
有一定的管理界面,能夠對黑名單及TOP IP的存取情況進行查詢及操作(完成);
支持將統計數據輸出到不同的儲存,如Redis、Influxdb等,實作已經抽象化,Redis的已經完成,其它儲存邏輯可根據情況自訂(完成);
支持多平台對存取數據進行呈現,如以後數據可以在Grafana中呈現,目前有基本的管理界面,更精細的管理界面及Grafana中展示數據後期再考慮實作(部份完成);
支持對IP及IP段存取最高TPS進行統一配置(完成),也支持對單個IP及IP段進行存取控制(完成);
多套用集群統一流量管控平台,可管理所有接入的套用的存取情況(完成);
二、架構
1. 互動架構
2. 實作架構
3. 系統模組
收集存取緯度數據的Agent模組,需要套用整合;
用於檢視及管理的控制台模組,控制台模組為Master/Slave架構,Master用於執行定時任務、對節點進行檢查、檢查系統收集的IP黑名單情況等;
三、設計原則
1. 高效能
存取緯度數據的統計、過濾及收集,必須是高效的,並且盡可能的減少由於這些緯度數據的收集給整合套用帶來的效能損耗;
存取緯度數據收集時,盡可能的采用全記憶體操作,減少或避免本地IO及遠端IO的操作;
存取緯度數據的上報、從遠端同步數據,使用異常操作;
2. 高可用
當IP限流系統的控制台不可用時,不能夠影響到整合存取緯度數據的統計的套用端;
對整合存取緯度統計的套用定期健康檢查,確保其可用性;
IP限流系統的控制台支持水平擴充套件,且無狀態;
3. 高可延伸
緯度數據的儲存支持擴充套件,可以根據實際情況支持不同的後端儲存結構,預設支持Redis,可延伸為支持MySQL、Elasticsearch、Influxdb等;
對超限存取IP處理規則的可延伸性,同時支持在控制台及整合客戶端進行擴充套件,且提供預設的處理規則;
4. 簡單易用
整合簡單,盡可能除在pom.xml中引入Jar包以外,不需要做其它任何的操作;
使用簡單,提供基本的管理界面給使用者,可對IP的存取情況、IP白名單、IP黑白單、QPS設定等進行管理和檢視;
四、儲存設計
系統預設設計只會保存最多一個小時的存取緯度數據,且每個套用的數據是存放在單的hashkey中的,因而單個套用數據量不大,且了存取上的高效性,此處就預設使用Redis做為儲存,也支持擴充套件為使用其它的儲存。
1. black-ips
用於儲存全域黑名單IP的Key,儲存結構為Hash,HashKey為IP,HashValue為物件com.eeeffff.limiter.common.vo.BlackIpVO,其定義的欄位如下:
註:
每個套用的IP黑名單數據都會單獨儲存到不同的Key中,其命名規則為"套用名稱-black-ips",如IP限制平台控制台的IP黑名單數據,保存的Key為:ip-limiter-dashboard-black-ips;
2. white-ips
用於儲存全域白名單IP的Key,儲存結構為Hash,HashKey為IP,HashValue為物件com.eeeffff.limiter.common.vo.WhiteIpVO,其定義的欄位如下:
註:
每個套用的IP白名單數據都會單獨儲存到不同的Key中,其命名規則為"套用名稱-white-ips",如IP限制平台控制台的IP白名單數據,保存的Key為:ip-limiter-dashboard-white-ips;
3. minute-access
用於儲存所有接入了IP限流平台的套用客戶端的每分鐘的存取統計匯總的Key,儲存結構為Hash,HashKey為代表存取的分鐘,HashValue為物件List,com.eeeffff.limiter.common.vo.AccessVO定義的欄位如下:
註:
每個套用的分鐘存取緯度數據都會單獨儲存到不同的Key中,其命名規則為"套用名稱-minute-access",如IP限制平台控制台的分鐘存取緯度數據,保存的Key為:ip-limiter-dashboard-minute-access;
每個套用每個節點的的分鐘存取緯度數據都會單獨儲存到不同的Key中,其命名規則為"套用名稱-ip及埠-minute-access",如IP限制平台控制台的分鐘存取緯度數據,保存的Key為:ip-limiter-dashboard-127.0.0.1:20520-minute-access;
4. ip-limit
用於儲存全域IP QPS設定的Key,儲存結構為Hash,HashKey為IP,HashValue為物件com.eeeffff.limiter.common.vo.IpLimitVO,其定義的欄位如下:
註:
每個套用的IP QPS設定數據都會單獨儲存到不同的Key中,其命名規則為"套用名稱-ip-limit",如IP限制平台控制台的IP白名單數據,保存的Key為:ip-limiter-dashboard-ip-limit;
5. registered-clients
用於儲存註冊到IP限流平台管理控制台的Key,儲存結構為Hash,HashKey為套用名稱,HashValue為物件Map,用於儲存該套用所有註冊的客戶端,Map的Key為套用客戶端的IP+埠,com.eeeffff.limiter.common.vo.Client對定義的欄位如下:
6. ip-limiter-dashboard-master
用於儲存IP限流平台的Master節點的Key,儲存結構為普通的字串,其值為當前master節點的IP+埠。
7. permitsPerSecondEachIp
用於儲存每個IP預設的最大QPS值的Key,儲存結構為普通的數位,其值為當每個IP預設的最大QPS值。
註:
每個套用都會單獨儲存IP預設值最大QPS值,儲存結構為普通的數位,其值為當前套用每個IP預設的最大QPS值,儲存每個套用預設存取QPS的Redis的Key命名規則為「套用名稱-permitsPerSecondEachIp」,如ip-limiter-dashboard的key為"ip-limiter-dashboard-permitsPerSecondEachIp"。
五、系統設計
1. IP限流Agent
該Agent用於整合到套用端,提供了一些方便性的功能,如IP存取數據的收集與上報、從遠端控制台獲取IP黑/白名單等;
每個節點本地預設保留60份以1分鐘為緯度的存取統計數據,即保存60分鐘以分鐘為緯度統計的存取數據,預設保留60份以秒為統計緯度的存取數據,這都可以根據相應的配置項進行調整。
1)功能模組
2)限流演算法
常用的限流演算法有以下四種:
計數器(固定視窗)演算法
滑動視窗演算法
漏桶演算法
令牌桶演算法
各種演算法的優缺點,可以檢視這裏。
選擇的演算法為計數器(固定視窗)演算法,因為我們的套用場景為單個IP的限流,而不是針對所有請求限流,且該演算法實作簡單,能夠滿足一定的突發情況的處理,該演算法的實作類為com.eeeffff.limiter.core.interceptor.IpQpsRateLimiter。
3)核心功能 IP流控請求合法性檢查:確保IP的存取不會超過QPS的限制,如果是黑名單的IP直接拒絕存取,確保系統的安全性
本地存取數據的上報:目前只是以分緯度主動上報了每分鐘IP的存取統計數據
從遠端更新IP黑白名單及QPS限制配置:更新到的配置會存放在本地緩存中,以確保本地檢查合法性存取的高效性
提供秒緯度存取統計介面:遠端控制台可以呼叫本地以秒緯度統計的即時QPS,更方便的了解當前套用的執行情況
4)核心流程
請求合法性檢查
註:IP白名單、IP黑名單及IP的QPS都是在本地記憶體或本地緩存中,沒有遠端的IO操作,因而該校驗過程僅會對整體效能產生非常微弱的影響。
請求合法性檢查 子邏輯之 IP白名單校驗邏輯
請求合法性檢查 子邏輯之 IP黑名單校驗邏輯
請求合法性檢查 子邏輯之 IP QPS超限存取校驗及增加IP的存取量
請求合法性檢查 子邏輯之 IP QPS超限存取校驗及增加IP的存取量 子邏輯之 當前IP是否超限存取的判斷
從遠端更新配置
5)核心配置
#IP Limiter控制台的地址
ip.limiter.core.dashboardAddress = 127.0.0.1:8080
#當前伺服器的IP地址
ip.limiter.core.serverAddress = [不配置則自動獲取,如果指定則例用指定值]
#本地緩存中保留多少份以秒緯度統計的數據,單位為秒
ip.limiter.core.secondsMetricLocalKeeped = 60
#本地套用最多保留多少份以分鐘為單位的統計,如值為60時,則表示本地會保留60份以分鐘為統計緯度統計的存取最多的Ip,也可以理解為保留60分鐘每分鐘存取最多的Ip的統計
ip.limiter.core.maxTopAccessMinutes = 60
#IP的QPS限制配置及IP黑/白名單從遠端控制台更新時間間隔,單位為毫秒
ip.limiter.core.ipQpsLimitAndBlackIpUpdateTimeInterval = 10000
2. IP限流控制台
1)功能模組
2)核心功能
a)超限存取IP自動限制存取
針對超限存取的IP,會臨時將其加入到黑名單中,以臨時阻止其對系統的存取。
限制分為四個等級:
限制存取一分鐘
限制存取一小時
限制存取二十四小時
限制永久存取
限制規則及限制升級規則:
針對首次存取超限的IP,會將其加到黑名單中,並限制其存取一分鐘,如果一分鐘後沒有超限存取的行為,則將解除其存取限制;
針對被限制存取一分鐘的IP,如果在一分鐘後解除其存取限制後,還有繼續超限存取的行為,則會將其存取限制升級為限制存取一小時,如果沒有系統則會自動將其從黑名單中移除;
針對被限制存取一小時的IP,如果在一小時後解除其存取限制後,還有繼續超限存取的行為,則會將其存取限制升級為限制存取一天,如果沒有系統則會自動將其從黑名單中移除;
針對被限制存取二十四小時的IP,如果在二十四小時後解除其存取限制後,還有繼續超限存取的行為,則會將其存取限制升級為限制記久存取,如果沒有系統則會自動將其從黑名單中移除;
b)IP黑/白名單的管理
支持針對指定套用設定IP黑/白名單,生效範圍為指定的套用,也支持從全域上設定IP黑/白名單,套用到所有套用中,管理功能包括IP黑/白名單增加、刪除、查詢的操作。
c)IP的最大QPS管理
IP的最大QPS設定分為以下幾塊內容:
針對指定套用指定IP的最大QPS進行設定,判斷IP是否超限存取最優先適用的規則;
針對指定套用所有預設IP的最大QPS進行設定,這個設定適用於存取該套用所有未指定最大QPS的IP,判斷IP是否超限存取次優先適用的規則;
針對所有套用預設IP的最大QPS進行設定,判斷IP是否超限存取最後適用的規則;
滿足以上任何規則,都認為該IP是超限存取IP。
d)註冊客戶端健康檢查
定期對註冊到控制台中所有的套用及每個套用的所有節點進行健康檢查,檢查邏輯如下:
套用的某個節點連續三次都檢查不成功,那該節點就會從該套用刪除掉;
套用的所有節點都檢查不成功,或者該套用本身已經沒有了任何可用的節點,那該套用就會被刪除;
e)系統對自動增加為黑名單的IP進行自檢
針對首次存取超限的IP,如果一分鐘後沒有超限存取的行為,則將解除其存取限制,系統會將其從黑名單中刪除;
針對被限制存取一分鐘的IP,如果在一分鐘解除其存取限制後,沒有繼續超限存取的行為,系統會自動將其從黑名單中移除;
針對被限制存取一小時的IP,如果在一小時解除其存取限制後,沒有繼續超限存取的行為,系統會自動將其從黑名單中移除;
針對被限制存取二十四小時的IP,如果在二十四小時解除其存取限制後,沒有繼續超限存取的行為,系統會自動將其從黑名單中移除;
f)查詢套用的即時存取情況
在控制台查詢指定套用端節點的即時IP存取情況。
3)核心流程
a)註冊客戶端健康檢查
b)系統對自動增加為黑名單的IP進行自檢
4)核心配置
spring.boot.enableautoconfiguration=true
#服務的埠號
server.port = 8080
spring.application.name = ip-limiter-dashboard
#以下是Apollo需要的相關配置參數,如果使用Apollo做為配置中心,在Apollo中建立名為ip-limiter-dashboard的計畫
#透過Apollo啟動,命令列要帶對應的參數,如:
#-Dspring.profiles.active=DEV -Dapollo.meta=http://apollo.dev.xxx.com:8072
##該參數透過命令列傳入,透過啟動命令傳入該參數,以便於支持多個不同的環境
##active.env= DEV
## 將 Apollo 配置載入提到初始化日誌系統之前,需要托管日誌配置時開啟
#apollo.bootstrap.eagerLoad.enabled = true
## 套用全域唯一的身份標識
#app.id = ip-limiter-dashboard
## Apollo Meta Server 地址,透過啟動命令傳入該參數,以便於支持多個不同的環境
##apollo.meta = http://apollo.dev.xxx.com:8072
## 自訂本地配置檔緩存路徑
#apollo.cacheDir = ./config
## 設定在套用啟動階段就載入 Apollo 配置
#apollo.bootstrap.enabled = true
## 註入 application namespace
#apollo.bootstrap.namespaces = application
#如果使用Apollo做為配置中心,將以下配置拷貝到Apollo中即可,並將其在當前配置檔中註釋掉
ip.limiter.dashboard.permitsPerSecondEachIp = 50
ip.limiter.dashboard.maxTopAccessIps = 10
ip.limiter.dashboard.maxRedisTopAccessIps = 50
ip.limiter.dashboard.globalMaxRedisTopAccessIps = 50
ip.limiter.dashboard.maxRedisTopAccessMinutes = 60
ip.limiter.dashboard.globalMaxRedisTopAccessMinutes = 60
ip.limiter.dashboard.redisLockMaxWaitMillis = 60000
ip.limiter.dashboard.maxTopAccessMinutes = 30
ip.limiter.dashboard.connectTimeout = 5000
ip.limiter.dashboard.soTimeout = 5000
ip.limiter.dashboard.maxConnTotal = 100
ip.limiter.dashboard.maxConnPerRoute = 10
ip.limiter.dashboard.maxHttpRetryTimes = 5
ip.limiter.dashboard.httpRetryIntervalTime = 20
ip.limiter.dashboard.appClientHealthCheckRate = 1
ip.limiter.dashboard.systemAddBlackIpCheckRate = 1
spring.redis.host = 192.168.12.111
spring.redis.port = 6379
spring.redis.password = 8lFvrZh7d7Ik8LtNwpBMakleishen
spring.redis.database = 12
spring.redis.timeout = 5000
spring.redis.jedis.pool.max-idle = 8
spring.redis.jedis.pool.min-idle = 0
spring.redis.jedis.pool.max-wait = 8
spring.redis.jedis.pool.max-active = 20
ip.limiter.core.dashboardAddress = 127.0.0.1:8080
5)功能界面 (單個套用)分緯度存取TOP統計
(單個套用)秒緯度存取TOP統計
(單個套用)IP白名單設定
(單個套用)IP黑名單設定
(單個套用)單個IP的QPS設定
(分局)分緯度存取TOP統計
(全域)黑名單設定
(全域)白名單設定
(全域)單個IP的QPS設定
六、IP限流平台的搭建
1. 下載源碼並安排
git clone https://gitee.com/laofeng/ip-limiter.git
cd ip-limiter
2. 修改必要的核心配置檔
首先需要啟動IP限流控制台,IP限流控制台預設使用Redis做為儲存,因而需要先對Redis進行配置,否則套用執行時會報錯,修改IP控制台的核心配置檔ip-limiter-dashboard/src/main/resources/application.properties,只需要修改其中的redis配置,其它的都可以使用預設值,如下所示:
spring.redis.host = 192.168.12.111
spring.redis.port = 6379
spring.redis.password = 8lFvrZh7d7Ik8LtNwpBMakleishen
spring.redis.database = 12
spring.redis.timeout = 5000
spring.redis.jedis.pool.max-idle = 8
spring.redis.jedis.pool.min-idle = 0
spring.redis.jedis.pool.max-wait = 8
spring.redis.jedis.pool.max-active = 20
3. 啟動IP限流平台控制台
cd ip-limiter-dashboard
./start.sh
註:啟動會報一些與Apollo相關的警告,可以不用理會,這個是方便配置後續接入Apollo的,如果實在是不喜歡,可以在pom中刪除Apollo的引入,並且刪除Apollo相關的程式碼即可。
控制台存取地址:
單個套用管理控制台地址:http://127.0.0.1:8080/limiter
全域套用管理控制台地址:http://127.0.0.1:8080/global-limiter
4. 啟動演示套用
演示套用的目錄ip-limiter-samples,裏麵包括了三個演示套用,分別為:
ip-limiter-spring-boot-sample: 用於演示spring boot集用IP限流平台
ip-limiter-spring-mvc-sample: 用於演示普通的spring mvc套用集用IP限流平台
ip-limiter-spring-gateway-sample: 用於演示spring gateway集用IP限流平台
這三種套用型別應該包含了大部份的套用型別,每種套用都有自己的一些不同的整合方式及特點。
1)啟動ip-limiter-spring-boot-sample
首先進入ip-limiter-spring-boot-sample目錄,透過以下命令啟動:
./install.sh
./start.sh
其預設的埠為10000,啟動成功後,可以透過http://localhost:10000/hello存取驗證,待控制台打印出往IP限流平台註冊成功字樣後,就可以在控制台的左上角的下拉框中看到該套用了。
2)啟動ip-limiter-spring-mvc-sample
該演示套用為了啟動上的方便,需要先在/etc/maven/setting.xml的pluginGroup中增加jetty的外掛程式:
<pluginGroups>
<pluginGroup>org.mortbay.jetty</pluginGroup>
</pluginGroups>
直接保存結束即可。
然後進入ip-limiter-spring-mvc-sample目錄,透過以下命令啟動:
./runJetty.sh
啟動的過程中可能會報一些Jetty的錯誤,這個不需要理會,待啟動完成後其預設的埠為10001,啟動成功後可以透過http://localhost:10001/app/health存取驗證,待控制台打印出往IP限流平台註冊成功字樣後,就可以在控制台的左上角的下拉框中看到該套用了。
3)啟動ip-limiter-spring-boot-sample
首先進入ip-limiter-spring-boot-sample目錄,透過以下命令啟動:
./install.sh
./start.sh
其預設的埠為10002,啟動成功後,可以透過http://localhost:10002/api_hello/hello存取驗證,該請求實透過閘道器存取http://localhost:10000/hello,因而ip-limiter-spring-boot-sample需要先啟動才可以驗證該套用,待控制台打印出往IP限流平台註冊成功字樣後,就可以在控制台的左上角的下拉框中看到該套用了。
七、套用的整合
目前套用的最新版本為1.1.3。
Spring Boot套用的整合
引入限流依賴的客戶端starter核心模組
<dependency>
<groupId>com.eeeffff.limiter</groupId>
<artifactId>ip-limiter-core-web</artifactId>
<version>${ip-limiter-core.version}</version>
</dependency>
其它的什麽也不用做了,方便吧@_@。
Spring MVC套用的整合
Spring MVC套用的接入就會多一些步驟,不過也不麻煩。1)引入限流依賴的客戶端starter核心模組
<dependency>
<groupId>com.eeeffff.limiter</groupId>
<artifactId>ip-limiter-core-web</artifactId>
<version>${ip-limiter-core.version}</version>
</dependency>
2)將IP限流平台的包加入掃描及增加Inteceptor
因為IP限流平台是基於Inteceptor的,在Spring的配置檔中增加如下配置:
<context:component-scan base-package="com.eeeffff.limiter"></context:component-scan>
<mvc:interceptors>
<ref bean="ipLimiterInterceptor" />
</mvc:interceptors>
如果遇到了整合上的問題,可以參考ip-limiter-spring-mvc-sample範例套用。
Spring Gateway的整合
Spring Gateway使用的是Netty做為通訊模組,不能夠像其它套用一樣使用Inteceptor來實作,需要使用其本身的GlobalFilter結合WebFilter來實作,因而需要整合不同的包:
<dependency>
<groupId>com.eeeffff.limiter</groupId>
<artifactId>ip-limiter-core-spring-gateway</artifactId>
<version>${ip-limiter-core.version}</version>
</dependency>
和普通的Spring Boot套用一樣,也是不需要做任何事情,就是這麽簡單。
原始碼下載地址:
https://gitee.com/laofeng/ip-limiter.git
看到最後,如果這個方法對你有用,一定要給我點個「 在看 」。