微服務的痛點
在產品研發過程中,引入一種技術來解決一個業務問題并不難,難的是能否合理評估技術風險,這個觀點對微服務同樣適用。
因此,本節將專門討論微服務會帶來哪些問題,這部分內容不管是在面試中還是日常架構設計中,對大家的幫助都會非常大。
痛點:微服務職責劃分
微服務的難點在于無法對一些特定職責進行清晰劃分,比如某個特定職責應該歸屬于服務A還是服務B?為了方便理解,下面舉幾個例子說明一下,微服務的職責劃分是怎么演變成公司技術部門的陷阱的。
首先,微服務的劃分原則都是很容易理解的。
1.根據存放主要數據的服務所在進行劃分
比如一個能根據商品ID找出商品信息的接口,把它放在商品服務中即可。
再比如獲取單個用戶的所有訂單,把它放在訂單服務中即可。
2.業務邏輯服務歸屬與業務人員的劃分可能存在關系
比如每個商品在每個門店的庫存應該放在商品服務還是門店服務?因為各個門店的商品庫存由該門店的運營人員管理,最終可以把它放在門店系統中。
3.業務邏輯服務歸屬與產品人員的劃分可能存在關系比如業務部門提出一個新需求,需要設計一個能對商品進行相關設置的功能,使得某些門店只能賣某些商品。
此時這個功能應該放在門店服務還是放在商品服務呢?這就需要看這個功能由哪條業務線的產品負責人負責了,比如,由商品系統的產品經理負責,就把它放在商品服務中;由門店的產品經理負責,就把它放在門店服務中。
4.業務邏輯服務歸屬與工期可能存在關系
緊接著上面的例子——實現某些門店只能賣某些商品的需求。根據前面產品從屬原則的劃分邏輯,特定門店特定商品的上架功能放在門店服務中,因為特定商品由門店的運營人員負責上架。
但是這種劃分邏輯會出現這樣的情況:門店服務的開發人員很忙,沒空接這個需求,而商品服務的開發人員剛好有空,但他們對門店服務的邏輯不了解。于是,商品服務的開發人員提議,如果想在兩周內實現這個需求,則必須把這個功能放在商品服務中。這種方案看起來很不通用,不過最終他們確實把這個功能放在了商品服務中,因為再優雅的設計也抵不過業務部門要求的上線時間壓力。
5.業務邏輯服務歸屬還可能與組織架構存在關系
通過康威定律就能很快明白這一概念。
康威是個程序員,他在1967年提出:設計系統的組織在設計系統時,會設計出基于這些組織的溝通結構的系統。
關于微服務職責劃分的痛點,通過前面幾個例子的介紹,大家應該有所體會了,接下來再講一個進銷存供應鏈系統的例子,加深理解。
這里的“進”指的是供應商的采購,“銷”指的是門店的銷售單,“存”指的是一些中央倉庫的庫存,且進銷存供應鏈系統與新零售系統之間緊密結合,對應的架構圖如圖12-3所示。

• 圖12-3 新零售和供應鏈系統關系
在這個架構中,原本門店的商品庫存由門店運營人員(即新零售業務)負責,中央倉庫庫存由供應鏈人員管理。后來,不知什么原因領導要求更改供應鏈總監職責,此時供應鏈總監需要同時負責門店商品庫存+中央倉庫庫存。
先來看看原職責劃分情況,對應關系如圖12-4所示。

