Node.js 源站應用穩定性保障
源站是 CDN 技術中的一部分,是發布內容的原始站點。CDN 負責承載流量的部分稱做緩存服務器,而緩存服務器自身不生產內容,需要從源站獲取原始內容。Dragonfly 作為淘寶內容管理系統(CMS)的源站,渲染并為緩存服務器提供了所有的頁面內容。
Dragonfly 使用 Node.js 開發,穩定性保障是一邊實踐探索、一邊總結經驗。現在來回顧,穩定性保障涉及了 Dragonfly 完整的開發運維的生命周期。因此本文依次從設計、實現、驗收、運維四個環節展開。
系統設計
從設計入手,我們分別梳理了 Dragonfly 在淘寶 CMS 生態圈的拓撲圖和源站內部流程的草圖。
源站的外部環境比較簡單。前面對接 CDN,實現流量承載和核心頁面的容災,穩定性很高。后面主要對接 Redis 緩存,用于獲取頁面的各種資源。(圖中的 配置中心 和 FileSync,分別用于獲取配置和共享模板片段,為弱依賴)
TMS 支持多終端頁面的投放,因此 CDN 需要有識別終端的能力,Dragonfly 為此做了相關的處理。梳理前,源站的內部流程如下(Dragonfly 使用了 Koa.js,其中間件執行流程為回形針型——正序進,逆序出):
這里我們注意到:
- 沒有輸入過濾模塊,為保證環境一致,用戶 Query 需要統一丟掉。
- 獲取頁面入口模塊依賴 Redis 這樣欠穩定的系統,但是并沒有納入容災備份的流程。
- 沒有統一的異常處理模塊,容災模塊只作異常檢測,并為妥善處理異常。
為此我們先在設計上進行了如下調整:
依據新的設計,我們確認了依賴不穩定系統的模塊都能容災模塊覆蓋。同時根據這張草圖,我么確認了需要在實現、運維環節檢查的內容:
- 確認外部依賴系統都有容錯策略。
- 確認內部錯誤拋出都做了正確的日志記錄,依據自身場景作恰當的容災或其它異常處理。
- 確認監控腳本被正確的配置。
系統實現
依據系統設計環節的評審成果,我們開始評審系統實現部分。
外部依賴容災保障
基本原則:
- 關鍵鏈路依賴的外部系統越少越好,外部依賴一定要有詳細的容災策略和預案。
- 依賴也包括第三方模塊,應該使用最新穩定版本。過期版本易出現 BUG 無人解決、集成困難、性能差等問題。
Dragonfly 外部依賴的具體保障:
CDN/源站
- CDN,根據自身特點有大量節點,依賴專業運維團隊的維護,部分節點異常不影響可用性。
- 源站異常,CDN 使用過期的副本。
終端探測
- 終端探測模塊使用 UA 進行判斷。遇到未知設備,可能出現判斷錯誤,因此系統提供了強制切換參數。
配置中心
- 配置中心即 Dragonfly 使用的配置推送系統。設計上,配置中心從服務端至客戶端有多級容災。
- 另外 Dragonfly 還在源碼中做了一份本地容災。
FileSync
- 文件同步系統 FileSync 維護了 CMS/應用 共用的前端代碼片段。
- 推送后存在本地,遇異常可本地容災、手工更新。
Redis
- Redis 性能不錯,但受網絡影響,Dragonfly 實際使用時超時較多。我們做了大量測試,發現主要原因為 Redis 傳輸小數據較多,因此確保 TCP 連接做了 Keep-Alive、關閉 Nagle 算法、關閉 Delay ACK 優化后,性能得到了很大改善。
- Redis 是數據緩存,Dragonfly 讀取數據以 Redis 為主,另外使用了 Aliyun OSS 作為備份數據源。在 Redis 異常、超時時使用 Aliyun OSS。
- Redis、Aliyun OSS 皆異常時頁面將無法更新,源站啟用本地容災。
內部容錯保障
基本原則有:
- 規范異常格式、拋出方式,進行統一處理是容災保障的基礎。
- 關鍵系統資源遇到瓶頸,要有降級策略。
Dragonfly 的具體處理方式:
上下文異常處理
- 發生后需要做日志記錄,使用靜態副本容災。
未捕獲的異常
- 寫日志觸發報警,重啟 Worker 進程。
實時備份
- 每個頁面請求,每 10s 生成一次靜態容災副本寫入硬盤。
內存監控
- 渲染過程中會產生大量緩存、臨時字符串,給垃圾回收帶來了很大壓力。
- 內存占用過高,無法及時回收時,需要強制重啟 Worker 進行回收。
- 更多優化方案持續進行。
過載降級
- 壓力過高時,Dragonfly 會收到 nginx 提供的 Over-Load 頭,此時直接返回靜態副本。
靜態開關
- 與開發團隊交流學到的手動降級方案。用于應付未知 BUG 導致的大批頁面異常。
測試(驗收)
系統設計時也要一并思考如何測試。優秀的設計應該是易于測試的。
單元測試
充分的單元測試是持續重構的保障。Dragonfly 的單元測試細節本文就不展開了,這里給出筆者的一些總結:
- 單元測試不拖累開發效率,反而是持續高效開發的保障。
- 測試覆蓋可以驗證代碼和測試質量,幫助我們找到潛在的缺陷。
- 單元測試設計要充分,從程序的基本單元入手,需求變更時必須及時更新。
- 單元測試應保持獨立性,每項測試不依賴其它測試,產生可覆蓋、一致的結果。這里 Mock 是項很有用的技術。
功能測試
單元測試保障了每個模塊的質量。對于整個系統而言,功能測試是確定是否實現用戶需求的有效方法。
功能測試的實現與單元測試大同小異。要特別說明的是,對于容災模塊,除了功能測試,我們還做了線上演練。
性能測試
主要利用壓測平臺模擬真實用戶訪問,頁面使用線上機器抓取的真實地址。出現明顯性能下滑的變更不能發布。
持續集成
既然測試是質量的保障,我們應該把測試自動化。選擇一個成熟的持續集成方案吧。
日志與監控(維護)
日志
日志是用于監控和排查問題的。應以監控和問題排查者的角度記錄。做到統一格式,按模塊分類記錄,集中管理。筆者理解的日志分類為:
診斷日志
- 例如源站會按 config/redis/xtemplate 具體分類記錄。
統計日志
- 例如源站的 QPS/RT 等訪問日志統計。
審計日志
- 例如用戶操作日志。
日志是需要定時維護的,這里依據 Dragonfly 的日志維護經驗給出總結:
- 無用日志必須清理。
- 設計日志時思考如何方便故障排查者。
- 排查故障后,應反思日志是否合理,并及時完善日志。
- 給一臺線上機器實現詳細 Debug 日志的開關,用于復雜問題的排查。
監控
有日志無監控等于沒做,部分監控經驗:
- 監控信息要便于快速解決問題。
- 監控要依據運維經驗調整合理,誤報過多容易導致麻痹。
總結
千里之堤,潰于蟻穴。系統穩定性需要持續的點滴積累,疏忽任何一個細微環節都可能給系統帶來巨大的風險。但是依賴合理的規劃、嚴格的驗收標準、持續完善的監控,穩定性保障并不困難。
來自: http://taobaofed.org/blog/2016/01/05/dragonfly-stability/