1. 程式人生 > >可惡的爬蟲直接把生產6臺機器爬掛了!

可惡的爬蟲直接把生產6臺機器爬掛了!

# 引言 - 正在午睡,突然收到線上瘋狂報警的郵件,檢視這個郵件發現這個報警的應用最近半個月都沒有釋出,應該不至於會有報警,但是還是開啟郵件通過監控發現是由於某個介面某個介面流量暴增,`CPU`暴漲。為了先解決問題只能先暫時擴容機器了,把機器擴容了一倍,問題得到暫時的解決。最後覆盤為什麼流量暴增?由於最近新上線了一個商品列表查詢介面,主要用來查詢商品資訊,展示給到使用者。業務邏輯也比較簡單,直接呼叫底層一個`soa`介面,然後把資料進行整合過濾,排序推薦啥的,然後吐給前端。這個介面平時流量都很平穩。線上只部署了6臺機器,面對這驟增的流量,只能進行瘋狂的擴容來解決這個問題。擴容機器後一下問題得到暫時的解決。後來經過請求分析原來大批的請求都是無效的,都是爬蟲過來爬取資訊的。這個介面當時上線的時候是裸著上的也沒有考慮到會有爬蟲過來。 # 解決辦法 - 既然是爬蟲那就只能通過反爬來解決了。自己寫一套反爬蟲系統,根據使用者的習慣,請求特徵啥的,瀏覽器`cookie`、同一個請求頻率、使用者`ID`、以及使用者註冊時間等來實現一個反爬系統。 - 直接接入公司現有的反爬系統,需要按照它提供的文件來提供指定的格式請求日誌讓它來分析。 **既然能夠直接用現成的,又何必自己重新造輪子呢**。最後決定還是採用接入反爬系統的爬蟲元件。爬蟲系統提供了兩種方案如下: ### 方案1: - 爬蟲系統提供批量獲取黑名單`IP`的介面(`getBlackIpList`)和移除黑名單`IP`介面(`removeBlackIp`)。 業務專案啟動的時候,呼叫`getBlackIpList`介面把所有`IP`黑名單全部存入到本地的一個容器裡面(Map、List),中間會有一個定時任務去呼叫`getBlackIpList`介面全量拉取黑名單(黑名單會實時更新,可能新增,也可能減少)來更新這個容器。 - 每次來一個請求先經過這個本地的黑名單`IP`池子,`IP`是否在這個池子裡面,如果在這個池子直接返回爬蟲錯誤碼,然後讓前端彈出一個複雜的圖形驗證碼,如果使用者輸入驗證碼成功(爬蟲基本不會去輸入驗證碼),然後把`IP`從本地容器移除,同時發起一個非同步請求呼叫移除黑名單`IP`介面(`removeBlackIp`),以防下次批量拉取黑單的時候又拉入進來了。然後在傳送一個`activemq`訊息告訴其他機器這個`IP`是被誤殺的黑名單,其他機器接受到了這個訊息也就會把自己容器裡面這個`IP`移除掉。(其實同步通知其他機器也可以通過把這個`IP`存入`redis`裡面,如果在命中容器裡面是黑名單的時候,再去`redis`裡面判斷這個`ip`是否存在`redis`裡面,如果存在則說明這個ip是被誤殺的,應該是正常請求,下次通過定時任務批量拉取黑名單的時候,拉取完之後把這個`redis`裡面的資料全部刪除,或者讓它自然過期。 這種方案:**效能較好,基本都是操作本地記憶體。但是實現有點麻煩,要維護一份IP黑名單放在業務系統中。** ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210117224422966.png) ### 方案2: - 爬蟲系統提供單個判斷IP是否黑名單介面`checkIpIsBlack`(但是介面耗時有點長5s)和移除黑名單`IP`介面(`removeBlackIp`)。每一個請求過來都去呼叫爬蟲系統提供的介面(判斷`IP`是否在黑名單裡面)這裡有一個網路請求會有點耗時。如果爬蟲系統返回是黑名單,就返回一個特殊的錯誤碼給到前端,然後前端彈出一個圖形驗證碼,如果輸入的驗證碼正確,則呼叫爬蟲系統提供的移除`IP`黑名單介面,把`IP`移除。 這種方案:**對於業務系統使用起來比較簡單,直接呼叫介面就好,沒有業務邏輯,但是這個介面耗時是沒法忍受的,嚴重影響使用者的體驗** 最終綜合考慮下來最後決定採用**方案1**.畢竟系統對響應時間是有要求的儘量不要增加不必要的耗時。 ### 方案1 實現 方案1虛擬碼實現 我們上文[《看了CopyOnWriteArrayList後自己實現了一個CopyOnWriteHashMap》](https://mp.weixin.qq.com/s/8RFSyMhUg7Ve6rQlBNs2yg)有提到過對於讀多寫少的執行緒安全的容器我們可以選擇`CopyOnWrite`容器。 ```java static CopyOnWriteArraySet blackIpCopyOnWriteArraySet = null; /** * 初始化 */ @PostConstruct public void init() { // 呼叫反爬系統介面 拉取批量黑名單