一個線上 Maven 詭異問題排查過程

å. 前言

如今的大部分 Java 應用基本都是經過 Maven 進行組織的,不管是分佈式應用仍是單體集羣應用每每都會經過一個 父 POM 加若干子 POM 完成項目的組織。然而這種多應用多模塊的拆分就帶來了一個巨大的體力成本 --- 發包git

舉個例子,說明下爲何會出現這種狀況:程序員

image.png

上面這個圖中有兩個應用 portal 和 dump,其中 portal 的四個包是須要對外引用的也就是說 client 、domain、common、log 這幾個包是兩個應用共享的二方包。而共享不可避免的會帶來競爭!redis

簡單分析會有以下的問題:數據庫

  1. 多應用發佈**:**dump 中須要在 domain 中添加一些類和方法勢必會致使 portal 應用跟着發佈一次,將代碼合併到基線markdown

  2. **版本錯亂:**多分支開發時你們使用的 snapshot 版本號不一致,不是在處理衝突就是在處理衝突的路上app

  3. **上線換包:**應用發佈前須要將全部代碼切成同一個正式版,在將代碼中全部引用版本的地方一一替換dom

ß. Maven 依賴機制(Dependency Mechanism)

爲了解決上面遇到的種種問題,怎麼作才能讓這種頻繁的 發包替換版本解決衝突 的流程更加簡便自動化呢?簡單來說個人思路是 集中式版本控制!是否是聽着很耳熟,和大名鼎鼎的 git 的思路恰好相反,接下來就一塊兒來看如何讓流程優雅起來以及踩到 Maven 的一些大坑後又是如何一步步爬起來的。maven

在此以前咱們先看看 Maven 項目究竟是如何對模塊和包進行組織的。分佈式

image.png

首先建立一個 Maven 項目,而後在經過上圖的三步你就能完成一個新模塊的建立。ide

image.png

結果你會獲得如上圖所示的一個父 POM 和兩個子 POM。

2.1 父子 POM

父 POM 核心內容以下:
image.png

分爲兩個部分,一個部分是父 POM 的聲明,包含 GAV 座標,打包方式必須爲 POM,由於須要使用聚合模型,另一部分就是父工程管理的子模塊 modules 標籤。

子 POM 相對要更簡單:

image.png

聲明本身的父模塊是誰,以及本身的 GAV 座標,可能細心的你發現了這裏他並無寫 GroupId 和 Version 這是由於父工程已經聲明瞭,若是沒有特別的版本號和 groupId 的要求直接繼承父工程的內容。

2.2 依賴傳遞

Maven 支持經過父 POM 中的依賴繼承的方式避免開咱們手動指定依賴庫的版本。可是傳遞依賴會致使依賴圖迅速增加的特別大,因此 Maven 對於傳遞依賴有必定的限制:

  • 當依賴了多個版本的組件時 Maven 只會選擇其中一個版本做爲依賴,而選擇的策略稱爲:nearest definition 最短路徑

  • 依賴自動引入: 當 A 依賴了 B 而 C 依賴了 A 那麼 C 組件會自動引入 B 組件

  • 依賴排除:這個理解起來就很簡單 ,若是不想引入自動引入的一些依賴能夠經過 ,排除依賴的手段將其去掉

2.3 依賴範圍

依賴項的範圍決定了何時這些依賴會被加載進去,在 Jar 瘦身等操做的時候特別有用,同時解決依賴衝突也是一把好手

  • compile 這個是默認值,也就是沒有寫做用域的依賴項在編譯和運行階段都會被加載到類路徑

  • provided 這個和 compile 很是相似只是他僅在編譯和測試階段被加載,運行時不會。例如咱們經常使用的 Servlet API 這個 jar 僅僅是在編譯測試須要,運行時 Tomcat 早已爲咱們準備好了這個 Jar ,若是加了反而會可能致使類衝突

  • runtime 此範圍表示編譯時不須要依賴項,可是執行時須要依賴項,例如數據庫的驅動

  • test 這個基本都是一些跑單測會依賴的 Jar

  • system 從參與度來講,和 provided 相同,不過被依賴項不會從maven倉庫抓,而是從本地文件系統拿,必定須要配合systemPath屬性使用。

當前項目爲 A,A依賴於B,B依賴於C。知道B在A項目中的scope,那麼怎麼知道C在A中的scope呢?這個就須要根據 nexus 的一張表來肯定:

image.png

好比 A 依賴 B 的範圍爲 provided ,B 依賴 C 的範圍爲 runtime 的 最終 A 依賴 C 的範圍爲 provided

ç. 大坑

在回到咱們一開始提出的問題,若是團隊裏三我的開發同一個應用,你們都須要修改二方包的版本號,分支合併必定會衝突。同時引用這個二方包的應用也必定會衝突,由於你們使用的版本號通常都不一樣,那麼以誰的爲準?誰來解決這個衝突?每每由於版本號的問題致使衝突合併半小時應用都不必定能夠構建的起來。

