微服務改造那些痛!蘇寧數據中台基於Spring Cloud架構實踐

從單體程序到微服務,再到當下流行的服務網格概念,Spring 連接起了這兩個時代。它曾是單體程序的代名詞,但是卻在微服務時代浴火重生,給我們帶來了 Spring Cloud。

微服務改造那些痛!蘇寧數據中臺基於Spring Cloud架構實踐

藉助於 Spring Cloud,蘇寧大數據中心完成了微服務架構轉型,在實踐中並不是一帆風順,有思索、有迷茫,更有解決問題的樂趣。

為什麼要微服務化?

為什麼是 Spring Cloud?

微服務改造那些痛!蘇寧數據中臺基於Spring Cloud架構實踐

蘇寧數據中臺後端是傳統的開發架構, VIP 負載均衡 + Nginx + SpringMVC,代碼以單體程序為主。

正常情況下一個項目使用統一域名,在蘇寧現有開發架構下,統一域名導致後端只能有一個 war 包,程序變成單體程序變成必然。

如下圖所示是典型的舊式項目代碼目錄:

  • 項目名稱-web:對外 war 包模塊
  • 項目名稱-interface:統一定義接口
  • 項目名稱-service:統一定義接口實現
微服務改造那些痛!蘇寧數據中臺基於Spring Cloud架構實踐

整個項目管理、開發思路,圍繞單體程序開發模型設計,帶來的弊端很明顯:

  • 代碼職責不清晰,每個人都在同一模塊下提交代碼
  • 違反高內聚低耦合
  • 服務擴展不方便

首先微服務化思路,並不高大上,我們為什麼選擇微服務化,首要原因是管理問題。

結合蘇寧現有開發架構,整個微服務架構如圖:域名解析 + VIP 負載均衡 + Nginx + 服務網關 + 各個服務。

微服務改造那些痛!蘇寧數據中臺基於Spring Cloud架構實踐

下圖是某個項目採用微服務化後的工程代碼目錄:

  • 項目名稱-模塊1
  • 項目名稱-模塊2
微服務改造那些痛!蘇寧數據中臺基於Spring Cloud架構實踐

整個代碼目錄更清晰,利於模塊拆分、人員職責安排。

數據中臺項目背景介紹

微服務改造那些痛!蘇寧數據中臺基於Spring Cloud架構實踐

蘇寧數據中臺是一個大項目群:

  • OLAP 是底層的加速、查詢引擎,底層支持 Druid、ES、PGCitus 集群,類似 Presto,跟 Presto 不同的是 OLAP 會主動對數據進行 Cube 預加速。
  • 百川是指標平臺層,讓用戶建模、定義指標,對外提供指標查詢服務。百川主要支持的建模方式是:星型模型。
  • 數據建模自然離不開維表維度,UDMS 系統就是來定義、管理所有維度、維表,目前收錄了整個集團近 200 多個維度,對外提供維度、維表信息服務。
  • 天工是類似 Tableau、Superset 的可視化報表設計平臺,與這些 BI 軟件最大的不同點是,天工基於百川的指標、UDMS 的維度來製作報表,數據來源已經高度標準化、歸一化。
  • 目前商業報告分析工具:Cognos、阿里 QuickBI 等,是將數據建模、可視化設計能力放到一起,這是天工與它們的最大區別。
  • 慧眼,是統一報表門戶,所有的報表統一發布到慧眼面向業務。慧眼最大的挑戰在於報表權限管控與自動匹配,總共 4000 多張報表,用戶 2w 多,一張報表開放給8000+人員是很常見的。
  • 所有這一切靠人工維護,既容易出錯又不利於數據安全,也不能及時響應用戶需求,這些都是慧眼系統要解決的問題。

微服務框架選型

Dubbo 架構介紹

Dubbo 主要有四個模塊:

  • Monitor(監控)
  • Regsitry(註冊中心)
  • Provider(服務方)
  • Consumer(消費方)

Provider 註冊服務到 Regsitry,Consumer 向 Regsitry 訂閱服務信息,Monitor服務監控服務調用情況。

整個服務調用流程如下:

  • 消費方在本地發起服務調用
  • 動態代理將調用交給 Loadbalance 模塊
  • Loadbalance 從 Registry 拿到服務實例信息
  • 將請求發送到一臺服務實例
  • 記錄監控日誌等信息
微服務改造那些痛!蘇寧數據中臺基於Spring Cloud架構實踐

Spring Cloud 架構介紹

