滴滴出行架構大神分享:大型微服務框架設計實踐
大綱
• 發現問題:服務開發過程中的痛點
• 以史鑒今:從服務框架的演進歷程中找到規律
• 大道至簡:大型微服務框架的設計要點
• 精雕細琢:框架關鍵實現細節
復雜業務開發過程中的痛點
痛點
• 時間緊、任務多、團隊大、業務增快,如何還能保證架構穩定可靠?
• 研發水平參差不其、項木壓力自顧不暇,如何保證質量基線不被突破?
• 公司有各種⼯具平臺、 SDK、優秀實踐,如何盡可能的在業務中使用?
•用什么“框架”可以解決問題?
從服務框架的演進歷程中找到規律
讓我們先來看下服務框架的進化史
標志性的服務框架
Web 服務框架: MVC 架構
• ASP.Net(since 2002):傳統 C/S 開發模式在 Web 上的應⽤
• Ruby on Rails(since 2005): MVC 框架的巔峰, “約定⼤于配置”
• Web 服務框架: SaaS 與 RESTful
• Sinatra(since 2007):純路由框架,諸多框架的靈感源泉
• 微服務框架: RPC 服務
• Thrift(since 2007):開源 IDL-based 框架的⿐祖
• 微服務架構:容器化與 FaaS
• Serverless(since 2015):基于云計算平臺,回歸框架本質
• Istio(since 2018):專注于解決網絡問題
服務框架的演進趨勢
服務框架正在演變成新的“操作系統”
• 學習曲線: Exponential Rise(漸進式) → Sigmoid(階躍式)
• 風格:配置 → 約定 → DSL → 容器化
• 業務代碼與框架代碼的關系: Is-a → Has-a → Duck-typing
• 工具鏈: IDE → 代碼⽣成器 → 編譯器
大型微服務框架的設計要點
站在全局視⻆觀察微服務架構
大型微服務框架的設計目標
框架即一款面向開發人員的效率產品,基于公司的基礎設施量身定制
• 目標用戶:來不不同背景、具有基本業務研發能⼒的開發者
• 設計要點:讓開發人員專注于業務開發本身,無需關注滴滴各種基礎設施底層細節
• 設計原則:直觀、簡潔、智能、個性化
• 預期收益:提升⼈效,降低維護成本;提升整體架構穩定性和可伸縮性;簡化技術升級難度
大型微服務框架的設計要點
完全屏蔽業務無關的通用技術細節
• 功能:服務治理、虛擬化、水平擴容、問題定位、性能壓測、系統監控、兼容遺留系統……
• 工具鏈:項目模板、代碼生成器、文檔生成器、發布打包腳本……
• 設計⻛格: Interceptors、組合模式、依賴注入……
• 讓不可靠的調⽤變得可靠
• RPC 調用 ≈ 函數調用
• 訪問基礎服務 ≈ 訪問本地存儲
• 服務拆分/合并 ≈ 類拆分/合并
框架關鍵實現細節
業務實踐
業務背景:復雜的業務流程,快速增漲與迭代,異構服務架構,跨國多機房部署
• 核心能力
• 隔離層封裝:各種存儲、隊列、平臺服務封裝
• 透明支持各種運維基礎設施:構建、發布、多機房配置、 metrics
• 提供效率和測試⼯具:⽇志采集、問題⾃動跟蹤、全鏈路壓測、 mock、接⼝測試
• 應⽤層協議隔離:⽀持 thrift/http 協議 interceptor
• 工具鏈:模板、代碼⽣成器、依賴管理、版本管理、發布腳本
站在巨人肩膀上:滴滴基礎平臺建設現狀
• Odin:運維平臺,提供 metrics 上報、多維度監控、報警、服務樹等功能
• 把脈:日志平臺,提供日志采集通道、基于 traceid 的全鏈路⽇志查詢能⼒
• DiSF:服務注冊平臺,提供高可用的服務名字服務、管理服務分組
• RDS:提供高可用、透明水平擴展的 MySQL 集群,支持數據總線、分身等能力
• DDMQ:低延遲高可用的消息隊列服務,單機 TPS 吞吐超過百萬,支持延時消息
• Fusion:基于 rocksdb 的高性能高可用分布式持久化存儲方案,完全兼容 Redis 協議
• 彈性云:基于 k8s,高效、可伸縮的集群管理平臺,服務自動容錯,基礎設施免運維
整體架構
實現要點:框架與業務正交
實現思路
• 傳統框架的 MVC、 middleware、 AOP、執行流程……都不存在也不需要
• 框架是一個執行環境,由一堆不關聯的基礎庫組成高度可擴展,業務可獨立于框架運行
如何實現
• 提供工具鏈,用于生成最初的項⽬模板并通過代碼生成器實現類似 AOP 的效果
• 基于 Go interface 的 duck-typing 特性和運行時反射,動態生成業務路由
收益
• 業務開發⽆需關注框架本身
• 框架本身的升級可以做到完全透明,方便所有服務統一框架版本
框架的啟動邏輯
實現要點:隔離層屏蔽業務與底層的聯系
如何實現
• 為所有基礎服務(mysql/redis/mq/es/...)定義 interface,業務只允許調用 interface 的方法
• 基于 SPI 設計思路,提供基礎服務的工廠,動態實例化對應 interface
收益
• 可透明的升級服務驅動,快速在大量服務中實現共性邏輯或者修復共性問題
• 透明的管理基礎服務的資源(長連接、 mysql cursor 等),避免出現資源泄露
• 統⼀控制重試、超時、服務發現、故障摘除邏輯,業務⽆感知且不易出錯,提升整體穩定性
• 對所有基礎服務提供了 mock 能力,可以實現 AOP 能力
案例: Redis 接口設計
實現要點:協議劫持
如何實現
• HTTP 協議:包裝 http.Handler,用責任鏈模式處理 http.Request 和 http.ResponseWriter
• RPC 協議:劫持協議序列化流程,用FSM(有限狀態機)來跟蹤序列化過程并適時修改數據
收益
• 將業務數據和服務框架數據充分隔離,避免互相⼲擾
• 實現接口熱補丁和 in/out 數據錄制與重放,方便測試
• 可透明的增強服務能力,為實現跨服務邊界的 context 打好基礎
使用FSM 劫持 thrift protocol
FSM 實現思路
• 利用 Go interface 特性,實現一個 interfaceproxy,代理并劫持部分感興趣的接口
• 維護一個 FSM 狀態機,當 protocol read/write走到感興趣的地⽅時候篡改 read/write 數據
實現要點:跨服務邊界的 context
如何實現
• 實現符合 context.Context 接口的自定義 context,支持序列化與反序列化,支持超時控制
• 結合協議劫持,透明的從服務框架數據中提取必要信息進行反序列化,并在所有 RPC 調用前
透明的將最新 context 序列化并放在服務框架數據中傳輸給下游
• 需要重新實現一個基于時間片輪轉的低精度 time.Timer,提升并發效率并避免 timer 泄露
收益
• 可透明的在服務間傳遞上下文信息,從而實現流量染色、調用跟蹤、防雪崩等功能
低精度 timer 實現原理
實現要點:防雪崩
如何實現
• 通過跨服務邊界的 context 來傳遞上游超時預期,并不斷記錄各個環節耗時
• 一旦框架發現自己的可用時間已經耗盡,主動終止后續 rpc 調⽤并快速返回,防止請求積壓
收益
• 從根本上避免請求堆積造成的雪崩
跨服務邊界的超時時間控制
超時信息如何跨服務邊界傳遞
• 超時時間由最上游設置,框架捕捉到超時信息并將時間記錄在 trace 里透明的傳播到下游每一個服務節點
• 每個節點從接收到請求開始后計時,自動計算自己消耗的時間并計算當前調用鏈路總耗時
• 當鏈路總耗時超過超時時間,自動 fail-fast,快速返回失敗信息
• 利⽤ Go context deadline 只會縮短不會提前的特性,方便的用 context 管理超時
• 避免服務器之間的時鐘差異影響計時,始終使用時間差來記錄號是,而不使用絕對 deadline
業務收益
支撐規模
• 涉及開發人員近 100 人,上線 70+ 服務,國內外雙機房部署
• 可支撐百萬級日訂單規模、萬級并發長連接
業務收益
• 零成本:透明接⼊公司運維、發布、日志、壓測等平臺,支持服務注冊發現、彈性伸縮等能力
• 零事故:從未出現因為單點故障造成的全局穩定性事故
• 高質量:快速實現全鏈路壓測常態化,透明實現多環境部署、單測、集成測試等
• 易維護:透明升級各種 driver,簡化代碼依賴管理過程
未來計劃
提升開發者體驗
• 命令行工具,用于整合各種工具
• 進一步與滴滴線上線下環境整合
• 整合更多的公司服務和框架
• 配置管理中⼼化
• 開源?