微服務改造那些痛!蘇寧數(shù)據(jù)中臺基于Spring Cloud架構實踐
原創(chuàng)【51CTO.com原創(chuàng)稿件】從單體程序到微服務,再到當下流行的服務網格概念,Spring 連接起了這兩個時代。它曾是單體程序的代名詞,但是卻在微服務時代浴火重生,給我們帶來了 Spring Cloud。
借助于 Spring Cloud,蘇寧大數(shù)據(jù)中心完成了微服務架構轉型,在實踐中并不是一帆風順,有思索、有迷茫,更有解決問題的樂趣。
為什么要微服務化?
為什么是 Spring Cloud?
蘇寧數(shù)據(jù)中臺后端是傳統(tǒng)的開發(fā)架構, VIP 負載均衡 + Nginx + SpringMVC,代碼以單體程序為主。
正常情況下一個項目使用統(tǒng)一域名,在蘇寧現(xiàn)有開發(fā)架構下,統(tǒng)一域名導致后端只能有一個 war 包,程序變成單體程序變成必然。
如下圖所示是典型的舊式項目代碼目錄:
- 項目名稱-web:對外 war 包模塊
- 項目名稱-interface:統(tǒng)一定義接口
- 項目名稱-service:統(tǒng)一定義接口實現(xiàn)
整個項目管理、開發(fā)思路,圍繞單體程序開發(fā)模型設計,帶來的弊端很明顯:
- 代碼職責不清晰,每個人都在同一模塊下提交代碼
- 違反高內聚低耦合
- 服務擴展不方便
首先微服務化思路,并不高大上,我們?yōu)槭裁催x擇微服務化,首要原因是管理問題。
結合蘇寧現(xiàn)有開發(fā)架構,整個微服務架構如圖:域名解析 + VIP 負載均衡 + Nginx + 服務網關 + 各個服務。
下圖是某個項目采用微服務化后的工程代碼目錄:
- 項目名稱-模塊1
- 項目名稱-模塊2
整個代碼目錄更清晰,利于模塊拆分、人員職責安排。
數(shù)據(jù)中臺項目背景介紹
蘇寧數(shù)據(jù)中臺是一個大項目群:
- OLAP 是底層的加速、查詢引擎,底層支持 Druid、ES、PGCitus 集群,類似 Presto,跟 Presto 不同的是 OLAP 會主動對數(shù)據(jù)進行 Cube 預加速。
- 百川是指標平臺層,讓用戶建模、定義指標,對外提供指標查詢服務。百川主要支持的建模方式是:星型模型。
- 數(shù)據(jù)建模自然離不開維表維度,UDMS 系統(tǒng)就是來定義、管理所有維度、維表,目前收錄了整個集團近 200 多個維度,對外提供維度、維表信息服務。
- 天工是類似 Tableau、Superset 的可視化報表設計平臺,與這些 BI 軟件***的不同點是,天工基于百川的指標、UDMS 的維度來制作報表,數(shù)據(jù)來源已經高度標準化、歸一化。
目前商業(yè)報告分析工具:Cognos、阿里 QuickBI 等,是將數(shù)據(jù)建模、可視化設計能力放到一起,這是天工與它們的***區(qū)別。
- 慧眼,是統(tǒng)一報表門戶,所有的報表統(tǒng)一發(fā)布到慧眼面向業(yè)務?;垩?**的挑戰(zhàn)在于報表權限管控與自動匹配,總共 4000 多張報表,用戶 2w 多,一張報表開放給8000+人員是很常見的。
所有這一切靠人工維護,既容易出錯又不利于數(shù)據(jù)安全,也不能及時響應用戶需求,這些都是慧眼系統(tǒng)要解決的問題。
微服務框架選型
Dubbo 架構介紹
Dubbo 主要有四個模塊:
- Monitor(監(jiān)控)
- Regsitry(注冊中心)
- Provider(服務方)
- Consumer(消費方)
Provider 注冊服務到 Regsitry,Consumer 向 Regsitry 訂閱服務信息,Monitor服務監(jiān)控服務調用情況。
整個服務調用流程如下:
- 消費方在本地發(fā)起服務調用
- 動態(tài)代理將調用交給 Loadbalance 模塊
- Loadbalance 從 Registry 拿到服務實例信息
- 將請求發(fā)送到一臺服務實例
- 記錄監(jiān)控日志等信息
Spring Cloud 架構介紹
Spring Cloud 整個架構與 Dubbo 非常類似:
- Eureka(注冊中心)
- Gateway(服務網關)
- Provider(服務方)
- Consumer(消費方)
- Zipkin(監(jiān)控)
不同的有如下幾點:
- Spring Cloud 是 Http Rest 接口,Dubbo 不是。
- Spring Cloud 注冊中心不使用 Zookeeper,使用自研的 Eureka。
關于 Zookeeper 是否適合做注冊中心,請參考文章:《Eureka! Why You Shouldn’t Use ZooKeeper for Service Discovery》、《阿里巴巴為什么不用 ZooKeeper 做服務發(fā)現(xiàn)》
- Spring Cloud 提供了 Gateway 網關組件。
- 與 Spring 生態(tài)兼容,生態(tài)鏈豐富,自定義 Filter、攔截器,來加強功能, 如:權限校驗、日志打印等;Spring Cloud Netflix 提供了熔斷、限流等組件。
綜合以上幾點,考慮到架構統(tǒng)一,未來發(fā)展趨勢,我們選擇了 Spring Cloud。
Spring Cloud 主要幫助我們做系統(tǒng)內部服務化,REST 接口形式,不會破壞現(xiàn)有服務,前端服務調用無需做任何調整。
選擇 Spring Cloud 還有一個重要原因是 Dubbo 與蘇寧 RSF 服務框架高度重合,在對外服務接口上,我們還是以 RSF 接口為主。
基于 Spring Cloud 的服務化實踐
整體架構介紹
整體有幾個組件:注冊中心、服務網關、服務監(jiān)控、負載均衡器。注冊中心使用 Spring Cloud 提供的 Eureka,服務網關使用 Spring Cloud 提供的 Zuul 組件,負載均衡器使用 Ribbon 組件。
服務網關的負載均衡策略選擇的是:WeightedResponseTimeRule,根據(jù)服務器響應時間來決定路由到哪個節(jié)點。
服務監(jiān)控組件,用來監(jiān)控服務性能、調用情況,最重要的一點,是將整個服務鏈路能串聯(lián)起來。
服務監(jiān)控設計
監(jiān)控是一個系統(tǒng)的眼睛,是斷然不可缺少的一部分,Zipkin 提供了很好的服務鏈路監(jiān)控,結合我們自身的使用場景,最終我們沒有選擇 Zipkin,為什么?
首先了解下 Zipkin 整體架構:
- 數(shù)據(jù)采集(Brave、Sleuth)
- Tranport 數(shù)據(jù)傳輸(支持 Kafka、直接發(fā)送 Collector)
- Collector(數(shù)據(jù)收集)
- Storage(存儲:ES)
- Search + Webui(監(jiān)控展示)
整體架構如下圖所示,Zipkin 監(jiān)控數(shù)據(jù)格式如下:
Zipkin 有如下缺點:
- 我們不止是監(jiān)控 Spring Cloud 服務調用,如:蘇寧 RSF 服務調用、SQL 的執(zhí)行時間、本地方法執(zhí)行時間等。
- 監(jiān)控數(shù)據(jù)格式不滿足業(yè)務需要。
- Collector 節(jié)點容易出現(xiàn)性能瓶頸,ES 聚合查詢性能較差。
- 跨線程,鏈路無法串聯(lián)。
基于以上幾點,我們決定自研服務鏈路監(jiān)控系統(tǒng),整個系統(tǒng)架構如下,我們利用 Kafka、Druid,原則上提供了***擴展性。
Druid 對應 Zipkin 中的角色:Collector(數(shù)據(jù)收集) + Storage(存儲:ES)。
我們結合業(yè)務的需要,設計了監(jiān)控日志格式,如下圖所示:
一條調用鏈路,有相同的根 ID,服務名由三部分組成:
- 系統(tǒng)名
- 一級名稱
- 二級名稱
一級名稱有 7 種值:
- url:http 接口
- url-call:調用 http 接口
- rs:rsf 接口
- rsf-call:調用 rsf 接口
- sql:執(zhí)行 sql
- cache:操作緩存
- method:本地方法
你可能會問,沒有存儲父 ID,如何判斷一條鏈路中的父子關系?這里我們設計一個特殊的事務 ID 生成規(guī)則,通過事務 ID 本身即能判斷父子關系,如下圖所示:
下圖監(jiān)控系統(tǒng)的鏈路展示頁面:
基于 Hystrix 的熔斷設計
Hystrix 對應的中文名字是“豪豬”,豪豬周身長滿了刺,能保護自己不受天敵的傷害,代表了一種防御機制,這與 Hystrix 本身的功能不謀而合。
因此 Netflix 團隊將該框架命名為 Hystrix,并使用了對應的卡通形象作為 Logo。
在一個分布式系統(tǒng)里,許多依賴會不可避免的調用失敗,比如超時、異常等。
如何能夠保證在一個依賴出問題的情況下,不會導致整體服務失敗,這個就是 Hystrix 需要做的事情。
Hystrix 提供了熔斷、隔離、Fallback、Cache、監(jiān)控等功能,它能夠在一個、或多個依賴同時出現(xiàn)問題時保證系統(tǒng)依然可用。
使用 Hystrix 很簡單,只需要添加相應依賴即可,最方便的方式是使用注解 HystrixCommand:
- fallbackMethod:指定 Fallback 方法
- threadPoolKey:線程池名稱
- threadPoolProperties:指定線程池參數(shù)(線程池大小、***隊列排隊數(shù)量)
- commandProperties:CIRCUIT_BREAKER 開頭的參數(shù)配置熔斷相關參數(shù),METRICS_ROLLING 開頭的參數(shù)設置指標計算相關參數(shù)
- 相關參數(shù)定義,參考類:HystrixPropertiesManager
- @RestController
- public class HystrixTest {
- @RequestMapping(value = "/query/user/name", method = RequestMethod.GET )
- @HystrixCommand(fallbackMethod = "getDefaultUserName", threadPoolKey = "query_user",
- threadPoolProperties = {
- @HystrixProperty(name = CORE_SIZE, value = "10"),
- @HystrixProperty(name = MAX_QUEUE_SIZE, value = "10")
- },
- commandProperties = {
- @HystrixProperty(name = CIRCUIT_BREAKER_ENABLED, value = "true"),
- @HystrixProperty(name = CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD, value = "1000"),
- @HystrixProperty(name = CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE, value = "25")
- }
- )
- static String getUserName(String userID) throws InterruptedException {
- Thread.sleep(-1);
- return userID;
- }
- public String getDefaultUserName(String userID) {
- return "";
- }
- }
基于服務網關 Zuul 實現(xiàn)的廣播功能
有些時候我們希望 url 請求被所有服務實例執(zhí)行,這里我們對 Zuul 做了一個改造,增加了一個 BroadCastFilter,在 url 請求 header 設置 gate_broadcast 為 true,那么這個請求,將被轉發(fā)給所有服務實例。
邏輯流程如下:
- 判斷 gate_broadcast 參數(shù)為 true
- 從 url 獲取 ServiceId
- 從 Ribbon 獲取服務所有實例
- 將請求發(fā)送給所有實例
- 將所有實例返回結果封裝,返回
微服務帶來的問題
服務拆分粒度不好把握
Spring Cloud 的微服務有一個 ServiceId 的概念,通常一個 war 包對應一個 ServiceId,這個 ServiceId 下可以有多個服務。粒度拆分方式主要有:橫向、縱向。
縱向切分主要有如下幾個方式:
- 按功能切,如用戶管理、指標管理、模型管理等。
- 按角色切,如管理員、商家、用戶。
橫向切分,一般用來提取公共的基礎服務,比如:用戶名密碼校驗服務、用戶基本信息查詢。
運維、開發(fā)復雜度增加
單體程序時代只有一個 war 包,微服務鼓勵服務拆分,war 數(shù)量、部署節(jié)點大大增加。
此外,一個流程處理往往會由多個分布式服務協(xié)同完成,帶來了不少棘手的問題:
- 需要通過分布式事務保障數(shù)據(jù)最終一致性
- 防止單個服務問題造成雪崩
這些都給開發(fā)者提出了更高的要求。
調試難度增加
微服務方式鼓勵服務拆分,通過服務間依賴完成功能,給開發(fā)、測試帶來了挑戰(zhàn),合理選擇微服務、代碼復用兩種方案。
后續(xù)架構演進
服務版本控制
沒有版本控制,意味著我們無法做灰度發(fā)布,毀滅性版本發(fā)布后,無法做到對老版本兼容,下圖為服務 A、B、C、D 間的版本依賴關系:
我們實現(xiàn)思路是對 Zuul 進行改造:
- 打版本標簽,在 Zuul 對訪問來源判斷(比如 App 版本 5.1 對應的查詢接口版本為 2.1),打上版本標簽
- 根據(jù)版本信息,路由到對應版本服務實例
基于 Gateway 的服務熔斷、限流機制
目前有一些開源的框架如 ratelimit,通過在 Ruul 增加 filter 來實現(xiàn)限流熔斷。
但是有幾個問題:
- 不支持動態(tài)配置
- 不能滿足業(yè)務變化,如配合版本控制
綜上所述,我們已經著手一些自研工作,能與我們業(yè)務場景貼合得更緊密。
總結
從 2016 年到現(xiàn)在,兩年的時間里,蘇寧大數(shù)據(jù)中心從傳統(tǒng)的單例開發(fā)模式,切換到基于 Spring Cloud 的微服務開發(fā)模式,并摸索出了一條適合自己的路,這不只是技術框架的切換,更是開發(fā)思維的升級。
令人欣喜的是,這兩年間 Spring Cloud 飛速發(fā)展,2018 年發(fā)布了革命性的 2.0 版本,這離不開社區(qū)的支持,許多像 Netflix 一樣的公司在筆耕不輟地為 Spring Cloud 生態(tài)添磚加瓦。
我們基于 Spring Cloud 開發(fā)出了一些服務于自己業(yè)務的組件,讓我們認識到自己也是有能力有責任去回饋社區(qū)。
路漫漫其修遠兮,好的架構一定是適應業(yè)務發(fā)展的架構,對于 Spring 這不是終點,對于我們更不是。
作者:王富平
簡介:蘇寧易購大數(shù)據(jù)中心數(shù)據(jù)中臺技術負責人,歷任百度大數(shù)據(jù)部高級工程師、1 號店搜索與精準化部門架構師。多年來,一直從事大數(shù)據(jù)方向的研發(fā)工作,對大數(shù)據(jù)工具、機器學習有深刻的認知,在實時計算領域經驗豐富,對 Storm、Spark Streaming 有深入了解。熱愛分享和技術傳播,目前關注數(shù)據(jù)分析平臺的建設,旨在打通數(shù)據(jù)建模到數(shù)據(jù)分析,基于 Druid、Kylin 等 OLAP 技術,提供一個平臺級別的數(shù)據(jù)指標服務,打造"數(shù)據(jù)即服務"的一站式解決方案。
【51CTO原創(chuàng)稿件,合作站點轉載請注明原文作者和出處為51CTO.com】