Hi,大家好,很榮幸有這個機會可以通過寫博文的方式,把這些年在後端開發過程中總結沉澱下來的經驗和設計思路分享出來
模組化設計
根據業務場景,將業務抽離成獨立模組,對外通過介面提供服務,減少系統複雜度和耦合度,實現可複用,易維護,易擴充
專案中實踐例子:
Before:
在返還購APP裡有個【我的紅包】的功能,使用者的紅包資料來自多個業務,如:邀請新使用者註冊領取100元紅包,大促活動雙倍紅包,等各種活動紅包,多個活動業務都實現了一套不同規則的紅包領取和紅包獎勵發放的機制,導致紅包不可管理,不能複用,難維護難擴充
After:
- 重構紅包業務
- 紅包可後臺管理
- 紅包資訊管理,可新增,可編輯,可配置紅包使用的規則,可管理使用者紅包
- 紅包獎勵發放統一處理
- 應用業務的接入只需要專注給使用者進行紅包發放即可
設計概要
Before VS After
產品有時提出的業務需求沒有往這方面去考慮,結合場景和未來擴充需要,在需求討論的時候提出模組化設計方案,並可以協助產品進行設計
通用服務抽離
在專案開發中經常會遇到些類似的功能,但是不同的開發人員都各自實現,或者因為不能複用又重新開發一個,導致了類似功能的重複開發,所以我們需要對能夠抽離獨立服務的功能進行抽離,達到複用的效果,並且可以不斷擴充完善,節約了後續開發成本,提高開發效率,易於維護和擴充
專案中實踐例子:
Before
在業務中經常需要對使用者進行資訊通知,如:簡訊定時通知,APP訊息推送,微信通知,等
開發人員在接到需求中有通知功能的時候沒有考慮後續擴充,就接入第三方資訊通知平臺,然後簡單封裝個資訊通知方法,後續也有類似資訊通知需求的時候,另一個開發人員發現當前這個通知方法無法滿足自己的需求,然後又自己去了解第三方平臺重新封裝了通知方法,或者後續需求加了定時通知的功能,開發人員針對業務去實現了個定時通知功能,但是隻能自己業務上使用,其他業務無法接入,沒有人去做這塊功能的抽離,久而久之就演變成功能重複開發,且不易於維護和擴充
After
接觸到這種可以抽離通用服務需求的時候,就會與產品確認這種需求是否後續會存在類似的需要,然後建議這把塊需求抽離成通用服務,方便後續維護和擴充
設計概要
Before VS After
架構獨立服務
專案開發過程中有些需求是與所在專案業務無關,如:收集使用者行為習慣,收集商品曝光點選,資料收集提供給BI進行統計報表輸出,公用拉新促活業務(柚子街和返還公用),類似這種需求,我們結合應用場景,考慮服務的獨立性,以及未來的擴充需要,架構獨立專案進行維護,在伺服器上獨立分散式部署不影響現有主業務伺服器資源
專案中實踐例子:
架構使用者行為跟蹤獨立服務,在開發前預估了下這個服務的請求量,並會有相對大量的併發請求
架構方案:
- 專案搭建選擇用nodejs來做服務端
- 單程式,基於事件驅動和無阻塞I/O,所以非常適合處理併發請求
- 負載均衡:cluster模組/PM2
- 架構nodejs獨立服務
- 提供服務介面給客戶端
- 介面不直接DB操作,保證併發下的穩定性
- 資料非同步入庫
- 通過程式把資料從:訊息佇列=>mysql
- nodejs+express+redis(list)/mq+mysql
使用者行為跟蹤服務的服務架構圖
高併發優化
高併發除了需要對伺服器進行垂直擴充套件和水平擴充套件之外,作為後端開發可以通過高併發優化,保證業務在高併發的時候能夠穩定的執行,避免業務停滯帶來的損失,給使用者帶來不好的體驗
快取:
- 服務端快取
- 記憶體資料庫
- redis
- memcache
- 方式
- 優先快取
- 穿透DB問題
- 只讀快取
- 更新/失效刪除
- 優先快取
- 注意
- 記憶體資料庫的分配的記憶體容量有限,合理規劃使用,濫用最終會導致記憶體空間不足
- 快取資料需要設定過期時間,無效/不使用的資料自動過期
- 壓縮資料快取資料,不使用欄位不新增到快取中
- 根據業務拆分散式部署快取伺服器
- 記憶體資料庫
- 客戶端快取
- 方式
- 客戶端請求資料介面,快取資料和資料版本號,並且每次請求帶上快取的資料版本號
- 服務端根據上報的資料版本號與資料當前版本號對比
- 版本號一樣不返回資料列表,版本號不一樣返回最新資料和最新版本號
- 場景:
- 更新頻率不高的資料
- 方式
服務端快取架構圖
非同步
非同步程式設計
- 方式:
- 多執行緒程式設計
- nodejs非同步程式設計
- 場景:
- 參與活動成功後進行簡訊通知
- 非主業務邏輯流程需要的操作,允許非同步處理其他輔助業務,等
業務非同步處理
- 方式
- 業務介面將客戶端上報的資料PUSH到訊息佇列(MQ中介軟體),然後就響應結果給使用者
- 編寫獨立程式去訂閱訊息佇列,非同步處理業務
- 場景:
- 大促活動整點搶限量紅包
- 參與成功後委婉提示:預計X天后進行紅包發放
- 併發量比較大的業務,且沒有其他更好的優化方案,業務允許非同步處理
- 大促活動整點搶限量紅包
- 注意:
- 把控佇列消耗的進度
- 保證冪等性和資料最終一致性
- 缺陷:
- 犧牲使用者體驗
【業務非同步處理】架構圖
【業務非同步處理】除了可以在高併發業務中使用,在上面通用服務的設計裡也是用這種架構方式
限流
在類秒殺的活動中通過限制請求量,可以避免超賣,超領等問題
高併發的活動業務,通過前端控流,分散請求,減少併發量
- 服務端限流
- redis 計數器
- 如:類秒殺活動
- 客戶端控流
- 通過參與活動遊戲的方式
- 紅包雨/小遊戲,等方式
服務降級
當伺服器資源消耗已經達到一定的級別的時候,為了保證核心業務正常執行,需要丟卒保車,棄車保帥,服務降級是最後的手段,避免伺服器當機導致業務停滯帶來的損失,以及給使用者帶來不好的體驗
- 業務降級
- 從複雜服務,變成簡單服務
- 從動態互動,變成靜態頁面
- 分流到CDN
- 從CDN拉取提前備好的JSON資料
- 引導到CDN靜態頁面
- 停止服務
- 停止非核心業務,並進行委婉提示
高併發優化概要圖
防刷/防羊毛黨
大多數公司的產品設計和程式猿對於推廣活動業務的防刷意識不強,在活動業務設計和開發的過程中沒有把防刷的功能加入業務中,給那些喜歡刷活動的人創造了很多的空子 等到你發現自己被刷的時候,已經產生了不小的損失,少則幾百幾千,多則幾萬
隨著利益的誘惑,現在已經浮現了一個新的職業“刷客”,專業刷網際網路活動為生,養了N臺手機+N個手機號碼+N個微信賬號,刷到的獎勵金進行提現,刷到活動商品進行低價轉手處理,開闢了一條新的灰色產業鏈
我們要拿起武器(程式碼)進行自我的防禦,風控,加高門檻,通過校驗和限制減少風險發生的各種可能性,減少風險發生時造成的損失
這裡列出常用套路(具體應用結合業務場景):
校驗請求合法性
- 請求引數合法性判斷
- 請求頭校驗
- user-agent
- referer
- ... ...
- 簽名校驗
- 對請求引數進行簽名
- 裝置限制
- IP限制
- 微信unionid/openid合法性判斷
- 驗證碼/手機簡訊驗證碼
- 犧牲體驗
- 自建黑名單系統過濾
業務風控
- 限制裝置/微信參與次數
- 限制最多獎勵次數
- 獎池限制
- 根據具體業務場景設計... ...
應對角色
- 普通使用者
- 技術使用者
- 專業刷客
- 目前還沒有很好的限制方式
防刷/防羊毛黨套路概要圖
附加
- APP/H5中籤名規則應該由客戶端童鞋開發,然後擴充API給前端JS呼叫,在H5發起介面請求的時候呼叫客戶端擴充的簽名,這樣可以避免前端JS裡構造簽名規則而被發現破解
併發問題
多操作
- 場景:
當==同使用者==多次觸發點選,或者通過模擬併發請求,就會出現多操作的問題,比如:簽到功能,一天只能簽到一次,可以獲得1積分,但是併發的情況下會出現使用者可以獲得多積分的問題
- 剖析:
簡化簽到邏輯一般是這樣的:
查詢是否有簽到記錄 --> 否 --> 新增今日簽到記錄 --> 累加使用者積分 --> 簽到成功
查詢是否有簽到記錄 --> 是 --> 今日已經簽到過
假設這個時候使用者A併發兩個簽到請求,這時會同時進入到 【查詢是否有簽到記錄】,然後同時返回否,就會新增兩條的簽到記錄,並且多累加積分
- 解決方案:
最理想簡單的方案,只需要在簽到記錄表新增【簽到日期】+【使用者ID】的組合唯一索引,當併發的時候只有會一條可以新增成功,其他新增操作會因為唯一約束而失敗
庫存負數
- 場景:
當==多使用者==併發點選參與活動,如:抽獎活動,這個時候獎品只有一個庫存了,理論上只有一個使用者可以獲得,但是併發的時候往往會出現他們都成功獲得獎品,導致獎品多支出,加大了活動成本
- 剖析:
有問題的邏輯流程一般是這樣的:
中獎 --> 查詢獎品庫存 --> 有 --> 更新獎品庫存 --> 新增中獎紀錄 --> 告知中獎
中獎 --> 查詢獎品庫存 --> 無 --> 告知無中獎
假設抽獎活動,當前獎品A只有最後一個庫存,然後使用者A、B、C,同時參與活動同時中獎獎品都是A,這個時候查詢商品庫存是存在1個,就會進行更新庫存,新增中獎紀錄,然後就同時中獎了
- 解決方案:
最理想根本就不需要用多做一個庫存的SELECT獎品庫存操作,只需要UPDATE 獎品庫存-1 WHERE 獎品庫存>=1,UPDATE成功後就說明是有庫存的,然後再做後續操作,併發的時候只會有一個使用者UPDATE成功
總結:
在開發業務介面的時候需要把==同使用者==和==多使用者==併發的場景考慮進去,這樣就可以避免在併發的時候產生資料異常問題,導致成本多支出
可以使用下面的工具進行模擬併發測試:
- Apache JMeter
- Charles Advanced Repeat
- Visual Studio 效能負載
資料採集技巧(番外)
普遍方案
- 獲取平臺資料介面
- 模擬介面請求
- 資料解析過濾
- 資料構造入庫
使用selenium+Headless自動化測試框架
- 開發
- 推薦用python開發
- python+selenium+headless
- 控制請求頻率,避免被平臺限制請求
- 使用代理IP,繞過請求IP限制
- 推薦用python開發
- 優點
- 無需模擬介面請求
- 無法攻克資料介面模擬請求(加密簽名等)
- 介面版本頻繁變化(需要重新調研)
- 平臺介面/頁面版本變化,可以快速調整
- 只需要調整採集資料所在的HTML元素的位置(class/id)
- 可以使用者操作/選中/點選/模擬登陸,等
- 登陸失效後可以模擬登陸
- 可以傳送登陸二維碼到釘釘進行掃碼登入
- 無需模擬介面請求
- 應用場景:
- 競品資料採集
- 淘寶商品價格和自建商品庫後臺價格監控
- 淘寶領券金額和自建商品庫後臺券金額監控
- ... ...
反反爬蟲
在做資料採集的過程中,有些平臺會對重要資料的請求設定反爬蟲策略,避免資料被競品挖掘和利用,以及消耗大量資源拖垮伺服器, 反爬蟲和反反爬蟲是技術之間的較量,這場沒有硝煙的戰爭永不停息。(程式設計師何必為難程式設計師)
-
反爬蟲可以分為以下兩種
- 服務端限制
- 伺服器端行請求限制,防止爬蟲進行資料請求
- 前端限制
- 前端通過CSS和HTML標籤進行干擾混淆關鍵資料,防止爬蟲輕易獲取資料
- 服務端限制
-
破解服務端限制:
- 模擬設定請求頭
- Referer
- User-Agent
- Authorization
- .....
- 破解簽名
- 簽名規則
- 在JS中找到簽名規則
- 簽名規則
- 控制請求平率
- 調整請求時間,延遲請求
- 代理IP
- 切換請求的代理IP,自建/第三方
- 登入限制
- 帶上登入成功後的Cookie/Authorization
- 驗證碼限制
- 識圖,基於庫/第三方
- 投毒破解
- 為了防止被投毒,需要對資料進行抽樣校驗
- 模擬設定請求頭
-
破解前端限制:
- font-face,自定義字型干擾
- 找到ttf字型檔案地址,然後下載下來,使用font解析模組包對ttf檔案進行解析,與文字編碼進行對映出中文
- 偽元素隱藏式
- 在CSS裡找到xxxx::before {content: "中文";}對應的中文
- backgroud-image移量
- 通過背景圖片的position位置偏移量和圖片中的內容進行對映
- html標籤干擾
- 過濾掉干擾混淆的HTML標籤,或者只讀取有效資料的HTML標籤的內容
- font-face,自定義字型干擾
總結
作為後端開發者,不僅是完成需求功能開發,要結合業務場景進行合理設計,架構未來,對核心業務進行壓測優化,以保證業務在併發下能夠正常執行,同時要考慮安全問題以及防刷,防羊毛黨,在編碼上避免壞程式碼味道,面相抽象開發,適當使用設計模式,避免技術債
開發應該銘記於心的精句:
- 技術的存在價值,是讓技術推動業務增長,實現公司盈利增長
- 沒有最好的架構只有最適合的架構
- 開發語言只是工具,在適合的場景中使用適合的工具
- 抽象思維是從具體存在的各不相同的問題當中洞察問題的本質,理解產品需求的深層次模型,治本而不是治標
- 知識很重要,她雖然不能直接給你財富,但是可以給你很多機會,活到老學到老