• 圖12-4 原職責劃分
在圖12-4中可以看到,在原有的組織架構中,新零售業務的產研只對接新零售業務,供應鏈業務的產研只對接供應鏈業務。現如今,門店庫存管理職責需要劃分到供應鏈業務中,也就是說新零售業務的產研不再負責這個需求,而是交由供應鏈業務的產研負責了。此時供應鏈業務的產研會把門店庫存積極地遷移到供應鏈的庫存管理中,因為門店庫存管理好了,供應鏈業務方的績效就好了,產研的績效也高了,年終獎也就更多了。
因此,在現實場景中,微服務職責的劃分會受太多人為因素的影響,大家也就能理解為什么現在關于服務職責劃分原則的相關資料不太多。
痛點:微服務粒度拆分
微服務還有一個痛點,就是服務太多。這里通過一個加盟商的例子把服務粒度的內容詳細介紹一下。
還是以上面的新零售系統為例。最初該系統只有登錄和信息管理功能,把這些功能存放在一個服務中即可,實現起來比較簡單。隨著加盟商的加入,因為加盟商準入、開店、退出都涉及費用問題,因此又需要增加財務功能(如應收、應付、實收、實付、退款、對賬等)。
隨著業務的逐步開展,又需要增加加盟商員工管理(員工管理、部門管理、權限管理)、返點、加盟商子門店管理等功能,而此時的加盟商管理系統只有一個服務,這是不合適的。那微服務的粒度到底拆分到多細比較合適呢?
比如,什么時候拆分加盟商服務比較合適,做加盟商的財務功能時還是加盟商的員工管理功能時?做加盟商的返點功能時還是加盟商的子門店管理功能時?
一般來說,在設計新功能之前,會遵循一個大致原則:根據新的微服務的大小,安排3~4人設計即可。
但是當一個微服務設計出來后,它的改動成本一般不高,除非實現大規模重構。為了防止開發人員出現閑置的情況,公司會安排他們設計新的功能,而設計新功能時,開發人員傾向于將獨立的功能存放在新的服務中,導致加盟商的財務、員工管理及返點功能都被獨立出來了。為了避免這種情況的發生,在對微服務粒度進行拆分時,還需要考慮另外一個因素——績效。
大家都知道,開發人員的績效很難實現量化,而微服務數可謂是一個難得的可量化指標。在規章制度上,雖然不會把微服務數列為一個KPI(這樣微服務數絕對會大增),但是開發人員在闡述個人工作量時偶爾還是會提微服務數,如果其他同事聽后開始留心,潛意識里也喜歡做微服務,隨著時間的推移,微服務就會越來越多,甚至出現人均5個微服務數的情況。
筆者公司后來注意到了這個問題。當這個問題在公開場合提出以后,團隊又開始人為控制微服務數量,這種方式確實起到了一定的效果。
那微服務的粒度大小控制在什么范圍比較合適呢?這就是一個痛點,因為沒有確切的答案。
痛點:沒人知道系統整體架構的全貌
是否碰到過這種情況:每隔幾個月或半年,領導就會讓匯報一下每個部門的微服務數量、公司微服務總數量、每個微服務都用來做什么等情況。因為企業微服務數較多,所以每次給領導匯報時,都有一個很長的清單。
然后領導開始抱怨:“幾百個微服務?系統這么復雜了嗎?誰知道所有系統的全貌?如果出現問題,你們如何快速定位問題?”此時幾個負責人都難以回應,可能在想:“我連自己部門的微服務列表都沒搞清楚。”
筆者在沒有使用微服務的公司,首先會把公司的系統架構全貌搞清楚,之后一旦出現問題,也就容易定位故障點了。可是自從來到使用微服務的公司后,便再也沒有這樣的沖動了,只要熟悉自己負責的部分即可,如果出現問題就臨時學習一下相關系統。
因此,在實際工作中,很難找到了解微服務系統架構全貌的人員,這就是微服務的一個痛點。
痛點:重復代碼多
沒有使用微服務的公司會把所有的代碼放在了同一個工程中,如果發現某些代碼可以重復使用,把這些代碼抽取出來存放在Common包中即可。但是這種代碼設計在微服務中經常會出現問題,這里還是舉個例子說明一下。
比如某個團隊做了一個日志自動埋點的功能,它能自動記錄一些特定方法的調用。其他團隊知道這個功能后,覺得很不錯,想直接拿來用,于是埋點團隊給出了Maven的聲明。但是第一個吃螃蟹的團隊使用后,很快出現了一個JAR版本沖突問題,這時如果他們將沖突的JAR進行升級,原始代碼就不能使用了。為節省人力成本,他們只好詢問埋點團隊如何實現版本兼容。
為了兼容這個團隊的JAR版本,自動埋點團隊又重新設計了一版埋點的JAR,并去掉了一些特定API的使用,兩個團隊終于可以正常使用了。
不過,第三個使用埋點JAR的團隊又匯報了一個JAR版本沖突問題,此時自動埋點團隊從投入產出比角度考慮,不得不放棄維護這個公用的JAR,并直接告知其他團隊:代碼就在Git上,自己直接復制修改吧。因此,這個代碼在不同團隊的微服務中最終存在多個版本。
后來這些團隊在復盤中得出結論:重用JAR本身沒有錯,錯就錯在使用的JAR版本太多了,必須改變這個局面。于是他們將所有JAR版本進行統一的項目正式立項了,但是第二天,因為有緊急業務需求,項目擱置。又過了一段時間,有人提起了這個重要項目,結果因其他的緊急業務需求再次擱置。后來大家逐漸明白,這個項目沒法做,因為投入產出比不高。
其實微服務之間存在重復的代碼也沒關系,因為部門之間的重復代碼比比皆是,而且技術中心每個部門都有自己framework/Common/shared/arc的GitLabsubgroups,它們可以實現對部門內部的通用代碼進行重用。
不過,在當時的項目里,維護這些不多的重復代碼總比統一排期做重構、統一評審JAR版本的成本低得多。
痛點:耗費更多服務器資源
筆者曾在一家小公司做過一段時間技術顧問。該公司原來使用的是單體式架構,一共部署了5臺服務器,后來他們一直抱怨系統耦合性太強,代碼之間經常互相影響,并且強烈要求將架構進行遷移。
于是,他們根據業務模塊把原來的單體式架構拆分成了6個微服務。考慮到高可用,每個服務至少需要部署在兩個節點上,再加上網關層需要兩臺服務器,最終一共部署了15臺服務器(因為其中一個服務比較耗資源,為了安全起見,多加了一個節點)。
在這個拆分過程中,業務沒有變,流量沒有變,代碼邏輯改動也不大,卻多出了9臺服務器,為此,團隊也發生過爭執,當時的爭議點是,如果是這種情形,就不應該一臺服務器只部署一種服務,而是把服務A、B部署在一個節點,服務B、C部署在一個節點,服務A、C再部署在一個節點,如圖12-5所示。

