合理地分解問題,往往能讓問題迎刃而解。
問題
工作中遇到一個問題。
有一些 IP, 有一些主機 H,需要對這些主機進行 IP 封禁,IP 封禁還有對應的埠(Port)和入站出站方向(Direction)。埠需要累加。比如第一次封禁了 (h1,ip1,port1-portN,in), 第二次又封禁了 (h1,ip1,portJ-portM,in),則封禁結果應該是 (h1,ip1,[port1-portN, portJ-PortM],in)。如果有N個IP(ip1-ipm) 對主機 (h1-hm) 封禁,則封禁結果是 m * n 個記錄。且埠必須能夠疊加。不同封禁方向也是不同的記錄。同時,下發給客戶端的封禁請求引數應當是每一個主機對其所有IP、埠、封禁方向的封禁資訊(可以分批,但不能一個個地發)。
思考
這個問題涉及四個維度:主機、封禁IP、封禁埠、封禁方向。
需要做的事情:
- 在服務端資料庫裡插入對應的封禁請求記錄;
- 向客戶端傳送封禁請求;
- 根據客戶端處理封禁結果來更新封禁請求記錄。
顯然,不可能一個個去插入記錄和傳送請求然後更新請求。實現功能似乎並不困難,難的是要考慮效能。要達到效能要求,通常就是“批次、併發、非同步、快取”四件套。
首先從批次處理上思考。最開始思考的是,一次性查出封禁請求裡所涉及的所有主機的已有封禁記錄,然後與當前封禁請求做對比分析,計算出有哪些主機變更了IP及變更的埠、封禁方向,哪些主機只是新增IP、埠。變更了IP、埠、封禁方向,需要更新已有記錄的埠,新增IP、埠則需要插入記錄。除了資料庫記錄,還需要批次向主機傳送封禁請求。想想都覺得似乎有點複雜。腦子轉不過來。
心裡湧出了碎碎念:
我:一定要埠疊加嗎?埠疊加會增加問題排查複雜度。因為結果是很多次封禁操作的累加,必須找到所有封禁操作請求,而線上報問題過來很可能會比較晚,可能就找不到了某次歷史操作請求,導致難以排查難以定位原因。使用者既然要封禁一些IP和埠,如果結果不如使用者所料而是多出了一些埠,使用者不會覺得奇怪嗎?覆蓋還是累加究竟哪種場景居多呢?
假想產品經理:埠累加更自然一些。比如一個客戶之前封禁了若干埠,現在又封禁一些埠,難道他還要把之前的埠重新封禁一遍?
我:但是這個埠疊加,好幾個維度,確實會增加系統複雜度。有點搞不定啊!
假想產品經理:為什麼客戶端沒有問題呢?
我:客戶端當然沒問題,因為只需要考慮一個主機。對一個主機的IP和埠加加減減就可以了啊。
我:要就此認輸嗎?十年工程師,這個都搞不定,怎麼說得過去呢?
。。。
當沒有現成方案來解決問題時,回到原點來思考問題。
絕大多數程式問題,都是結構化問題。程式=資料結構+演算法。從技術角度廣義來看,軟體開發則是資料儲存加業務流程的組合,即基本或高階點的 CRUD 加一些稍微複雜點的演算法,最終砌成一佗平平無奇的流水線程式碼(程式設計師像不像未來的流水線工人呢?)。讀者應該還記得之前寫過的一篇“軟體開發:組織大規模邏輯的技藝”的文章。絕大多數軟體問題都是結構化問題。只要釐清結構,設計好演算法,再施以一些工程手段(併發、非同步、封裝、多型、解耦、API、設計模式、容錯等),問題就迎刃而解。
想到這裡,突然有了一個想法:對啊,既然客戶端是考慮一個主機,那麼服務端為什麼不能也是一次只考慮一個主機呢?因為給客戶端傳送封禁請求本身就是主機維度的,因此以主機維度來拆分封禁請求,是合理的。如果服務端一次只考慮一個主機,問題不是簡化了麼?至於多個主機,併發來搞就可以了啊。
之所以沒考慮到這一點,是因為上一個版本才做過多臺主機對一個IP 封禁,是把多臺主機給拆分成多個批次進行處理(一個批次是若干臺主機對一個IP),然後下發給客戶端的是一個主機一個IP的封禁資訊。看來是拆錯了維度。要是上次就是一個批次是以一個主機一個IP的話,這次加個併發不就完事了麼?上次之所以按照 IP 拆分為維度,是因為只有一個IP,會有很多臺主機,如果一次處理一臺主機一個IP的話,假設有 10w 臺主機,那可想見的慢了。
求解
有了思路,就感覺豁然開朗了。
針對每個主機是容易處理的,因為單個封禁記錄是以(Host, IP, Direction) 為唯一標識的,因此一個主機的一次封禁請求,只需要計算出針對一臺主機有哪些唯一標識有變更,做一些加減即可。至於所有主機,開個併發慢慢搞。在一個主機一個批次裡,資料庫插入記錄可以批次處理,而給客戶端傳送請求則只要一次操作。所以說,非同步、批次、併發,是處理大量操作類請求(資料庫訪問+API請求)的利器。如果還不夠,快取來湊。
小結
遇到多維度問題時,選擇一個合適的維度進行拆分,往往可以讓問題迎刃而解。
應用效能問題,應用層求解通常就是“非同步、併發、批次、快取”四件套,資料層求解則是索引、分庫分表分片。四管齊下,大部分效能問題都能解決,如果還有,要麼是中介軟體的調優、要麼是精簡流程。讀者可閱:“網際網路應用服務端的常用技術思想與機制綱要”
你說這人吧,遇到費腦的不願思考,整天寫流水線程式碼也沒意思,有時還是要有點挑戰性的事情才行。