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,自定義字體干擾
總結
作為后端開發者,不僅是完成需求功能開發,要結合業務場景進行合理設計,架構未來,對核心業務進行壓測優化,以保證業務在并發下能夠正常運行,同時要考慮安全問題以及防刷,防羊毛黨,在編碼上避免壞代碼味道,面相抽象開發,適當使用設計模式,避免技術債
開發應該銘記于心的精句:
- 技術的存在價值,是讓技術推動業務增長,實現公司盈利增長
- 沒有最好的架構只有最適合的架構
- 開發語言只是工具,在適合的場景中使用適合的工具
- 抽象思維是從具體存在的各不相同的問題當中洞察問題的本質,理解產品需求的深層次模型,治本而不是治標
- 知識很重要,她雖然不能直接給你財富,但是可以給你很多機會,活到老學到老
aixin
歡迎star:《大話WEB開發》 Github