為什麼要打造多租戶的企業級Maven私有倉庫服務?
在Java的世界中,我們通常使用Maven的依賴體系來管理構件(artifact,又稱為二方庫或三方庫)的依賴。Maven倉庫用于存儲這些構件。一般的遠端倉庫(比如Maven Central)隻提供下載下傳功能。而使用者想要管理自己的私有二方庫,就隻能搭建Maven私服。常用的Maven私服軟體有Nexus和Artifactory等。Maven私服是很多企業都需要的功能。
如今雲服務越來越深入人心,使用者不僅紛紛把自身的服務釋出到雲端,甚至也會使用雲服務來管理自己的整個研發流程。而如果有一個企業級的Maven私有倉庫服務,使用者隻需要簡單的開通服務,就可開箱即用,無需自己搭建,免去了維護的煩惱。
現有Maven私庫軟體的問題
打造這樣的服務首先可能會考慮借助已有的一些Maven私庫解決方案進行改造。Nexus和Artifactory的開源版雖然支援多租戶,但由于其缺乏高可用特性,無法進行水準拓展,單節點的穩定性達不到要求。Nexus和Artifactory的商業版本具有高可用特性,但單叢集支援的使用者數始終有上限。
如下是Nexus的商業版本的高可用架構:

雖然其商業版本支援高可用,但仍具有以下問題:
- 商業版本按照使用者數和使用時間收費,花費較大;
- 每個節點是有狀态的,通過inter-node replication的方式進行狀态同步,擴縮容有限制;
- 單個節點支援的倉庫數量是有性能限制的,超過限制後需要對叢集對sharding處理,進一步增加系統的複雜度。
Artifactory的商業版也具有類似的問題。
高可用的Maven私庫服務的理想架構
既然現有的軟體無法滿足要求,那麼隻有通過自研的方式來自己實作一套高可用的私有倉庫管理系統了。一個理想中的私有倉庫管理系統應該是這樣子的。
- 每個節點都實作了标準的Maven倉庫服務規範,他們彼此之間是無狀态的,通過負載均衡向外暴露服務,這樣進行擴縮容會更加的平滑,便于支撐起海量的服務;
- 使用對象存儲服務作為建構的存儲倉庫,比如阿裡雲的OSS。這樣所有節點都可以共用存儲,無需擔心容量及浏覽問題,有效的解決IO瓶頸;
- 私有倉庫的中繼資料都存放在于應用無關的資料庫中,而索引資訊則存儲在Elastic Search中,資料庫和Elastic Search都進行了叢集化高可用配置。
實作标準的Maven倉庫服務規範
抛開倉庫管理、使用者及權限管理、系統管理等功能來說,一個最小可用的Maven倉庫服務僅需要滿足支援Maven用戶端進行上傳和下載下傳的功能。也就是說私有倉庫服務暴露的http URL需要滿足一定的布局,具體可參見[Repository Layout - Final] (
https://cwiki.apache.org/confluence/display/MAVENOLD/Repository+Layout+-+Final)一文。首先需要了解兩個概念。
Maven依賴
Maven依賴是我們在建構項目時使用到的構件(artifact),也就是常說的二方庫和三方庫。比如我們項目想使用fastjson元件時,需要在項目中的pom.xml檔案中加入如下依賴:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
如上所示,在Maven世界中确定一個構件需要三個參數:groupId,artifactId,version。這三個參數構成了Maven世界中的坐标。
主構件和次要構件
其實除了這三個參數外,還有另外兩個參數。第一個是packaging。該參數定義了Maven項目的打包方式,可能的值有pom, jar, maven-plugin, ejb, war, ear, rar, par等,預設值為jar,是以在上述的依賴中并不需要填寫packaging值。第二個是classifier。classifier用來擴充構件内容。它可以是任意字元串,經典的兩個值為javadoc和sources。比如你通路
http://maven.aliyun.com/nexus/service/local/repositories/central/content/com/alibaba/fastjson/1.2.47/,傳回的内容中除了fastjson-1.2.47.jar還有fastjson-1.2.47-sources.jar和fastjson-1.2.47-javadoc.jar檔案。fastjson-1.2.47.jar被稱為主構件(Primary Artifacts),而定義了classifier的構件被稱為次要構件(Secondary Artifacts)。
如果想擷取次要構件可以這樣寫。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
<classifier>sources</classifier>
</dependency>
其實Maven倉庫主要就是用來存儲管理這些構件。Maven和Gradle之類的用戶端通路Maven倉庫時,進行上傳和下載下傳構件時要求倉庫URL滿足一定的布局。
倉庫URL布局
當上傳或下載下傳依賴時,Maven倉庫的URL布局為:
/$groupId[0]/../$groupId[n]/$artifactId/$version/$artifactId-$version.$extension
如果classifier不為空的話(也就是說該構件為次要構件),則URL布局為:
$groupId[0]/../$groupId[n]/$artifactId/$version/$artifactId-$version-classifier.$extension
在這裡會把groupId依據“.”分割為目錄。比如com.alibaba會變為com/alibaba。fastjson構件的jar的倉庫位址為:
/com/alibaba/fastjson/1.2.47/fastjson-1.2.47.jar
而fastjson的次要構件javadoc的倉庫位址為:
/com/alibaba/fastjson/1.2.47/fastjson-1.2.47-javadoc.jar
每個主構件都應該有一個POM檔案與之對應:
/$groupId[0]/../${groupId[n]/$artifactId/$version/$artifactId-$version.pom
次要構件則無需單獨的POM檔案,它會引用主構件的POM檔案。
Maven會為每個構件(包括pom檔案)生成md5和sha1校驗檔案,放置在同級目錄下。比如fastjson-1.2.47.jar的校驗檔案為fastjson-1.2.47.jar.sh1和fastjson-1.2.47.jar.md5。
隻要實作了這樣的URL布局就可以滿足一個最小化的maven倉庫需要具備的功能。
後端對象存儲服務化改造
根據日常的統計來看,Maven倉庫服務的下載下傳數量遠遠大于上傳數量。為了提供更好、更快的服務,我們可以對下載下傳進行一系列的優化。優化以前下載下傳構件的流程是這樣的。
從上圖中可以看出,當下載下傳構件時,倉庫服務需要先從對象存儲服務下載下傳資源,然後再轉發給用戶端。整個過程不僅耗時,而且會對倉庫服務節點的性能造成影響。經過仔細分析以後,我們發現其實倉庫服務無需從對象存儲服務下載下傳構件,隻需要收到用戶端的請求後,給用戶端傳回一個302狀态碼,并生成一個預先簽名的對象存儲服務下載下傳連結。這樣用戶端就可以直接通過對象存儲服務下載下傳所需的構件。
新的實作方式無需倉庫服務和對象存儲服務進行資料流的互動,IO壓力都交給了更專業的對象存儲服務,下載下傳速度可以提高數倍。
除此之外,還可以進行進一步的優化,即在對象存儲服務之前加上一層CDN服務,這樣可以保證不同的地區的用戶端的下載下傳體驗都是一緻的。
最後
目前這套架構已經替換了
maven.aliyun.com,現在maven.aliyun.com運作的如絲般平滑。如果你需要使用可以自行上傳下載下傳的maven私有倉庫服務,可以通路我們的
雲效版。