• 圖12-5 混合部署示意圖
可是這個方案很快就被大家否決了,因為如果每個服務器只部署一種服務,服務器的名字直接以服務的名字命名就行,之后運維排查問題時也比較方便。那么,如果把不同的微服務混合部署,服務器又該如何命名呢?
于是有人提出:“要不就這樣吧,反正服務器比較便宜,多幾臺也無所謂。”大家紛紛附和贊同。公司就這樣多了一筆開銷(不過并不是只有小公司這樣做,大公司同樣如此,也經常會有人抱怨服務器資源不夠用)。過了幾天后,CTO召集所有開發人員開會:“這個季度的服務器預算太多了,財務部門審核不通過,你們需要想辦法縮減一下服務器數量。”
會議結束后,大家各自回到工位,開始對每個服務進行檢查,于是就有了下面這段對話。
甲:“這個服務怎么用了這么多臺服務器?很耗資源嗎?”
乙:“不是,主要是公司強制要求我們實現多數據中心部署。”
甲:“這個服務很重要嗎?是內部使用的嗎?”
乙:“是,這個目前只是開發人員在使用。”
甲:“那為什么要做負載均衡?只留一臺吧。”
乙:“好吧。”
甲:“現在我們縮減多少臺服務器了?”
乙:“……”
在筆者任職或合作過的公司中,這種情形很常見,因此不得不說微服務真的很耗服務器。
痛點:分布式事務
分布式事務這個痛點對于微服務來說,簡直就是地獄。為了讓你深刻理解這個痛點,先以一個筆者經歷過的實際下單項目為例。原本的下單流程是這樣的:插入訂單——>修改庫存——>插入交易單——>插入財務應收款單——>返回結果給用戶,讓用戶跳轉。
單體式架構中,只需要把上面的下單流程包含在一個事務里就可以了,如果某個流程出錯,直接回滾數據,并通過業務代碼告知用戶出錯,讓用戶重試即可。
可是遷移到微服務后,因為這幾個流程分別存放在不同的服務中,所以需要更新不同的數據庫,也就需要考慮以下邏輯。
1)某個流程出錯是否需要將數據全部回滾?如果需要,就要在每個流程中寫上回滾代碼。那萬一回滾失敗了呢?是不是還需要寫回滾代碼,回滾代碼算回滾嗎?或者是某些流程回滾,某些流程不回滾?那哪些流程回滾,哪些流程不回滾呢?
2)是否選擇統一不回滾,失敗就重試?這樣豈不是需要做成異步?如果做成異步,會不會出現時間超時?如果超時了,用戶怎么辦?需要回滾嗎?
如果只是某些特定的流程讓人難以抉擇也就罷了,但是這種分布式服務更新數據的場景實在是太多了,如果每個場景都要考慮這些邏輯,團隊肯定更痛苦,而且還要花時間溝通這些邏輯。
因此,針對這種情況,在大部分場景下不考慮回滾和重試,只考慮寫“HAppy Path”,如果報錯就記錄異常日志,再線下手工處理。
這個項目上線后的結果是,機房網絡抖動是常有的事情,以至于數據更新總是出現異常,比如上游數據更新了,下游數據沒有更新,出現了錯誤數據。
使用微服務時,分布式事務一直是痛點也是難點,因此痛定思痛,團隊決定盡快解決這個問題,關于此問題的解決方案將在后面進行說明。
痛點:服務之間的依賴
在設計類時,往往需要遵循類與類之間不可循環依賴的原則,因此最終設計出來的類關系是層次分明的結構,如圖12-6所示。

