前序
額,十分遺憾,這次並不是分享BUG了,所以不能讓大家看到我出糗的樣子了,而且,這次也沒有太多技術性的內容,多少會顯得有些枯燥乏味。不過呢,可能本次所涉及到的項目遷移拆分方案,在諸位看來也並非完美,所以各位還是有機會批評一波,娛樂一波。
背景
話不多說,我們先來談談這次這次項目遷移拆分的背景。
經典模型
我們先來看看目前大多數微服務框架的系統架構,這里以Dubbo為RPC服務基礎,並且用傳統的電商業務模型(熟知我的觀眾肯定知道,我向來是保護公司業務代碼的,所以這里使用大家廣泛熟知的業務模型為例)。 簡單講解下:
- 這是一件簡單的電商業務模型,在微服務業務細分下,分為用戶服務、商品服務、交易服務、倉儲服務、售后物流服務、財務服務。(要是具體細分以及其他服務的就不在這里多說)
- 微服務架構采用Dubbo作為服務治理框架,各個服務通過Dubbo進行RPC通信。
- 各個服務當中涉及到的實體類、工具類、Dubbo接口等放在公共接口服務當中(我也不知道該怎么說,其實就是一個公共的jar包,不能說是一個服務,但現在姑且這么稱呼吧),由各自業務服務web工程進行引用,其實就是在maven的pom文件當中引用。
以上可能是最簡單的一個模型,當然在業務、架構上各個公司都有自己的方案以及擴展之類的,這就不再考慮之內,但是其實核心還是圍繞着這個最原始的模型。
當前現狀
那么現在遇到了什么問題?我們再來看看這樣一個模型。各自的業務服務web工程都會對這個公共服務進行依賴,這本身也沒什么問題,畢竟這里面放的都是一些Dubbo接口等,具體的實現類還是在各自的服務工程當中。但是問題來了,不知道從什么時候開始,這個模型發生了變化,就是所有的實現類都寫在了這個公共接口服務里面。
由於這本身就是多模塊的maven工程,於是各自的web工程引用自己所需要的那一部分module。於是所有的web工程只是一些不同環境profile下的配置文件。這個就與上面的模型的初衷背道而馳。如圖所示:
產生影響
那么這樣會有什么問題:
- 項目結構的問題。所有人的代碼都在這個公共接口服務當中,使得結構變得很混亂,對於web工程甚至會引用不屬於自己的module,例如商品服務當中引用了財務的實現類,本質上走的是dubbo協議,web工程反而引入了一些多余的jar,甚至還會引起maven的依賴優先的問題,造成事故的風險點。(maven的依賴優先指的是引用不同module但是包含了同一個jar的不同版本的情況,maven采用依賴最短路徑優先原則,這個有機會再之后的文章中細說)
- 代碼管理的問題。由於不同模塊的代碼都在這個公共服務當中,在合並代碼的時候會產生大量沖突。例如商品服務的同學提交代碼進行Merge的時候會發生產生大量的沖突,這些沖突基本上是非自己業務模塊,或交易、或倉儲、或其他業務模塊等。在理想模型下,也就是在我第一個圖片當中,那么進行merge產生的沖突也應該是各自業務模塊的代碼沖突,這個是免不了。(導致合並代碼最后演化成copy code的模式,或者使用Git提供的cherry-pick,但是本質上其實也是高級版的copy code,而無法使用正常的merge)
- 部署管理的問題。既然大家都把代碼合並在這個公共服務當中,那么在CI部署的時候(不管是gitlab上還是Jenkins),編譯打包這個過程就十分緩慢。原本這些編譯打包的負擔可以落在各自業務web服務中,現在反而都集中在這一個服務當中。隨着日后業務代碼的不斷增多,這個時耗將會變得越來越長,甚至一旦有代碼出錯導致整個部署發布都失敗。
這個是目前觀察下來最為頭疼的幾個痛點。這個事情我從去年一直不斷反饋,這個公共服務需要進行拆分。但是一是由於一些部門層面上的原因,二是業務發展吃緊,也沒有人也沒有這個時間去主導或者去推動跟進這個事情,導致這個事情一拖再拖,於是就陷入了一種惡性循環的地步。
直到這次雙十一,因為大促期間無法發布,所以也就沒有需求迭代。於是我再次提出這個事情,這次總算是有時間去推進這個事情。(我還是認為,既然已經發現諸多的痛點,還是應該想着提前去根治,否則這遲早是一把達摩克利斯之劍,要么在一開始就應該防止這種事情的發生)
實施前
那么現在想想,應該怎么去入手這個事情。(是不是覺得這個事情看上去好像沒啥技術含量,但是突然就不知道怎么做,我當時還以為是大佬們去做這個事情,但是大佬們有其他優化,所以就變成誰提出,誰解決。當然,做程序員嘛,還是以解決問題為主)
分析目的
我們的目的是將業務具體的實現類(這里主要是service層的實現和controller層的接口)遷移至各自的web工程。那么,首先想到的是是否可以直接從公共服務當中刪去那些代碼,然后新增到各自的web工程呢,或者通過git操作進行合並。那么我覺得,如果只是從做法上考量,兩種答案當然都是可以。但是呢,要是這么簡單的話,我今天也就沒有必要寫這篇文章了。我們想想,要是真的就這么做了,會有哪些問題:
- 第一種,如果是刪除代碼,移動代碼的話,這種做法我覺得是效果最差的。因為雖然你代碼是實現了轉移,但是一並的也失去了git上面的commit記錄,其次還無法保留項目當中的各個分支。
- 第二種,似乎效果是好了一點,但是由於工程結構的不一樣,在通過Git操作之后會有大量的沖突。網上也有很多的類似操作,但是由於網上提供的demo業務場景比較單一過於簡單,就像這種git如何合並兩個項目?,而且實際上的操作也比這種demo復雜且難實施,風險點過大不可控。
項目分支
可能很多人會奇怪,既然我是遷移項目,為什么還要保留分支。首先,我們要明確,如果是那種幾個月前的開發分支,或者只是為了合並代碼生成的合並分支,這種分支對於整個工程來說,意義不大。在遷移的時候重要的分支其實就是分為兩類:
- 現階段正在開發的開發分支,例如feature/abc;
- 具有重要里程碑意義的分值,例如灰度環境: gray/abc,或者是特殊分支: vip/abc,亦或者是預發分支: release/abc,還有就是這種備份分支: remaster/abc等等。
為此這類分支就要毫無保留的進行遷移,保留對代碼工程的維護性。
當然我們公司還存在着另外一個特殊性,給這件事情造成極大的困難。由於我們公司是一家To B企業,所從事的業務也是面向企業的,所以對不同類型的公司就會進入到不同的環境,我們稱之為“灰度環境”(雖然個人認為稱為這里灰度與實際意義上的灰度意思有點不符,我所認為的灰度可能更多還是傾向於小流量用戶所處環境,且用於新功能測試階段的灰度過渡,這個可能就是To B與To C的不同吧)。於是在我們公司就會存在許多“灰度環境”,這個給推進這個項目遷移工作帶來極大的阻力。
我之所以在這里介紹這么多篇幅,是因為這個確實是造成了很大問題:
- 首先,既然是項目遷移拆分,那么肯定不是一個晚上全部改掉完成,想想有這么多環境的存在;而且就算通宵改造,那么就不怕出現風險嘛!這個鍋背不起。
- 其次,如果改造的話,那么公共接口服務當中具體的實現類肯定都是要刪掉的(git上刪除),剩下一個光禿禿的公共接口,那么各位開發同學在開發的時候,肯定會從這個光禿禿的公共接口服務的master切出分支。正常情況下是一個環境一個環境的進行更迭,那么當有緊急bug需要修復的時候,從這個光禿禿的服務中切出的hotfix分支合並到還未改造的環境的時候,就會把對應環境的你那個服務上的代碼都刪除了,但是那個環境由於還沒有進行改造,而此時又發生刪除情況,那么這顯然就會出事。除非在某一時刻全部改造完成來避免這種情況,例如上面1中所說。如圖所示:
實施方案
那么難道就真的無濟於事了嗎?后來我仔細考慮了下,既然一步到位的事情做不到,那么是不是就可以繞一下曲線救國。於是我尋思着,既然不能直接改造,那我是不是每一個業務服務可以重新鏡像出一個公共接口服務,將依賴從原先的公共接口服務轉移到這個公共接口服務鏡像當中,由於各自業務都有自己的鏡像,那么只需要對各自的鏡像進行修改,這樣改動與影響就小很多了。如圖所示: 這樣做有如下好處:
- 既然是復制鏡像的做法,你那么整個Git工程當中的commit記錄和所有的分支都保留。
- 每一個業務模塊都可以對自己的鏡像進行增刪改,而不會影響到其他業務模塊。
- 每個環境迭代發布,只需要將maven引用轉移到公共接口服務鏡像即可。
我們來看看接下去的環境迭代: 這樣就可以避免上面的問題,對於未遷移的環境和已經遷移的環境,都可以很好的兼容。
最后的問題
但是這里還有一個問題,不知道各位觀眾有沒有留意到,那么原先的公共接口服務,該怎么處理呢。這里也會面臨是否刪除代碼的問題:
- 如果刪除代碼,但是這樣又跟剛才說的第2點的問題是一樣了;
- 如果不刪除代碼,那么原先的公共接口服務當中和鏡像當中的實現類就會重復,這樣兩者進行編譯的時候,后一個編譯打包的就會覆蓋前者,也會存在問題。
那么怎么辦呢?這個還是主管給我提供了思路,由於原先公共接口服務的maven坐標和鏡像的maven坐標是一樣的,那么就需要將鏡像的maven坐標進行改名,然后讓web工程引用鏡像的maven坐標,這樣,即便同一個實現類在公共接口服務和鏡像中都存在,但是web工程實際上加載的只會是鏡像當中的class,這樣就能解決這種問題。
實施中
既然上面已經將思路與方案都已經闡述清楚,那么接下來進行實施,這個過程就相對容易點了,就好像高樓大廈,架子已經搭建好了,只需要搬磚即可。
由於各個業務都是由各個業務小組進行推進,業務遷移拆分也有先后順序,那么就以商品服務的拆分為例來展開:
- 通知各個開發,主要是商品服務開發人員,在統一時刻進行分支提交,因為需要將公共接口服務進行鏡像復制。這是很關鍵一點,不然當復制出鏡像之后,尚有分支還在原先公共接口服務當中,那么這個分支上的功能代碼的遷移就會十分雞肋。
- 在鏡像當中只保留商品服務的實現類,包括controller、service實現類、business,dao等,其余非商品服務代碼都進行刪除,commit並且push。
- 修改maven坐標,進行改名,這里的改名最快的方法就是修改groupId,例如原先叫com.abc.abc,那么現在修改為com.abc.xyz,而artifactId不變,依舊保持語義,例如abc-item。
- version:set。這個就是在對應環境中進行部署打包,正式環境肯定是deploy。
- 在商品服務的web工程中,將涉及到依賴商品實現類的maven坐標進行修改,其實就是修改groupId就好,這樣web工程就引用到鏡像而不是原先舊的公共接口服務。
- 發布,檢查啟動是否報錯。當然,我是在鏡像當中加了一個心跳請求controller,來驗證請求是否走到了鏡像當中的代碼。
那么對於其他開發同學的情況,我們這邊也需要進行考慮一下:
- 非商品業務開發: 原則上,這些開發的改動是不能夠觸及商品業務的代碼。但是由於之前都是在一個公共接口服務當中,這一點就很難保證。這種情況的話,要么予以駁回,要么就是配合copy code將這些代碼復制到進行當中;
- 商品業務開發: 在切出鏡像之前,有些同學已經有了業務開發的分支,那么這種情況下,只需要將鏡像的master分支進行反向merge即可,因為master分支是只保留了商品的業務代碼,其他代碼都已經進行了delete,在反向merge之后,也一並會將那些代碼進行刪除;而在鏡像已經切出來之后,那么如果需要開發新功能或者進行hotfix的話,那么只需從鏡像checkout出新的分支即可。
實施后
在遷移拆分之后,需要進行簡單驗證。這里我以商品服務為例,在鏡像當中的Controller層寫一個心跳請求:
@RequestMapping(value = "/healthCheck", method = RequestMethod.GET) @ResponseBody public Object healthCheck() { return successResponse(); } 復制代碼
那么如果可以請求到這個接口,說明商品web服務工程已經成功引用了鏡像里面的module,其余業務模塊也都可以用這個方法監測。
小結
總的來說,可能本次項目遷移拆分的工程,難度更多的還是偏向管理方面的考量。雖然我不太清楚現在其他公司的項目架構是否有遇到這樣的瓶頸,但就我身邊朋友所處小公司的項目,也多少可以窺見接下來的趨勢也會遇到相同的問題。其實也不難理解,在前期還是以發展業務為主(肯定是賺錢要緊,不然誰來發工資),只是我覺得,任何前期小型項目演化成一個巨無霸的時候,一定要意識到項目對於現在團隊建設所影響到的方方面面,因為細節決定成敗不只是書上說說的道理。
另一方面,在我們不能一蹴而就的情況下,需要進行一定程度上的曲線救國,通過引入中間環節來解決問題。正如上面所述的解決方案中,通過引入鏡像以及更改maven坐標的方式,來間接實現遷移拆分的效果,並且在這種多環境的場景中也能很好的實現兼容,並且在業務中將代碼實現了隔離,大大增強了系統的可維護性。
最后
如果我的文章對你有所幫助,還希望各位大佬點個關注\color{red}{點個關注}點個關注 點個贊\color{red}{點個贊}點個贊,再次感謝大家的支持!
這里也附上我的Github地址:https://github.com/showyool/juejin.git
作者:showyool
鏈接:https://juejin.im/post/6893685346228240398
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。