公司有項目是基於 Scala 編寫的,與之配套的構建工具是 SBT , 它是 Simple Build Tool 的縮寫,雖然我以爲它一點也不簡單。html
這個項目有一個很大的痛點就是刷新依賴 (對應 SBT 的 update)很是之耗時,能夠參見下圖:java
注意圖中紅框部分,耗時1266秒,近半個小時。在刷新期間資源佔用也很高,致使電腦很卡 (風扇還呼呼呼的轉,溫度蹭蹭蹭的長)。git
最關鍵的是因爲依賴的不少服務升級很快 (幾乎天天都有升級),因此這個操做天天也會持續不少次,不可思議耗費在這方面的時間是何其之多。github
人生苦短,在刷新了幾回以後,我再也受不了這漫長的等待時間,因而開始了這漫漫的優化之路。shell
正所謂工欲善其事必先利其器apache
不知道你們遇見這種問題會怎麼作,我反正是二話不說打開 Google 直接搜: SBT 依賴下載慢。緩存
還別說,有共鳴的人還很多, 總結了下幾乎都是如下的解決方案網絡
- 添加代理
- 添加國內鏡像源
我這確定不是源的問題啊,我司用的私有倉庫,既然私有jar都下載下來了,確定是走的私有倉庫啊。app
翻了幾頁,沒有滿意的答案,也試了幾個方案,也沒啥用。maven
看來仍是得本身從問題的根源開始找起啊......
爲了保險起見, 我仍是先排查一下是否是鏡像問題, 項目的 build.sbt
配置文件中是有私有倉庫的相關配置項的:
lazy val commonSettings = Seq(
//....
// ... 私有倉庫
resolvers := {Resolver.url("xr-ivy-releasez", new URL("http://nexus.xxxx.com/repository/ivy-releases/"))(Resolver.ivyStylePatterns) +: resolvers.value},
resolvers := { {"xr-maven-public" at "http://nexus.xxxx.com/repository/public/"} +: resolvers.value},
// ....
)
複製代碼
此時我突然想到一種狀況:難道是默認走的公共庫,在公共庫找不到依賴纔會走私有庫 ?
爲了驗證猜測,我使用 wireshark 抓包進行分析,過濾器指定協議 http (由於倉庫是走的http)
還能夠指定 ip.src 和 ip.dst 從而使得數據包更加符合咱們的要求
而後打開 sbt shell 進行 update 操做
觀察抓包結果
發現訪問的都是 /repository/public/***
的請求,對應的 Host 也是我司的私有庫,這說明配置是生效了的,並且都是從私有倉庫進行下載。
可是我也發現了一些404的請求
好吧,是我司的私有庫沒有 /repository/ivy-release
,果斷將對應的倉庫配置去掉,省去不必的請求。
雖然走了私有庫,可是我每次刷新都會請求倉庫,這就不符合道理了,難道 SBT 連基本的依賴緩存都沒有 ?
對抓到的數據包進行再次過濾,只看 Http Request
,發現請求的都是 SNAPSHOT
版本的依賴庫, 參見下圖
這說明 SBT 是有緩存的,由於正式版都沒有請求倉庫,但是爲何 SNAPSHOT
每次都去請求遠程倉庫呢? 難道是 SNAPSHOT
被區別對待,不會被緩存?
既然 SBT 是基於 Ivy 的,那就從 Ivy下手。
我在Ivy 的官網(ant.apache.org/ivy/history…緩存的表格:
Attribute | Description | Required |
---|---|---|
default | the name of the default cache to use on all resolvers not defining the cache instance to use | No, defaults to a default cache manager instance named 'default-cache' |
defaultCacheDir | a path to a directory to use as default basedir for both resolution and repository cache(s) | No, defaults to .ivy2/cache in the user's home directory |
resolutionCacheDir | the path of the directory to use for all resolution cache data | No, defaults to defaultCacheDir |
repositoryCacheDir | the path of the default directory to use for repository cache data. This should not point to a directory used as a repository! | No, defaults to defaultCacheDir |
注意關鍵字 defaultCacheDir
, 這個就是 Ivy 的緩存目錄,對應路徑爲用戶目錄下的 .ivy2/cache
。
個人是 mac, 對應目錄就是 ~/.ivy/cache
, 果不其然,進入該目錄查看一下:
在 ~/.ivy/cache
下發現了不少依賴庫的目錄, 下面就須要驗證一下有沒有緩存 SNAPSHOT
的版本了, 以我司的 user-client 4.1.2-SNAPSHOT
爲目標進行查找:
從圖中顯示,目錄中明明有緩存 SNAPSHOT
的啊,可爲何不走本地緩存呢 ?
這沒辦法了,只能去 SBT 官網找答案了,在官網文檔找到了 Dependency Management ,看名字彷佛和依賴管理有關,
其中的 Cached-Resolution 彷佛和緩存相關, 並且開頭就是下面這段話
To set up Cached Resolution include the following setting in your project’s build:
updateOptions := updateOptions.value.withCachedResolution(true)
說的是要配置緩存解析,那就得加上 updateOptions := updateOptions.value.withCachedResolution(true)
的配置, 這也太簡單了吧?
無論啦,先加上試試。
加配置,刷新,抓包一鼓作氣, 然而結果慘不忍睹
看着一頁頁的請求發出去,此刻我是奔潰的!賊子安敢欺我!
正在我想靜靜之際,SBT 刷新完成,我一不當心瞄了一眼,耗時竟然只有之前的1/4 了?
我靠,怎麼肥四(回事)?不是沒生效嗎,怎麼時間縮短了這麼多?
爲了確保不是眼花,我又重啓刷新了幾回,發現耗時相差無幾,並且我發現若是不重啓直接update,通常耗時都只有幾秒,個人天啦。
不死心的我又去看了下文檔,原來是我對這個配置理解錯了,這個配置的意思並非說 SNAPSHOT
就不請求遠程倉庫了。
這裏的緩存指的是sbt啓動後第一次執行update後,會緩存全部的依賴解析信息, 也就是說緩存是和進程相關的。
而個人項目是有4個子項目,每一個子項目都共同依賴了 service
模塊, 該模塊維護着幾乎全部的依賴。
當第一個項目 update 後,其餘三個項目 update 時都會直接走緩存了,這也是爲何耗時只有最開始1/4。
真實無意插柳柳成蔭啊......
雖然如今時間只要之前的1/4了,可仍是要5分鐘啊,這絕對不是一個能夠將就的數字!
並且還有另一個很是重要的緣由,由於窮!
此話怎講?由於 SBT 一直啓動着太耗內存了,我這可憐的 8G 可得省着點兒。但是停掉 SBT,緩存就得從新構建了,因此是窮激發了個人進一步探索......
再次思考一下:爲何 SNAPSHOT
依賴每次啓動都要去遠程倉庫拉取呢 ? 能不能只在依賴的版本有更新的時候再去拉取呢 ?
在文檔 Cached-Resolution中, 發現了關鍵詞 SNAPSHOT and dynamic dependencies,其中對 SNAPSHOTR
和緩存作了一些描述:
When a minigraph contains either a SNAPSHOT or dynamic dependency, the graph is considered dynamic, and it will be invalidated after a single task execution. Therefore, if you have any SNAPSHOT in your graph, your experience may degrade.
說的是依賴關係中若是有 SNAPSHOT
版本,會致使某個子依賴關係緩存失效, 而這個子依賴就是動態的,反正就是不會走緩存的意思。
既然得知問題的根源是由於使用了 SNAPSHOT
, 若是不使用 SNAPSHO
不就沒這個問題了嘛。
然而現實是骨感的,公司內部幾十個服務大多數都用的 SNAPSHOT
做爲版本號,並且各類互相依賴,短期內是不可能直接過渡的了,因此直接PASS該方案了。
只能繼續在文檔中摸索,發現一個相關配置
updateOptions := updateOptions.value.withLatestSnapshots(false)
這個配置的做用是什麼呢?
由於 SBT 能夠配置多個遠程倉庫源(經過 Resolver),默認狀況下 SBT 會從全部的遠程倉庫去拉取指定版本的 SNAPSHOT
依賴, 而後比對它們的發佈時間,取最新的那一個。
經過配置 withLatestSnapshots(false)
能夠禁用該策略, 這樣 SBT 就直接使用從遠程倉庫拉取到的第一個 SNAPSHOT
依賴。
加上配置而後測試,發現網絡請求數確實少了,總體update耗時減小了一分鐘左右,可是這個會致使沒法拉取到同版本的最新SNAPSHOT
由於快照在不改變版本的狀況下是能夠重複發佈的,區分同版本不一樣快照就只能按照時間戳來了。
SBT 沒法肯定本地的快照是最新的,因此每次啓動都會去倉庫拉取最新快照。
使用 withLatestSnapshots(false) 後就不會取最新的,而是直接取第一個。
不取最新的 SNAPSHOT
對咱們影響不大, 由於咱們內部的服務若是有改動,基本就會升級版本號(就算是 SNAPSHOT
), 不多有一直重複發同版本的SNAPSHOT
的狀況。
這麼一說,彷佛咱們連用
SNAPSHOT
的意義都不大了,然而歷史緣由......
雖然有所提高,可是最關鍵的問題,SNAPSHOT
每次 update 都會走網絡請求的問題仍是沒解決。
只能繼續在文檔中掙扎,還好黃天不負有心人啊, 在官方文檔 Cache And Configuration 一節找到了相關內容
When
offline := true
, remote SNAPSHOTs will not be updated by a resolution, even an explicitly requested update. This should effectively support working without a connection to remote repositories. Reproducible examples demonstrating otherwise are appreciated. Obviously, update must have successfully run before going offline.
文檔說若是配置了offline := true
, 是不會從遠程倉庫更新 SNAPSHOT
的依賴了,這不正是咱們要的東西嗎?
可是後面又說了,更新必須在進入離線模式以前就完成,這句話的意思是否是離線模式下我連版本升級也作不到呢?
只有本身動手了才知道,在不升級版本的狀況下,加上配置再次進行 update 並抓包, 沒有任何的請求到達倉庫了
再來看看最終的更新時間
只須要一分鐘不到,此刻我得先壓制心裏的狂喜,再驗證一下在 offline := true
的狀況下,升級版本是否會從遠程倉庫請求?
隨意修改了一個庫的版本,而後重啓 sbt 執行 update, 發現是成功從遠程倉庫拉取到了的,哈哈,一切都不是問題!
意外老是伴隨着驚喜同時到來,在我隨後的使用中卻又發現了另外的問題:若是 SBT 的第一次update完成之後, 我隨後修改依賴的版本,在不重啓SBT的狀況下再次執行update,是讀不到最新的依賴版本的。
初步猜想是和緩存有關係的,可是問題也不大了,就算更新依賴版本而後重啓 SBT 進行 update, 耗時也不過1分鐘左右 ,比最開始的半小時已經好多了。
要不,我把這個問題留給大家了?
最後從30分鐘到1分鐘實際上就是在 build.sbt
加了兩行配置
offline := true,
updateOptions := updateOptions.value.withCachedResolution(true).withLatestSnapshots(false)
複製代碼
整個分析問題的思路也很簡單,就是先找到問題根源,再去找解決方案。
在尋找解決方案的時候通常都是搜索引擎,文檔或者源碼,正常狀況下文檔應該都能解決問題了,這期間我就繞了很多彎路,我甚至曾去看了 SBT 的 Resolver 的源碼, 如今看來,絕對是跑偏了。
整個解決過程並無多麼高深莫測甚至能夠說是無聊至極,由於大部分時間都是看文檔並驗證其配置。
不過仍是那句話:工欲善其事必先利其器