圖12-6 預期服務關系結構圖
如果把依賴關系轉移至微服務,結果會怎樣呢?先舉個例子。
比如商品系統針對不同門店類型設置不同價格時需要調用門店系統中的類,這時商品系統就依賴了門店系統;同時因門店系統中存在商品庫存,門店系統也就依賴了商品系統的商品信息,從而形成了循環依賴。
再比如最底層的財務系統,從理論上講,它不需要依賴其他系統。而實際上剛好相反,它必須依賴訂單信息,比如費用由什么訂單產生,同時它還需要依賴會員信息和門店信息,比如是誰付的錢和誰收的錢。
因此,隨著需求越來越多,服務之間的依賴關系就會變成千絲萬縷、難以理清的架構,如圖12-7所示。

• 圖12-7 服務關系現實結構
通過圖12-7發現,服務之間的依賴可謂是“你中有我,我中有你”。
那么這種復雜的依賴關系一般會出現什么問題呢?下面先來說說筆者的兩次架構經歷。
1.重構時,為了評估影響面讓各個團隊雞飛狗跳項目組需要重構兩個服務,因為系統已經上線了,為了保證重構不影響業務,就需要在測試過程中評估哪些服務會受影響。
因為之前一段時間線上環境已經出現了兩次一級故障,所以CTO要求此次務必認真評估影響面,不能再出現類似問題。
于是一個Leader提出要求:先根據重構的代碼找到受影響的接口,然后根據接口找到所有調用這些接口的上游代碼,再找到那些調用上游接口的接口,以此類推。
由于該方案分析成本過高,且一旦出現遺漏就會前功盡棄,所以直接被CTO否決了。
最終有人提出了一個較合理的方案:根據全鏈路日志系統中的服務間依賴,找到這兩個服務的所有上游服務及上游的上游服務。
對這個方案進行評估后,項目組發現重構后大半的微服務都會受到影響,于是要上線的那幾天,很多其他團隊的人不得不一起通宵達旦地做回歸測試。
2.重構時,為了不影響其他團隊,出現了很多版本
有了之前的教訓,后續遇到新的重構需求時,項目組就直接把原來的服務abcServiceV1寫成新服務abcServiceV2,此時新的代碼直接調用V2版本,而舊的代碼繼續調用V1版本,有時間再下架abcServiceV1,這樣就不用很多人陪著加班了。
后來V1、V2的形式越來越流行,服務數量出現暴漲。而且在實際開發工作中,開發人員很少在后期下架舊版服務,最終導致服務數量越來越多且新舊版本并存,維護起來更痛苦了。
以上就是服務之間的依賴導致的問題,這個問題的解決方案將在第15章進行講解。
痛點:聯調的痛苦
以往的需求排期是這樣的:需求評審時間——>開發完成時間——>測試完成時間——>上線時間。
遷移到微服務后,需求排期增加了兩個環節:需求評審時間——>接口設計時間——>開發完成時間——>聯調完成時間——>測試完成時間——>上線時間。
在這種變化下,每次遇到比較緊急的需求時,都需要額外問一句:接口文檔好了嗎?聯調怎么樣了?
為什么這么在意聯調?因為在一個軟件項目中,影響項目排期的往往不是技術問題而是第三方依賴問題,一旦涉及溝通、協調等問題就會特別耗時間。
這里舉一個例子簡單說明。
有一次,門店系統正在進行小的需求改動,此時需要商品系統開發人員配合提供一個簡單的接口,而商品系統的開發人員說:“我們正在忙另外一個項目,周二抽空提供這個接口。”
門店系統開發人員簡單評估一下上線周期:周二拿到接口,周三進行聯調,周四、周五測試兩天,應該周五晚上就可以上線了,于是向業務人員進行了相關反饋。但是把門店系統的功能設計好后,因商品系統的開發人員開展的項目臨時修改了一個緊急需求,要求周二務必通宵完成,為此,他們無法在周二給出接口,最終門店系統周五上線的計劃就被延誤了。
而這種事情在實際開發過程中經常發生,對工作效率影響不小。
下面再舉一個例子來說明。
有一次,筆者所在團隊正在做一個涉及30個服務的大項目。周五完成所有需求評審后,首要目標是核對接口文檔。
因為接口文檔是由各個項目組根據實際需求匯總的各自需要提供的接口,總計300多個接口,導致這個過程花了整整兩周時間。
核對完接口文檔后,十幾個項目組之間又開始協調接口聯調時間,這個過程又花了3天時間。對完接口后,各自開發功能的速度就很快了,這個階段也花了兩周時間。
對完接口后,在實際開發過程中接口還會修改嗎?肯定會,而且增加、修改、刪除接口都有可能。但是對完接口后,至少可以保證在大概一致的方向上前進,如果確實需要調整,修改的也只是一些小細節,并不會影響開發進度。
所以這個核對接口的工作是值得的。
功能設計完成后,就需要進行聯調了,而這個過程往往最耗時,因為需要耗費大量的時間在溝通上。可以通過下面這段對話感受一下。
調用方:“addXXX的接口怎么樣了?” 被調用方:“好了,你可以調調看。” 調用方:“不行啊,返回了404。” 被調用方:“哎呀,環境部署錯了,稍等一下。” 調用方:“趕緊的。” 被調用方:“好的。” …… 被調用方:“好了。” 這時,調用方在聯調時發現需要增加一個字段,就說:“addXXX的接口需要增加一個修改時間字段,你幫我加一下。”被調用方:“可以,不過我正忙著另外一個項目,要不明天給你?” 調用方:“別啊,今天必須聯調完。” 被調用方:“那我晚上趕一趕,9點給你行嗎?” 調用方:“好吧。”
所以,在做項目時最麻煩的事情之一就是協調時間,因為它不可控。畢竟每個開發人員的需求優先級都不一樣,除非所有相關項目組的第一優先級都相同,否則協調時間會是一件很讓人頭疼的事情。
而這個大項目共包含300多個接口,也就是說300多個接口都需要協調,這就使得聯調的時間一點不比開發功能的時間少。
關于這個痛點,將在后續中給出解決方案。
痛點:部署上的難題
使用單體式架構時,每個開發人員都想在本地把整個系統部署完后再調試,此時部署方式非常簡單。可是遷移到微服務后,項目經常涉及10個以上的微服務,此時,如果讓開發人員將這些微服務在本地部署完后再聯調,根本無法實現。首先內存很可能不夠,即使內存足夠,也幾乎沒有開發人員會熟悉所有微服務的部署。
為此,可以專門建立一套測試環境供開發人員進行聯調,這樣開發人員就可以將本地正在開發的服務接入聯調環境,如圖12-8所示。

