前言
隨着架構設計的發展,微服務架構可以說是目前架構領域炙手可熱的設計理念。在公司,筆者也一直在負責系統的服務化設計和開發工作。
今天就來談談微服務落地實踐中的一些問題。希望對微服務設計無從下手的朋友,起到一些參考作用;另外也希望把自己的觀點分享出來,期待與大家一起交流,能夠認識到不足之處。
一、服務拆分
在落地微服務之前,我們遇到的第一個問題就是:應該如何拆分服務?
大家知道,關於如何拆分服務,並沒有一個完全通用的法則。不過有以下幾點要素,我們可以參考。
1、業務獨立性
一般情況下,我們第一時間都會考慮到這點。將系統中的業務模塊,按照職責標識出來,每個單獨的模塊拆分成一個獨立的服務。
這樣拆分之後,我們的服務要滿足一個原則:高內聚,低耦合。
比如,我們把一個系統拆分爲商品服務、訂單服務、物流服務。一般情況下,我們修改物流服務的時候,並不會影響商品服務,這就是低耦合的體現;那麼訂單服務裏面的功能和邏輯,都是圍繞着訂單這個核心業務流程的,就可以說它是高內聚的。
2、業務穩定性
我們可以把系統中的業務按照穩定性進行區分。比如,用戶註冊、登錄部分,在我司的系統中,這一塊的代碼只要寫完,基本就不再會發生變動,那麼我就將他們拆爲用戶服務。
同理,還有日誌服務、監控服務,這些模塊基本上也都是很穩定的業務,可以考慮單獨拆分。
3、業務可靠性
這裏講究的是,將可靠性要求高的核心服務和可靠性要求低的非核心服務拆分開來,然後重點保證核心服務的高可用。
避免由於非核心服務故障,而影響核心服務。
4、業務性能
基於業務性能拆分,考慮的是將性能壓力大的模塊拆分出來。對於這一點,筆者有兩點想法:
- 避免性能壓力大的服務影響其他服務。
- 將高流量的業務獨立出來,扛不住的情況下,方便水平擴展。
比如,在筆者參與的一個系統中,曾通過 RocketMQ
對接來自多家廠商的大量數據。
當時,就獨立出來一個消息服務,專門用來消費消息。然後有的是在本地處理,有的通過 RPC
接口轉發到其他服務處理,有的直接通過 WebSocket
推送到前端展示。
這樣的話,即便流量激增,考慮給消息服務增加機器,提高消費能力就好了。
當了解到上面這幾種拆分方式之後,我們就可以根據自己的業務範圍和技術團隊規模,來考慮自己系統的服務拆分了。
不過在這裏,尤爲值得注意的是,微服務切忌拆分的過細,一定要結合業務規模和技術團隊的規模。
筆者以前就遇到一個項目,在做服務化的過程中,當時的技術負責人按照業務獨立性來拆分服務,結果拆出了10來個服務;然後更狠的是,每個業務服務又把Service
層和Controller
層拆分開來,每個業務方法都需要遠程調用。據當事人描述,這樣做是爲了方便後期提升擴展能力。
不過說實話,有些系統業務量並沒有那麼大,一味的迎合微服務中的微字,無疑是給自己增加難度,破壞整體系統的穩定性。所以,在重新梳理了業務流程後,筆者對這個系統進行了重構,縮減服務數量。
在這裏,筆者想說明一點。Service
層和Controller
層是可以拆分成多個模塊的,這個沒關係。不過,它只應該是模塊的分離,而不是服務的拆分。比如我們可以在開發階段把它們拆分成多個模塊,然後通過 Maven modules
聚合到一塊,在部署運行階段,它們還都是一個服務。
二、技術選型
當完成了服務拆分之後,選用什麼框架進行開發呢,應該選擇 Dubbo 還是 SpringCloud ?
筆者不想單純討論它們的優劣之處,在這裏可以就着這個問題分享下筆者的心路歷程。
最初進行選型時,筆者選擇了 SpringCloud
,畢竟它號稱是微服務一站式解決方案。然後就搭建框架,集成各種組件,完成了一些 demo 的開發和測試。
不過,在完成這部分工作後,重新審視整個系統,看到SpringCloud
中,涉及到的Eureka、Ribbon、Feign、Hystrix、Zuul、Config
這些組件。
這時候,我會產生兩個疑問:
- 這些組件是不是非用不可 ? 有沒有更輕便的系統方案 ?
- 這麼多東西,任一環節出了問題,我們是否能hold的住?
筆者對於一站式
這個詞有兩層理解。第一,它簡化了分佈式系統基礎設施的開發,易於開發;第二,簡化的同時,它一定是屏蔽了複雜的配置和實現原理,不易於深入理解它的原理。
作爲架構師或者團隊技術負責人,我們必須對自己系統涉及到的技術點做到知根知底,或者說,最起碼要懂得它們的原理。這樣,即便遇到了問題,在 Baidu / Google
不出來結果時,也不會慌。
基於這樣一個思路,筆者把目光又轉向了Dubbo
。對於筆者來說,對Dubbo
就比較熟悉了,從它的框架本身來說,已經實現了負載均衡和集羣容錯,調用方式也是基於接口的。相較SpringCloud
而言,不需要再額外引入像Ribbon/Feign
這樣的組件。
還有一點,Dubbo
得益於強大的SPI
機制,我們可以非常方便的擴展它。如果業務上有需要,在很多地方都可以對它進行擴展,比如 RPC 協議、集羣、註冊中心、序列化方式、線程池等。
不過話說回來,Dubbo
只是一個高性能的 RPC 框架,拿它和SpringCloud
相比,更多的還是比較REST和RPC
。不過關於這一點,就一般的項目而已,這點性能差異還不足以一錘定音,最多隻是錦上添花而已。
至於其他的組件,比如Hystrix/Zuul/Config/zipkin
等,筆者的觀點,還是看業務規模。微服務只是一種設計思想,架構理念,而不是說用到了很多分佈式框架才叫微服務。這些框架的產生,只是爲了解決微服務系統遇到的問題的。
需知一口喫不成個胖子,在做技術選型時,切記直接對標像阿里、京東、美團這樣的大廠經驗,一來,也許我們遇不到那樣的業務場景;再者,一般公司也沒有人家那樣的人才儲備。畢竟如果線上出了問題,是沒人跟你分享損失的。
總的來說,還是結合自己的實際情況,以最穩妥的技術方案,完成業務上的需求。
三、節外生枝
拆分了服務,也完成了技術方案的選型,那就萬事大吉,開始擼代碼了嗎 ? 如果單純作爲開發人員,那確實開擼就行了。如果你是一個系統的負責人,只滿足於高屋建瓴,不考慮細節問題,那必然會節外生枝。
1、超時和容錯
服務化之後,不同服務之間的調用就是遠程調用。遠程調用有個最基本的設置,即超時時間。
比如,在Dubbo
中,默認的超時時間是1秒。我們不能單純的使用默認值或者統一設置成另外的值,爲Dubbo
設置超時時間最好是有針對性的。
比如,比較簡單的業務可以設置的短一些;但對於複雜業務而言,則需要適當的加長這個時間。因爲,這裏還涉及到一個集羣容錯的問題。
在Dubbo
中,集羣容錯的默認策略是失敗重試,次數爲2。假如有的業務本身就需要耗費較長的時間來執行,因爲超時時間太短就會觸發容錯機制,來重試。大量的併發重試請求,很可能會佔滿Dubbo
的線程池,甚至影響後端數據庫系統,導致連接被耗盡。
2、容錯和冪等性
我們上面說,如果超時時間設置太短,有可能會導致大量請求會不斷重試,而導致異常。
這裏還隱瞞着另外一個細節,即讀請求和寫請求。如果是讀請求,那麼重試無所謂;如果是寫請求,我們的接口是否也支持自動重試呢 ? 這就會涉及到接口冪等性問題。
如果寫請求的接口,不支持冪等性,那麼集羣容錯就得改爲 failfast
,即快速失敗。
3、分佈式事務
筆者感覺,分佈式事務在業界是一個沒有徹底解決的技術難題。 沒有通用的解決方案,也沒有既高效又簡便的手段。
雖然如此,但我們也得事先考慮到這一點,不然數據肯定會變成髒亂差。
在考慮解決方案之前,我們需要先看看自己的系統是不是真的追求強一致性;按照BASE
理論,在分佈式系統中,允許不同節點在同步的過程存在延時,但是經過一段時間的修復後,能夠達到數據的最終一致性。
基於這兩個思路,我們纔好制定自己的分佈式事務方案。
對於要求強一致性的場景,或許可以考慮XA協議,通過二階段提交或者三階段提交來保證。
對於要求最終一致性的場景,可以考慮採用 TCC 模式,補償模式,或者基於消息隊列的模式。
比如基於消息隊列模式,可以採用 RocketMQ
。它支持事務消息,那麼這時候整個流程大概是這樣的:
- 通過
RocketMQ
發送事務消息到消息隊列; - 消息發送成功,則執行本地事務;
- 如果本地事務執行成功,則提交
RocketMQ
事務消息,提交後對消費者可見; - 如果本地事務執行失敗,則刪除
RocketMQ
事務消息,消費者不會看到這條消息。
另外,在這裏安利下阿里開源的Seata
。目前最新版本是1.1.0,支持多種事務模式,比如 AT、TCC、SAGA 和 XA 事務模式。
筆者有篇文章是基於 Seata 0.7
版本的寫的,有興趣的朋友可以瞭解下。
4、消息隊列
在分佈式系統架構中,爲了系統間的解耦和異步處理,應對高併發和大流量,消息隊列絕對是一大利器。
在使用這一利器前,我們也得考慮下有可能因爲消息隊列帶來的煩惱。
首先需要考慮的就是可用性,如果消息隊列不可用,會不會對系統本身造成大量的不可用;
然後,消息會不會丟失呢 ? 如何保證消息可靠性傳輸呢?比如要考慮消息隊列本身的刷盤機制、同步機制;數據發送時的確認和消費後的提交;
然後就是重複消費,如果保證了消息不會丟失,多多少少都可能會有重複消息的問題,這時候就要考慮重複消費有沒有問題,即消息冪等性;
還有,消息順序性問題,你們的業務場景裏,是否有消息順序性問題,如果有這個問題,要麼在設計時規避它,要麼在消費時保證它的順序。
5、統一日誌
隨着微服務的拆分,日誌系統也可能會演變爲獨立的模塊。爲了查看日誌,我們可能需要登錄到不同的服務器去一個個查看。
因此,搭建統一的日誌處理平臺是必然的。我們可以採用 ELK
的解決方案進行日誌聚合。
在這裏,還需要鏈路追蹤問題。在微服務複雜的鏈式調用中,會比單體應用更難以定位和追蹤問題。
對於這個問題,我們考慮引入分佈式調用鏈,生成一個全局唯一的 TraceID
,通過它把整個調用鏈串聯起來。結合Dubbo框架的話,我們實現自己的Filter
,用來透傳這個TraceID
。
具體思路可以參考:SpringBoot+Dubbo集成ELK實戰
當然,我們也可以選用一些成熟的開源框架來解決。
總結
本文簡單總結了微服務設計和開發過程中,可能會涉及到的一些問題。
以上觀點只是一家之言,僅僅是筆者在過去時間裏的經驗總結。
如果對您有幫助,請點贊鼓勵~ 如果您有不同觀點,請積極發言,共同交流~