Spring Cloud 整個架構與 Dubbo 非常類似:

  • Eureka(註冊中心)
  • Gateway(服務網關)
  • Provider(服務方)
  • Consumer(消費方)
  • Zipkin(監控)

不同的有如下幾點:

  • Spring Cloud 是 Http Rest 接口,Dubbo 不是。
  • Spring Cloud 註冊中心不使用 Zookeeper,使用自研的 Eureka。
  • 關於 Zookeeper 是否適合做註冊中心,請參考文章:《Eureka! Why You Shouldn’t Use ZooKeeper for Service Discovery》、《阿里巴巴為什麼不用 ZooKeeper 做服務發現》
  • Spring Cloud 提供了 Gateway 網關組件。
  • 與 Spring 生態兼容,生態鏈豐富,自定義 Filter、攔截器,來加強功能, 如:權限校驗、日誌打印等;Spring Cloud Netflix 提供了熔斷、限流等組件。

綜合以上幾點,考慮到架構統一,未來發展趨勢,我們選擇了 Spring Cloud。

Spring Cloud 主要幫助我們做系統內部服務化,REST 接口形式,不會破壞現有服務,前端服務調用無需做任何調整。

選擇 Spring Cloud 還有一個重要原因是 Dubbo 與蘇寧 RSF 服務框架高度重合,在對外服務接口上,我們還是以 RSF 接口為主。

基於 Spring Cloud 的服務化實踐

整體架構介紹

整體有幾個組件:註冊中心、服務網關、服務監控、負載均衡器。註冊中心使用 Spring Cloud 提供的 Eureka,服務網關使用 Spring Cloud 提供的 Zuul 組件,負載均衡器使用 Ribbon 組件。

服務網關的負載均衡策略選擇的是:WeightedResponseTimeRule,根據服務器響應時間來決定路由到哪個節點。

服務監控組件,用來監控服務性能、調用情況,最重要的一點,是將整個服務鏈路能串聯起來。

微服務改造那些痛!蘇寧數據中臺基於Spring Cloud架構實踐

服務監控設計

監控是一個系統的眼睛,是斷然不可缺少的一部分,Zipkin 提供了很好的服務鏈路監控,結合我們自身的使用場景,最終我們沒有選擇 Zipkin,為什麼?

首先了解下 Zipkin 整體架構:

  • 數據採集(Brave、Sleuth)
  • Tranport 數據傳輸(支持 Kafka、直接發送 Collector)
  • Collector(數據收集)
  • Storage(存儲:ES)
  • Search + Webui(監控展示)

整體架構如下圖所示,Zipkin 監控數據格式如下:

微服務改造那些痛!蘇寧數據中臺基於Spring Cloud架構實踐

Zipkin 有如下缺點:

  • 我們不止是監控 Spring Cloud 服務調用,如:蘇寧 RSF 服務調用、SQL 的執行時間、本地方法執行時間等。
  • 監控數據格式不滿足業務需要。
  • Collector 節點容易出現性能瓶頸,ES 聚合查詢性能較差。
  • 跨線程,鏈路無法串聯。

基於以上幾點,我們決定自研服務鏈路監控系統,整個系統架構如下,我們利用 Kafka、Druid,原則上提供了無限擴展性。

Druid 對應 Zipkin 中的角色:Collector(數據收集) + Storage(存儲:ES)。

微服務改造那些痛!蘇寧數據中臺基於Spring Cloud架構實踐

我們結合業務的需要,設計了監控日誌格式,如下圖所示:

微服務改造那些痛!蘇寧數據中臺基於Spring Cloud架構實踐

一條調用鏈路,有相同的根 ID,服務名由三部分組成:

  • 系統名
  • 一級名稱
  • 二級名稱

一級名稱有 7 種值:

  • url:http 接口
  • url-call:調用 http 接口
  • rs:rsf 接口
  • rsf-call:調用 rsf 接口
  • sql:執行 sql
  • cache:操作緩存
  • method:本地方法

你可能會問,沒有存儲父 ID,如何判斷一條鏈路中的父子關係?這裡我們設計一個特殊的事務 ID 生成規則,通過事務 ID 本身即能判斷父子關係,如下圖所示:

微服務改造那些痛!蘇寧數據中臺基於Spring Cloud架構實踐

下圖監控系統的鏈路展示頁面:

微服務改造那些痛!蘇寧數據中臺基於Spring Cloud架構實踐

微服務改造那些痛!蘇寧數據中臺基於Spring Cloud架構實踐

基於 Hystrix 的熔斷設計

Hystrix 對應的中文名字是“豪豬”,豪豬周身長滿了刺,能保護自己不受天敵的傷害,代表了一種防禦機制,這與 Hystrix 本身的功能不謀而合。

