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