同時在發佈上線的時候要改包爲正式包,須要替換不少個地方,你們的版本還須要一致,每每須要解決多個地方的版本衝突。

爲了解決這個問題,我採用了以下的方案:

  1. 你們在同一個環境開發的時候版本號永遠都保持統一,好比在預發你的包版本只能是 pre0-snapshot 不然分支提交不上去

  2. 全部的包版本都收束到主 POM 中,禁止單獨在每一個 POM 中單獨聲明要發佈或依賴的二方包

改造先後,主 POM樣子以下:

image.pngimage.png

子 POM 中就不在單獨聲明版本號了 而是直接繼承父 POM 中定義的版本號:

image.png

這樣確實很好的解決了上面的兩個問題,可是在某次部署過程當中遇到了一個很是詭異的問題。

咱們項目結構以下:

ProjA
|  -- Apache Commons 3.0
|________
|        Proj B's Client
|        | -- mq-client
|        | -- redis-client
|        | -- etc.
|
|________
         Server
         | -- Server Libraries
         | -- etc.
複製代碼

A 工程引用了 B 工程的 client 包,而其 client 包中引入了 mq 和 redis 的客戶端,所以 A 工程在不用引入這兩個包的狀況下能夠直接使用這兩個包中的類。可是在某次部署的過程當中,A 工程怎麼都找不到 mq 和 redis 的類文件,這就讓人摸不着頭腦了,線上都是能夠的,爲什麼預發就有這個問題了???

∂. 溯源

又到了緊張而又刺激的問題排查階段了。從 mvn 倉庫上下載了最新的編譯後的包放到 jad 中發現代碼都是和個人分支保持一致的,沒有啥問題,並且看到 snapshot 包後面的時間戳也是我發佈包的時間戳。

那也就是發包的過程和結果都沒啥問題,確定是拉包的時候出問題了唄,看看拉包的過程是否有異常。

mvn clean && mvn install -fn 
複製代碼

一套命令跑下來,好像也沒有 error,可是包就是拉不下來。看看日誌裏面有什麼貓膩吧!一頓日誌的搜查發現了一行 waring 日誌:應用引入的依賴包無效,依賴包中傳遞依賴項不可用,能夠經過開啓debug獲取更多信息。

[WARNING] the POM for A is invalid, transitive dependencies (if any) will not be available, enable debug logging for more details...
複製代碼

開啓maven debug功能後,警告後緊跟了一條錯誤信息,以下。

[WARNING] The POM forxx:jar:1.0-SNAPSHOT is invalid, transitive dependencies (if any) will not be available: 2 problems were encountered while building the effective model for xx:1.0-SNAPSHOT
[ERROR] 'dependencies.dependency.version' for xx:jar is missing.
[ERROR] 'dependencies.dependency.version' for xx:jar is missing.
複製代碼

transitive dependencies 這玩意不就是依賴傳遞麼,我已開始還不知道遇到的這個問題如何用文字向搜索引擎描述,如今顯然就是傳遞依賴的一些包沒有被引入啊,這不就找到問題所在了, 由於下面有兩個包沒有聲明 jar 的包版本。

可是爲什麼會出現這個問題呢?根據上述報錯的關鍵字我在 stackoverflow 中找到了答案:

One reason for this is when you rely on a project for which the parent pom is outdated. This often happens if you are updating the parent pom without installing/deploying it.

To see if this is the case, just run with mvn dependency:tree -X and search for the exact error. It will mention it misses things you know are in the parent pom, not in the artifact you depend on (e.g. a jar version).The fix is pretty simple: install the parent pom using mvn install -N and re-try

上面短短几句話即說明了緣由也給出瞭解決方案,美利堅的程序員果真牛皮!描述的大體意思就是由於這個二方包的父 POM 用的是老版本里面沒有包含一些傳遞依賴的 jar 包的版本致使不少包拉不下來。解決方案也很簡單直接把父 POM 中的依賴版本號加上並從新打包發佈下就行了。

回顧上面說的組件的傳遞依賴,這裏的二方包中依賴的 redis 和 mq 的 client 包沒有拉下來是由於二方包 POM 中的某個 jar 的版本號即沒有在父 POM 中定義也沒有在二方 POM 中定義。二方包在找組件的依賴的時候首先會在本 POM 找,若是沒有找到就會根據

<parent>
        <artifactId>module-test</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
複製代碼

聲明的父 POM 的版本號去父 POM 中找,由於父 POM 用的老版本里面根本沒有那個包的版本號因此就報了剛纔那個錯誤。

因此若是要發佈新的二方包並且想要使用傳遞依賴的特性的話必定要從新發布父 POM !

相關文章
相關標籤/搜索