• 圖12-8 聯調環境示意圖
但是,這種架構有時候會出現以下3種問題。
1.聯調環境的數據缺漏非常多
因為聯調接入的服務是本地開發過程中的服務,即數據是開發數據,所以單個服務中的數據不具備完整性。
而且因為是開發環境,導致上下游服務之間沒有調通,比如訂單系統中會出現上下游的單據也不一致、不完整的情況,即不是出現訂單少了收款單的情況,就是出現準入少了審批單的情況。
2.服務調用錯誤
經常會有人發出類似下面的抱怨。
甲:“這個接口怎么有問題?你看,A字段和B字段都缺失了。” 乙:“怎么會呢?我明明加上去了。” 甲:“你是不是忘記部署了?還是部署失敗了?” 乙:“我看看。” 甲:“你是不是調用了XXX的服務?問一下XXX。”過了一會兒,乙過來說:“還真是,他剛好在接入這個服務,我找他 去。”
3.聯調環境極度不穩定
因為開發人員常常需要對聯調中的服務進行部署,或者將不穩定的開發服務接入聯調環境,再加上前面提及單個服務中的數據不具備完整性,所以,在聯調環境下走完整個流程是不太可能的。為此,只能將聯調環境用作接口間的局部聯調。
這就是聯調環境難以部署帶來的痛點,導致太多時間花在協調問題上。那么有沒有一個辦法可以簡單地創建一套相對獨立的測試環境?
小結
講到這里,微服務的9個痛點就講完了。
有一段時間,筆者和同事們都抱怨領導太保守了,總是用一些守舊的技術,我們在公司學不到什么新技術。而筆者帶團隊的時候,組員也經常會反饋這個問題。
從個人立場來說,筆者也的確想盡可能地使用新技術,這樣自身才能成長。但是從公司的角度來說,新舊系統兼容帶來的額外維護成本,新技術的學習、試錯成本,可能是個人感受不到的。但是,當你要對整個團隊的技術負責時,你也會變得保守。
有位同事說得很好:“程序員喜歡用新技術我能理解。但新技術層出不窮,遷移新技術用3年,結果3年后,更新的技術又出來了,這么多的技術負債誰來解決?你學到了新技術,去新公司用這些技術面試獲得更高的崗位,可是舊公司的爛攤子誰來接?”
筆者并不反對使用前沿一些的技術,但對于新技術,要有敬畏心,除了知道它的優點,也要了解它的缺點。
所以這一章花了很長的篇幅來介紹筆者團隊使用微服務時碰到的痛點。那么,關于微服務的優勢前面只講了5點,而微服務的痛點講了9點,為什么還要使用微服務?如果使用單體式架構的話,隨著業務的復雜化,將會出現無論怎么加人都無法迭代的情況。而如果使用微服務,雖然它存在很多問題,但是至少可以通過增加人力的方式來保持迭代。原因就這么簡單,跟那些痛點無關。
微服務架構的痛點介紹完了,接下來開始講一些進階的微服務實戰場景,看看如何解決本章提到的這些痛點。