因此 Netflix 團隊將該框架命名為 Hystrix,並使用了對應的卡通形象作為 Logo。

在一個分佈式系統裡,許多依賴會不可避免的調用失敗,比如超時、異常等。

如何能夠保證在一個依賴出問題的情況下,不會導致整體服務失敗,這個就是 Hystrix 需要做的事情。

Hystrix 提供了熔斷、隔離、Fallback、Cache、監控等功能,它能夠在一個、或多個依賴同時出現問題時保證系統依然可用。

使用 Hystrix 很簡單,只需要添加相應依賴即可,最方便的方式是使用註解 HystrixCommand:

  • fallbackMethod:指定 Fallback 方法
  • threadPoolKey:線程池名稱
  • threadPoolProperties:指定線程池參數(線程池大小、最大隊列排隊數量)
  • commandProperties:CIRCUIT_BREAKER 開頭的參數配置熔斷相關參數,METRICS_ROLLING 開頭的參數設置指標計算相關參數
  • 相關參數定義,參考類:HystrixPropertiesManager
@RestControllerpublic 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 實現的廣播功能

有些時候我們希望 url 請求被所有服務實例執行,這裡我們對 Zuul 做了一個改造,增加了一個 BroadCastFilter,在 url 請求 header 設置 gate_broadcast 為 true,那麼這個請求,將被轉發給所有服務實例。

邏輯流程如下:

  • 判斷 gate_broadcast 參數為 true
  • 從 url 獲取 ServiceId
  • 從 Ribbon 獲取服務所有實例
  • 將請求發送給所有實例
  • 將所有實例返回結果封裝,返回

微服務帶來的問題

服務拆分粒度不好把握

Spring Cloud 的微服務有一個 ServiceId 的概念,通常一個 war 包對應一個 ServiceId,這個 ServiceId 下可以有多個服務。粒度拆分方式主要有:橫向、縱向。

縱向切分主要有如下幾個方式:

  • 按功能切,如用戶管理、指標管理、模型管理等。
  • 按角色切,如管理員、商家、用戶。

橫向切分,一般用來提取公共的基礎服務,比如:用戶名密碼校驗服務、用戶基本信息查詢。

運維、開發複雜度增加

單體程序時代只有一個 war 包,微服務鼓勵服務拆分,war 數量、部署節點大大增加。

此外,一個流程處理往往會由多個分佈式服務協同完成,帶來了不少棘手的問題:

  • 需要通過分佈式事務保障數據最終一致性
  • 防止單個服務問題造成雪崩

這些都給開發者提出了更高的要求。

調試難度增加

微服務方式鼓勵服務拆分,通過服務間依賴完成功能,給開發、測試帶來了挑戰,合理選擇微服務、代碼複用兩種方案。

後續架構演進

服務版本控制

沒有版本控制,意味著我們無法做灰度發佈,毀滅性版本發佈後,無法做到對老版本兼容,下圖為服務 A、B、C、D 間的版本依賴關係:

微服務改造那些痛!蘇寧數據中臺基於Spring Cloud架構實踐

我們實現思路是對 Zuul 進行改造:

  • 打版本標籤,在 Zuul 對訪問來源判斷(比如 App 版本 5.1 對應的查詢接口版本為 2.1),打上版本標籤
  • 根據版本信息,路由到對應版本服務實例

基於 Gateway 的服務熔斷、限流機制

目前有一些開源的框架如 ratelimit,通過在 Ruul 增加 filter 來實現限流熔斷。

但是有幾個問題:

  • 不支持動態配置
  • 不能滿足業務變化,如配合版本控制

綜上所述,我們已經著手一些自研工作,能與我們業務場景貼合得更緊密。

總結

從 2016 年到現在,兩年的時間裡,蘇寧大數據中心從傳統的單例開發模式,切換到基於 Spring Cloud 的微服務開發模式,並摸索出了一條適合自己的路,這不只是技術框架的切換,更是開發思維的升級。

令人欣喜的是,這兩年間 Spring Cloud 飛速發展,2018 年發佈了革命性的 2.0 版本,這離不開社區的支持,許多像 Netflix 一樣的公司在筆耕不輟地為 Spring Cloud 生態添磚加瓦。

我們基於 Spring Cloud 開發出了一些服務於自己業務的組件,讓我們認識到自己也是有能力有責任去回饋社區。

路漫漫其修遠兮,好的架構一定是適應業務發展的架構,對於 Spring 這不是終點,對於我們更不是。


分享到:


相關文章: