做者| 修冶
背 景
微服務在最近幾年大行其道,不少公司的研發人員都在考慮微服務架構,同時,隨着 Docker 容器技術和自動化運維等相關技術發展,微服務變得更容易管理,這給了微服務架構良好的發展機會。 在作微服務的路上,拆分服務是個很熱的話題。咱們應該按照什麼原則將現有的業務進行拆分?是否拆分得越細就越好?接下來一塊兒談談服務拆分的策略和堅持的原則。 數據庫
拆分目的是什麼?
在介紹如何拆分以前,咱們須要瞭解下拆分的目的是什麼,這樣纔不會在後續的拆分過程當中忘了最初的目的。 拆分的本質是爲了將複雜的問題簡單化,那麼咱們在單體架構階段遇到了哪些複雜性問題呢?首先來回想下當初爲何選用了單體架構,在電商項目剛啓動的時候,咱們只但願能儘快地將項目搭建起來,方便將產品更早的投放市場進行快速驗證。在開發初期,這種架構確實給開發和運維帶來了很大的便捷,主要體如今: 安全
- 開發簡單直接,代碼和項目集中式管理。
- 排查問題時只須要排查這個應用就能夠了,更有針對性。
- 只須要維護一個工程,節省維護系統運行的人力成本。
可是隨着功能愈來愈多,開發團隊的規模愈來愈大,單體架構的缺陷慢慢體現出來,主要有如下幾個方面: 在技術層面,數據庫的鏈接數成爲應用服務器擴容的瓶頸,由於鏈接 MySQL 的客戶端數量是有限制的。 除此以外,單體架構增長了研發的成本抑制了研發效率的提高。好比公司的垂直電商系統團隊會被按業務線拆分爲不一樣的組。當如此多的小團隊共同維護一套代碼和一個系統時,在配合的過程當中就會出現問題。不一樣的團隊之間溝通少,假如一個團隊須要一個發送短信的功能,那麼有的研發同窗會認爲最快的方式不是詢問其餘團隊是否有現成的,而是本身寫一套,可是這種想法是不合適的,會形成功能服務的重複開發。因爲代碼部署在一塊兒,每一個人都向同一個代碼庫提交代碼,代碼衝突沒法避免;同時功能之間耦合嚴重,可能你只是更改了很小的邏輯卻致使其它功能不可用,從而在測試時須要對總體功能迴歸,延長了交付時間。模塊之間互相依賴,一個小團隊中的成員犯了一個錯誤,就可能會影響到其它團隊維護的服務,對於總體系統穩定性影響很大。 最後,單體架構對於系統的運維也會有很大的影響。想象一下,在項目初期你的代碼可能只有幾千行,構建一次只須要一分鐘,那麼你能夠很敏捷靈活地頻繁上線變動修復問題。可是當你的系統擴充到幾十萬行甚至上百萬行代碼的時候,一次構建的過程包括編譯、單元測試、打包和上傳到正式環境,花費的時間可能達到十幾分鍾,而且任何小的修改,都須要構建整個項目,上線變動的過程很是不靈活。 而這些問題均可以經過微服務化拆分來解決。 爲了方便你更好的理解這塊,在此附上一份表格(內容來源:《持續演進的 Cloud Native:雲原生架構下微服務最佳》一書),能夠更直觀地幫助你認識拆分的目的。 服務器
拆分時機應該如何決策?
產品初期,應該以單體架構優先。由於面對一個新的領域,對業務的理解很難在開始階段就比較清晰,每每是通過一段時間以後,才能逐步穩定,若是拆分過早,致使邊界拆分不合理或者拆的過細,反而會影響生產力。不少時候,從一個已有的單體架構中逐步劃分服務,要比一開始就構建微服務簡單得多。同時公司的產品並無被市場驗證過,有可能會失敗,因此這個投入的風險也會比較高。 另外,在資源受限的狀況下,採用微服務架構不少優點沒法體現,性能上的劣勢反而會比較明顯。以下圖所示。當業務複雜度達到必定程度後,微服務架構消耗的成本纔會體現優點,並非全部的場景都適合採用微服務架構,服務的劃分應逐步進行,持續演進。產品初期,業務複雜度不高的時候,應該儘可能採用單體架構。 網絡
隨着公司的商業模式逐漸獲得驗證,且產品得到了市場的承認,爲了能加快產品的迭代效率快速佔領市場,公司開始引進更多的開發同窗,這時系統的複雜度會變得愈來愈高,就出現單體應用和團隊規模之間出現矛盾,研發效率不升反降。上圖中的交叉點代表,業務已經達到了必定的複雜度,單體應用已經沒法知足業務增加的需求,研發效率開始降低,而這時就是須要考慮進行服務拆分的時機點。這個點須要架構師去權衡。筆者所在的公司,是當團隊規模達到百人的時候,才考慮進行服務化。 當咱們清楚了何時進行拆分,就能夠直接落地了嗎?不是的,微服務拆分的落地還要提早準備好配套的基礎設施,如服務描述、註冊中心、服務框架、服務監控、服務追蹤、服務治理等幾大基本組件,以上每一個組件缺一不可,每一個組件展開又包括不少技術門檻,好比,容器技術、持續部署、DevOps 等相關概念,以及人才的儲備和觀念的變化。微服務不只僅是技術的升級,更是開發方式、組織架構、開發觀念的轉變。閉包
至此,什麼時候進行微服務的拆分,總體總結以下: 架構
- 業務規模:業務模式獲得市場的驗證,須要進一步加快腳步快速佔領市場,這時業務的規模變得愈來愈大,按產品生命週期來劃分(導入期、成長期、成熟期、衰退期)這時通常在成長期階段。若是是導入期,儘可能採用單體架構。
- 團隊規模:通常是團隊達到百人的時候。
- 技術儲備:領域驅動設計、註冊中心、配置中心、日誌系統、持續交付、監控系統、分佈式定時任務、CAP 理論、分佈式調用鏈、API 網關等等。
- 人才儲備:精通微服務落地經驗的架構師及相應開發同窗。
- 研發效率:研發效率大幅降低,具體問題參加上面拆分目的裏提到的。
拆分時應該堅守哪些指導原則?
1. 單一服務內部功能高內聚低耦合併發
也就是說每一個服務只完成本身職責內的任務,對於不是本身職責的功能交給其它服務來完成。 2. 閉包原則( CCP ) 微服務的閉包原則就是當咱們須要改變一個微服務的時候,全部依賴都在這個微服務的組件內,不須要修改其餘微服務。 3. 服務自治、接口隔離原則 儘可能消除對其餘服務的強依賴,這樣能夠下降溝通成本,提高服務穩定性。服務經過標準的接口隔離,隱藏內部實現細節。這使得服務能夠獨立開發、測試、部署、運行,以服務爲單位持續交付。 4. 持續演進原則 在服務拆分的初期,你其實很難肯定服務究竟要拆成什麼樣。從微服務這幾個字來看,服務的粒度貌似應該足夠小,可是服務多了也會帶來問題,服務數量快速增加會帶來架構複雜度急劇升高,開發、測試、運維等環節很難快速適應,會致使故障率大幅增長,可用性下降,非必要狀況,應逐步劃分,持續演進,避免服務數量的爆炸性增加,這等同於灰度發佈的效果,先拿出幾個不過重要的功能拆分出一個服務作試驗,若是出現故障,則能夠減小故障的影響範圍。 5. 拆分的過程儘可能避免影響產品的平常功能迭代 也就是說要一邊作產品功能迭代,一邊完成服務化拆分。好比優先剝離比較獨立的邊界服務( 如短信服務等 ),從非核心的服務出發減小拆分對現有業務的影響,也給團隊一個練習、試錯的機會。同時當兩個服務存在依賴關係時優先拆分被依賴的服務。 6. 服務接口的定義要具有可擴展性 服務拆分以後,因爲服務是以獨立進程的方式部署,因此服務之間通訊就再也不是進程內部的方法調用而是跨進程的網絡通訊了。在這種通訊模型下服務接口的定義要具有可擴展性,不然在服務變動時會形成意想不到的錯誤。好比微服務的接口由於升級把以前的三個參數改爲了四個,上線後致使調用方大量報錯,推薦作法服務接口的參數類型最好是封裝類,這樣若是增長參數就沒必要變動接口的簽名,而只須要在類中添加字段就能夠了 7. 避免環形依賴與雙向依賴 儘可能不要有服務之間的環形依賴或雙向依賴,緣由是存在這種狀況說明咱們的功能邊界沒有化分清楚或者有通用的功能沒有下沉下來。 框架
8. 階段性合併 隨着你對業務領域理解的逐漸深刻或者業務自己邏輯發生了比較大的變化,亦或者以前的拆分沒有考慮的很清楚,致使拆分後的服務邊界變得愈來愈混亂,這時就要從新梳理領域邊界,不斷糾正拆分的合理性。 運維
拆分的粒度是否是越細越好?
目前不少傳統的單體應用再向微服務架構進行升級改造,若是拆分粒度太細會增長運維複雜度,粒度過大又起不到效果,那麼改造過程當中如何平衡拆分粒度呢?分佈式
1. 弓箭原理 平衡拆分粒度能夠從兩方面進行權衡,一是業務發展的複雜度,二是團隊規模的人數。如上圖,它就像弓箭同樣,只有當業務複雜度和團隊人數足夠大的時候,射出的服務拆分粒度這把劍纔會飛的更遠,發揮出最大的威力。 好比說電商的商品服務,當咱們把商品從大的單體裏拆分出來的時候,就商品服務自己來說,邏輯並無足夠複雜到 2 ~ 3 我的無法維護的地步,這時咱們沒有必要繼續將商品服務拆的更細,可是隨着業務的發展,商品的業務邏輯變的愈來愈複雜,可能同時服務公司的多個平臺,此時你會發現商品服務自己面臨的問題跟單體架構階段面臨的問題基本同樣,這個階段就須要咱們將商品拆成更細粒度的服務,好比,庫存服務、價格服務、類目服務、商品基礎信息服務等等。
雖然業務複雜度已經知足了,若是公司此時沒有足夠的人力(招聘不及時或員工異動比較多),服務最好也不要拆分,拆分會由於人力的不足致使更多的問題,如研發效率大幅降低(一個開發負責與其不匹配數量的服務)。這裏引伸另一個問題,一個微服務究竟須要幾個開發維護是比較理性的?我引用下李雲華老師在"從零開始學架構「 中的一段經典論述,能夠解決此問題。 2. 三個火槍手原則 爲何說是三我的分配一個服務是比較理性的?而不是 4 個,也不是 2 個呢? 首先,從系統規模來說,3 我的負責開發一個系統,系統的複雜度恰好達到每一個人都能全面理解整個系統,又可以進行分工的粒度;若是是 2 我的開發一個系統,系統的複雜度不夠,開發人員可能以爲沒法體現本身的技術實力;若是是 4 個甚至更多人開發一個系統,系統複雜度又會沒法讓開發人員對系統的細節都瞭解很深。 其次,從團隊管理來講,3 我的能夠造成一個穩定的備份,即便 1 我的休假或者調配到其餘系統,剩餘 2 我的還能夠支撐;若是是 2 我的,抽調 1 個後剩餘的 1 我的壓力很大;若是是 1 我的,這就是單點了,團隊沒有備份,某些狀況下是很危險的,假如這我的休假了,系統出問題了怎麼辦? 最後,從技術提高的角度來說,3 我的的技術小組既可以造成有效的討論,又可以快速達成一致意見;若是是 2 我的,可能會出現互相堅持本身的意見,或者 2 我的經驗都不足致使設計缺陷;若是是 1 我的,因爲沒有人跟他進行技術討論,極可能陷入思惟盲區致使重大問題;若是是 4 我的或者更多,可能有的參與的人員並無認真參與,只是完成任務而已。 「三個火槍手」的原則主要應用於微服務設計和開發階段,若是微服務通過一段時間發展後已經比較穩定,處於維護期了,無須太多的開發,那麼平均 1 我的維護 1 個微服務甚至幾個微服務均可以。固然考慮到人員備份問題,每一個微服務最好都安排 2 我的維護,每一個人均可以維護多個微服務。 綜上所訴,拆分粒度不是越細越好,粒度須要符合弓箭原理及三個火槍手原則。
拆分策略有哪些?
拆分策略能夠按功能和非功能維度進行考慮,功能維度主要是劃分清楚業務的邊界,非功能維度主要考慮六點包括擴展性、複用性、高性能、高可用、安全性、異構性。接下來詳細介紹下。
功能維度 功能維度主要是劃分清楚業務邊界,採用的主要設計方法能夠利用 DDD(關於 DDD 的理論知識能夠參考網上其它資料),DDD 的戰略設計會創建領域模型,能夠經過領域模型指導微服務的拆分,主要分四步進行:
- 第一步,找出領域實體和值對象等領域對象。
- 第二步,找出聚合根,根據實體、值對象與聚合根的依賴關係,創建聚合。
- 第三步,根據業務及語義邊界等因素,定義限界上下文。
- 第四步,每個限界上下文能夠拆分爲一個對應的微服務,但也要考慮一些非功能因素。
以電商的場景爲例,交易鏈路劃分的限界上下文以下圖左半部分,根據一個限界上下文能夠設計一個微服務,拆解出來的微服務以下圖右側部分。
2. 非功能維度 當咱們按照功能維度進行拆分後,並非就萬事大吉了,大部分場景下,咱們還須要加入其它維度進一步拆分,才能最終解決單體架構帶來的問題。
-
擴展性:區分系統中變與不變的部分,不變的部分通常是成熟的、通用的服務功能,變的部分通常是改動比較多、知足業務迭代擴展性須要的功能,咱們能夠將不變的部分拆分出來,做爲共用的服務,將變的部分獨立出來知足個性化擴展須要。同時根據二八原則,系統中常常變更的部分大約只佔 20%,而剩下的 80 % 基本不變或極少變化,這樣的拆分也解決了發佈頻率過多而影響成熟服務穩定性的問題。
-
複用性:不一樣的業務裏或服務裏常常會出現重複的功能,好比每一個服務都有鑑權、限流、安全及日誌監控等功能,能夠將這些經過的功能拆分出來造成獨立的服務,也就是微服務裏面的 API 網關。在如,對於滴滴業務,有快車和順風車業務,其中都涉及到了訂單支付的功能,那麼就能夠將訂單支付獨立出來,做爲通用服務服務好上層業務。以下圖:
- 高性能:將性能要求高或者性能壓力大的模塊拆分出來,避免性能壓力大的服務影響其它服務。常見的拆分方式和具體的性能瓶頸有關,例如電商的搶購,性能壓力最大的是入口的排隊功能,能夠將排隊功能獨立爲一個服務。同時,咱們也能夠基於讀寫分離來拆分,好比電商的商品信息,在 App 端主要是商詳有大量的讀取操做,可是寫入端商家中心訪問量確不多。所以能夠對流量較大或較爲核心的服務作讀寫分離,拆分爲兩個服務發佈,一個負責讀,另一個負責寫。還有數據一致性是另外一個基於性能維度拆分須要考慮的點,對於強一致的數據,屬於強耦合,儘可能放在同一個服務中(可是有時會由於各類緣由須要進行拆分,那就須要有響應的機制進行保證),弱一致性一般能夠拆分爲不一樣的服務。
-
高可用:將可靠性要求高的核心服務和可靠性要求低的非核心服務拆分開來,而後重點保證核心服務的高可用。具體拆分的時候,核心服務能夠是一個也能夠是多個,只要最終的服務數量知足「三個火槍手」的原則就能夠。好比針對商家服務,能夠拆分一個核心服務一個非核心服務,核心服務供交易服務訪問,非核心提供給商家中心訪問 。
-
安全性:不一樣的服務可能對信息安全有不一樣的要求,所以把須要高度安全的服務拆分出來,進行區別部署,好比設置特定的 DMZ 區域對服務進行分區部署,能夠更有針對性地知足信息安全的要求,也能夠下降對防火牆等安全設備吞吐量、併發性等方面的要求,下降成本,提升效率。
-
異構性:對於對開發語言種類有要求的業務場景,能夠用不一樣的語言將其功能獨立出來實現一個獨立服務。
以上幾種拆分方式不是多選一,而是能夠根據實際狀況自由排列組合。同時拆分不只僅是架構上的調整,也意味着要在組織結構上作出相應的適應性優化,以確保拆分後的服務由相對獨立的團隊負責維護。
服務都拆了爲何還要合併?
古希臘哲學家赫拉克利特曾經說過:「人不能兩次踏進同一條河流。」隨着時間的流逝,任何事物的狀態都會發生變化。線上系統一樣如此,即便一個系統在不一樣時刻的情況也毫不會如出一轍。如今拆分出來的服務粒度也許合適,但誰能保證這個粒度可以一直正確呢。
服務都拆了爲何還要合,就是要不斷適應新的業務發展階段,筆者這裏作個類比看你們是否清晰,拆至關於咱們開發代碼,合至關於重構代碼,爲何要重構呢,相信你確定知道。微服務的合也是同樣的道理,隨着咱們對應用程序領域的瞭解愈來愈深,它們可能會隨着時間的推移而變化。例如,你可能會發現因爲過多的進程間通訊而致使特定的分解效率低下,致使你必須把一些服務組合在一塊兒。 同時由於人員和服務數量的不匹配,致使的維護成本增長,也是致使服務合併的一個重要緣由。例如,今年疫情的影響致使不少企業開始大量裁人,人員流失可是服務的數量確沒有變,形成服務數量和人員的不平衡,一個開發同窗同時要維護至少 5 個服務的開發,效率大幅降低。 那麼若是微服務數量過多和資源不匹配,則能夠考慮合併多個微服務到服務包,部署到一臺服務器,這樣能夠節省服務運行時的基礎資源消耗也下降了維護成本。須要注意的是,雖然服務包是運行在一個進程中,可是服務包內的服務依然要知足微服務定義,以便在將來某一天要從新拆開的時候能夠很快就分離。服務合併到服務包示意圖以下:
拆分過程當中要注意的風險
1. 不打無準備之仗 開發團隊是否具有足夠的經驗,可否駕馭微服務的技術棧,多是第一個須要考慮的點。這裏並非要求團隊必須具有完善的經驗才能啓動服務拆分,若是團隊中有這方面的專家當然是最好的。若是沒有,那可能就須要事先進行充分的技術論證和預演,至少不打無準備之仗。避免哪一個簡單就先拆哪一個,哪一個新業務要上了,先起一個服務再說。不然可能在一些分佈式常見的問題上會踩坑,好比服務器資源不夠、運維困難、服務之間調用混亂、調用重試、超時機制、分佈式事務等等。 2. 不斷糾正 咱們須要認可咱們的認知是有限的,只能基於目前的業務狀態和有限的對將來的預測來制定出一個相對合適的拆分方案,而不是所謂的最優方案,任何方案都只能保證在當下提供了相對合適的粒度和劃分原則,要時刻作好在將來的末一個時刻會變得不和時宜、須要再次調整的準備。所以隨着業務的演進,須要咱們從新審視服務的劃分是否合理,如服務拆的太細,致使人員效率反而降低,故障的機率也大大增長,則須要從新劃分好領域邊界。
3. 要作行動派,而不是理論派
在具體怎麼拆分上,也不要太糾結因而否合適,不動手怎麼知道合不合適呢?若是拆了以後發現真的不合適,在從新調整就行了。你可能會說,從新調整成本比較高。但實際上這個問題的本質是有沒有針對服務化架構搭建起一套完成的能力體系,好比服務治理平臺、數據遷移工具、數據雙寫等等,若是有的話,從新調整的成本是不會過高的。