跳轉到主要內容
工程的博客

Scala規模磚

通過李Haoyi

2021年12月3日 工程的博客

分享這篇文章

成百上千的開發者和數百萬行代碼,磚是最大的Scala的商店。這篇文章將會是一個廣泛的Scala在磚,從一開始到使用,風格,工具和挑戰。我們將討論的話題從雲基礎設施和周圍的人工流程定製語言工具來管理我們的大型Scala代碼庫。從這篇文章中,您將了解所有或大或小,使得Scala磚工作,對任何人都有用的案例研究組織支持使用Scala的增長。

使用

磚是由Apache火花™,原創者,開始為分布式Scala集合。Scala被選中,因為它是為數不多的可序列化的lambda函數的語言,因為其JVM運行時允許簡單的互操作與Hadoop-based大數據的生態係統。從那時起,火花和磚已經遠遠超出了任何人的最初的想象。這種增長的細節超出了本文的範圍,但最初的Scala基礎依然存在。

語言分解

Scala是今天的通用語在磚。看著我們的代碼庫,最流行的語言是Scala,數以百萬計的線,其次是Jsonnet(配置管理),Python(腳本、ML PySpark)和打印稿(Web)。我們使用Scala無處不在:在分布式海量數據處理,後端服務,甚至一些CLI /膠水代碼工具和腳本。磚不反對寫non-Scala代碼;我們也有高性能的c++代碼,一些詹金斯Groovy, Lua運行Nginx,去其他的事情。但是大型的代碼仍然在Scala中。

語言分解

Scala的風格

Scala是一個靈活的語言;它可以寫成一個類java麵向對象語言、Haskell-like函數式語言,或者類似python腳本語言。如果我有描述Scala寫在磚的風格,我想把它Java-ish 50%, Python-ish 30%, 20%的功能:

  • 後端服務往往嚴重依賴於Java庫:網狀的,碼頭,傑克遜,AWS /天藍色/ GCP-Java-SDK等等。
  • Script-like代碼通常使用的庫com-lihaoyi生態係統:requests-scala os-lib upickle等等。
  • 我們使用函數式編程的基本特性:函數字麵量,不可變數據,案例類層次結構、模式匹配、收集轉換,等等。
  • 零的使用“典型的”Scala框架:玩,Akka, Scalaz,貓,f.t.等等。

雖然整個代碼庫Scala風格不同,但一般仍是介於better-Java type-safe-Python風格,和一些基本的功能特性。新磚一般沒有任何問題閱讀代碼即使零Scala背景或培訓,可以立即開始作出貢獻。磚的複雜係統都有自己的理解和貢獻的障礙(編寫大型高性能多重雲係統是不平凡的!)但足夠學習Scala生產力通常不是一個問題。

Scala熟練

幾乎每個人都在磚寫一些Scala,但很少有人愛好者。我們沒有正式的Scala培訓。人們有各種各樣的背景和編寫Scala第一天,慢慢挑選更多的功能特性隨著時間的推移。結果Java-Python-ish風格的自然結果。

盡管幾乎每個人都寫一些Scala,大多數人在磚不要太深入的語言。人們首先基礎設施工程師、數據工程師毫升工程師,產品工程師,等等。偶爾,我們必須深入探討應對棘手的東西(如陰影、反射、宏等),但這是遠遠超出大多數磚的規範工程師需要處理。

當地的工具

總的來說,大多數住在mono-repo磚代碼。磚使用巴澤爾的構建工具的一切mono-repo: Scala, Python, c++, Groovy, Jsonnet配置文件,碼頭工人容器,Protobuf代碼生成器,等。鑒於我們從Scala開始,這曾經是所有SBT,但我們主要遷移到巴澤爾為其更好地支持大型代碼庫。我們仍然保持一些較小的開源回購SBT或磨,和一些代碼並行巴澤爾/ SBT構建我們試圖完成遷移,但大部分我們的代碼和基礎設施是建立在巴澤爾。

巴澤爾在磚

巴澤爾是大型團隊的優秀。它是唯一的構建工具,運行所有的構建步驟和測試在單獨的LXC容器默認情況下,這有助於避免意想不到的部分構建之間的相互作用。默認情況下,它是平行和增量,是越來越重要的代碼庫的規模增長。一旦建立和工作,它傾向於相同的工作在每個人的筆記本電腦或構建機器。雖然不是100%的,實際上它是足夠好,很大程度上避免巨大的類inter-test幹擾相關的問題或意外的依賴性,這是保持代碼庫的構建可靠的關鍵。我們討論使用巴澤爾並行化和加速測試運行在博客快速並行測試與巴澤爾在磚

巴澤爾的缺點是它需要一個大的團隊。從python-generating-makefiles巴澤爾封裝了20年的進化,它顯示:有很多積累的繁瑣和鋒利的邊緣和複雜性。盡管它一旦建立,有效配置巴澤爾來做你想做的事是一種挑戰。基本上,你需要一個2 - 4人團隊專門從事巴澤爾把它運行良好。

此外,通過使用巴澤爾你放棄很多現有的開源工具和知識。告訴你一些圖書館pip安裝什麼東西嗎?提供了一個SBT / Maven / Gradle /機插件一起工作?一些可執行的希望apt-get安裝愛德華嗎?巴澤爾可以使用這些,需要自己編寫大量的集成。任何單獨的集成並不是太難,你經常需要很多人,這加起來成為相當重要的時間投資。

雖然這些缺點是可以接受的成本更大的組織,這讓巴澤爾總不獨奏和小型團隊項目。甚至磚仍有一些開源的代碼庫的SBT或機巴澤爾沒有意義。大部分的代碼和開發人員,然而,他們都是在巴澤爾。

編譯時間

Scala編譯速度是一種常見的問題,我們把重要的努力緩解該問題:

  • 建立巴澤爾Scala編譯使用一個長壽的背景工人讓編譯器編譯JVM熱,快。
  • 設置增量編譯(通過鋅)和並行編譯(通過九頭蛇)在選擇的基礎上為那些想使用它。
  • Scala 2.12的升級到最新版本,這比以前的版本要快得多。

更多細節在博客的工作Scala快速構建與巴澤爾在磚。雖然Scala編譯器仍然不是特別快,我們的投資在這意味著Scala編譯時間不是我們的工程師所麵臨的難點。

交叉建築物

交叉建築物是Scala的另一個共同關心團隊:Scala是二進製主要版本之間的不兼容,這意味著代碼為了支持多個版本都需要單獨編譯。甚至忽略了Scala,支持多個火花版本也有類似的要求。磚的Bazel-Scala集成cross-building建成,每一個構建目標(相當於一個“模塊”或“子項目”)可以指定一個Scala版本支持列表:

cross_scala_lib (base_name =“my_lib”,cross_scala_versions = [“2.11”,“2.12”),cross_deps = [“other_lib”),src = [“Test.scala”),)

與上麵的輸入,我們cross_scala_lib函數生成my_lib_2.11my_lib_2.12版本的構建目標、依賴於相應的other_lib_2.11other_lib_2.12目標。Scala有效,每個版本都有自己的子圖指出構建目標在大巴澤爾的構建圖。

Scala有效,每個版本都有自己的子圖指出構建目標在大巴澤爾的構建圖。

這種風格的複製cross-building構建圖有幾個優點cross-building更傳統的機製,其中包括一組全局配置國旗在構建工具(例如,+ + 2.12.12在SBT):

  • 不同版本的相同的構建目標自動構建和測試並行,因為他們都是同一個大巴澤爾的一部分構建圖表。
  • 開發人員可以清楚地看到Scala版本的構建目標支持。
  • 我們可以同時使用多個Scala版本,例如,部署一個多jvm在Scala 2.12應用程序後端服務與火花司機在Scala 2.11。
  • 我們可以逐步推出新的Scala版本的支持,大大簡化了遷移沒有“大爆炸”開通以來從舊到新的版本。

雖然這技術cross-building來自我們自己的內部構建的磚,它傳播其他地方:對構建工具的cross-build支持,甚至舊SBT構建工具通過SBT-CrossProject

管理第三方依賴關係

第三方依賴預處理和反映;依賴項決議從“熱”中移除edit-compile-test路徑和隻需要重新運行如果你更新/添加一個依賴項。這是一個常見的模式在磚的代碼庫。

每一個外部下載位置我們使用不可避免地下降;無論是Maven中央片狀,PyPI停機,甚至www.7-zip.org回到500年代。不知何故,這似乎沒有關係我們正在下載什麼來自:外部下載不可避免地停止工作,導致停機時間和挫折對於磚開發人員。

我們鏡子的方式類似於lockfile的依賴關係,常見的一些生態係統:當你改變一個第三方依賴,你運行一個腳本,該腳本更新lockfile最新解決依賴關係。但我們添加一些轉折:

  • 而不是僅僅依賴版本記錄,我們鏡子各自的依賴我們的內部包存儲庫。因此我們不僅避免根據第三方包主機版本解決但我們也避免根據他們下載。
  • 而不是記錄一個平坦的依賴項列表,我們還記錄它們之間的依賴關係圖。這允許任何內部構建目標取決於第三方包拉在傳遞依賴完全沒有接觸網絡。
  • 我們可以管理多個互不相容的依賴性在同一個代碼庫解決多個lockfiles。這給了我們靈活處理不相容的生態係統,例如,火花2.4和3.0火花,同時還能夠保證隻要有人堅持從單個lockfile依賴性,他們不會有任何意外衝突的依賴。

這種方式管理外部依賴給我們兩全其美。,

正如你所看到的,而“maven /更新”的過程來修改外部依賴(虛線箭頭)需要訪問第三方包回購,更常見的“巴澤爾構建”的過程(實心箭頭)完全在代碼和基礎設施,我們控製的地方。

這種方式管理外部依賴給我們兩全其美。我們得到了細粒度的依賴項解析工具像Maven或SBT提供,同時提供固定依賴lock-file-based工具像Pip或Npm提供版本,以及運行自己的包的hermeticity鏡子。這不同於大多數開源軟件構建工具如何管理第三方依賴關係,但在很多方麵它更好。供應商以這種方式依賴是更快,更可靠,更不可能受到第三方服務中斷的影響比正常的方式直接使用第三方包存儲庫作為構建的一部分。

產品毛羽工作流

也許最後一個有趣的部分我們當地的發展經驗是產品毛羽:事情可能一個好主意,但有足夠的例外,你不能把它們變成錯誤。這一類包括Scalafmt Scalastyle,編譯器警告,等來處理這些,我們:

  • 不執行短絨在當地發展,這有助於簡化開發循環保持它快。
  • 實施合並到主時短絨;這將確保代碼的主人是高質量的。
  • 為場景提供逃生艙口的短絨是錯誤的,需要被否決了。

這種策略適用於所有短絨,就與次要句法差異(例如,/ / scalafmt:vs/ / scalastyle:vs@SuppressWarnings逃生出口)。這警告從終端的瞬態的東西滾動過去長期存在的工件出現在代碼:

@SupressWarnings(數組(“匹配可能詳盡的”)val targetCapacityType=fleetSpec.fleetOption匹配{情況下FleetOption.SpotOption (_)=>“現貨”情況下FleetOption.OnDemandOption (_)=>- - - - - -需求”}

所有這些儀式在產品毛羽的目的是迫使人們注意線頭錯誤。就其本質而言,短絨總是假陽性,但大部分時間,他們強調實際代碼味道和問題。迫使人們沉默短絨與注釋部隊作者和評論家認為每個警告並決定是否它真的是假陽性還是強調一個真正的問題。這種方法還避免了常見的故障模式警告堆積在控製台輸出理會。最後,我們可以更積極地推出新的短絨,即使沒有100%精度的假陽性總是可以覆蓋後適當的考慮。

遠程基礎設施

除了在本地運行在您的機器上的構建工具,Scala開發磚是由幾個關鍵服務。這些運行在我們的AWS開發和測試環境和開發在磚工作取得進展的關鍵。

巴澤爾遠程緩存

巴澤爾遠程緩存的概念很簡單:永遠不會編譯兩次同樣的事情,全公司。如果你編譯編譯你的同事在他們的筆記本電腦,使用相同的輸入,您應該能夠僅僅下載工件他們編譯。

巴澤爾遠程緩存的概念很簡單:永遠不會編譯兩次同樣的事情,全公司

巴澤爾的遠程緩存是一個特性構建工具,但需要一個支持服務器實現巴澤爾遠程緩存協議。當時,沒有良好的開源實現,所以我們建立自己的:一個小golang服務器之上GroupCache和S3。這大大加快工作,特別是如果你在最近的主版本的增量更改,幾乎一切都被編譯已經通過一些同事或CI機器。

巴澤爾遠程緩存不是一帆風順。這是另一個服務我們需要照顧。有時壞工件緩存,導致構建失敗。盡管如此,巴澤爾遠程緩存的速度優勢是足夠的開發過程,我們的生活不能沒有它。

Devbox

磚Devbox的想法很簡單:在本地編輯代碼,運行它的結實的雲VM共存與你所有的雲基礎設施。

一個典型的工作流是在Intellij編輯代碼,運行bash命令devbox構建/測試/部署。下麵你將看到的devbox行動:每次用戶編輯的代碼在IntelliJ,綠色的“滴答”圖標在菜單欄簡要地閃回前一個藍色的“同步”圖標閃爍綠色,表明同步完成:

Devbox有定製高性能文件同步器將代碼更改從您的本地電腦遠程VM。連接到fsevents在os x和inotify在Linux上,它可以實時響應代碼更改。當你從你的編輯器控製台,單擊代碼同步,隨時可以使用。

這一係列的優勢發展中在你的筆記本電腦:

  • Devbox運行Linux,這是與我們的CI環境,接近我們的生產環境中開發人員的mac osx筆記本電腦。這有助於確保你的代碼的行為相同的在dev, CI和刺激。Devbox運行Linux,這是相同的磚CI環境和接近我們的生產環境比開發商的mac osx筆記本電腦。
  • 與我們Kubernetes-clusters Devbox住在EC2,遠程緩存,docker-registries。這意味著大devbox之間的網絡性能和任何你關心的事情。磚Devbox生活在EC2 Kubernetes-clusters /遠程緩存/ docker-registries。這意味著任何你關心的偉大的網絡性能。
  • 巴澤爾/碼頭工人的那個不需要與IntelliJ / Youtube視頻群聊的係統資源。你的筆記本電腦沒有那麼熱,你的粉絲不旋轉起來,您的操作係統(主要是磚開發者的mac osx)就不會延遲。磚,巴澤爾/碼頭工人的那個不需要與IntelliJ / Youtube視頻群聊的係統資源。
  • Devbox是可定製的,可以運行任何EC2實例類型。想RAID0-ed短暫的磁盤文件係統性能更好?96核和384 gb的RAM來測試compute-heavy嗎?就去做吧!我們不使用的時候關閉實例,因此更昂貴的情況下不會打破銀行用於短時間內。磚,Devbox是可定製的,可以運行任何EC2實例類型。
  • Devbox是一次性的。apt-get安裝錯了?不小心rm一些係統文件你不應該嗎?一些第三方安裝程序讓您的係統在一個糟糕的國家嗎?它隻是一個EC2實例,所以扔掉它,得到一個新的。

做事的速度差Devbox是戲劇性的:multi-minute上傳或下載減少幾秒鍾。需要部署Kubernetes嗎?上傳集裝箱碼頭工人注冊嗎?下載二進製文件從遠程緩存大嗎?這樣做在Devbox 10 g數據中心網絡訂單的大小比這樣做你的筆記本電腦在家裏或辦公室的無線網絡。即使本地計算/ disk-bound工作流通常更快運行在Devbox相比開發人員的筆記本電腦上運行它們。

Runbot

Runbot是一個定製的CI的平台,在ScalBeplay体育安卓版本a中,管理我們的彈性“裸EC2”集群的實例與100年代和10000年代的核心。基本上一個手工詹金斯,但與所有我們想要的東西,沒有我們不希望的一切。大約10 k-loc Scala,用來驗證所有拉請求合並成磚的主要存儲庫。

磚Runbot是一個定製的CI的平台,在Scala中,管理我們的彈性Beplay体育安卓版本

Runbot利用巴澤爾的構建圖取決於選擇性地拉上運行測試請求代碼改變,目標有意義的詞結果返回給開發人員盡快。Runbot還集成了其他我們開發的基礎設施:

  • 我們故意把Runbot CI測試環境和Devbox遠程開發環境盡可能相似,甚至運行相同的ami,試圖避免場景的代碼在一個或其他表現不同。
  • Runbot巴澤爾的工人實例充分利用遠程緩存,允許他們跳過“樣板”構建步驟,隻重新編譯和測試的事情可能是受到拉力要求。

更詳細深入Runbot係統可以在博客中找到發展中磚的Runbot CI的解決方案

測試碎片

測試碎片讓開發人員輕鬆地旋轉hermetic-ish Databricks-in-a-box,讓你通過瀏覽器或運行集成測試或手動測試API。磚是一種多重雲產品支持亞馬遜/天藍色/穀歌雲平台,磚的考試碎片同樣可以旋轉上任何雲給你一個集成測試和手工測試的代碼更改。Beplay体育安卓版本

測試碎片讓開發人員輕鬆地旋轉hermetic-ish Databricks-in-a-box,讓你通過瀏覽器或運行集成測試或手動測試API。

測試數據磚平台的碎片或多或少包含整個-我們所有的後端服務減少了資源分配和一些簡化基礎設施。Beplay体育安卓版本大多數都是Scala服務,雖然我們有一些其他的語言混合在一起。

維護數據磚”測試碎片是一個持續的挑戰:

  • 我們的測試碎片是為了準確地反映當前的生產環境盡可能的高保真。
  • 作為測試使用碎片作為迭代開發循環的一部分,創建和更新他們應該盡可能快。
  • 我們有數百名開發人員使用測試碎片,這是不可行的旋轉為每一個全尺寸的生產部署,我們必須找到方法來走捷徑,同時保留忠誠。
  • 我們的生產環境是快速發展的,新的服務、新的基礎設施組件,有時甚至是新雲平台,我們的測試碎片必須跟上。Beplay体育安卓版本

測試碎片需要大規模和複雜的基礎設施,我們達到我們意想不到的各種局限性的存在。你會怎麼做當你耗盡資源組Azure賬戶嗎?在AWS負載均衡器的創建成為瓶頸?當豆莢使Kubernetes集群的數量開始行為不端?而“磚”,聽起來很簡單,100年代的實用性提供這樣一個環境中開發人員是一個持續的挑戰。很多創造性的技術用於處理上麵的四個約束條件,保證磚的經驗的開發人員使用測試碎片仍然盡可能順利。

磚目前運行數百個測試碎片分布在多個雲平台和地區。盡管維護這樣的環境的挑戰,測試碎片是沒有商量餘地的。他們提供了一個至關重要的集成和手工測試環境代碼合並到主前運到登台和生產。

好的部分

Scala / JVM性能通常是偉大的

磚沒有短缺的性能問題,一些過去和一些正在進行的。然而,幾乎所有這些問題是由於Scala或JVM。

這並不是說磚有時沒有性能問題。然而,他們往往是在數據庫中查詢,在rpc或整個係統架構。雖然有時一些沒有有效地編寫應用程序級別的代碼會導致經濟放緩,這類事情通常是簡單的整理與分析器和重構。

Scala讓我們寫一些令人驚訝的高性能代碼,例如,我們的Sjsonnet配置編譯器數量級的速度比c++實現它取代,正如前麵討論在我們的博客寫一個更快Jsonnet編譯器

但總的來說,Scala的主要好處/ JVM的性能良好我們如何看待計算我們的Scala代碼的性能。而在大規模分布式係統性能可能是一個棘手的話題,我們的Scala代碼的計算性能運行在JVM上的不是一個問題。

一個靈活的通用語很容易分享工具和專業知識

能夠分享工具在整個組織中是偉大的。我們可以使用相同的構建工具集成,IDE集成、分析器、短絨,代碼風格,等後端web服務,我們的高性能大數據運行時,我們的小腳本和可執行文件。

即使代碼風格變化在整個組織中,所有相同的工具仍然適用,它足夠熟悉,語言沒有障礙的人跳。

人力是有限的時這一點尤為重要。維護一個工具鏈與上述收集豐富的工具已經是一個大的投資。即使我們有少量的語言,很明顯,“次要的”語言工具鏈是Scala不像我們的拋光工具鏈,並將它們的難度水平是明顯的。要複製我們的Scala工具鏈投資N次支持各種不同的語言將是一個非常昂貴的努力我們迄今得以避免。

Scala是出人意料的好腳本/膠!

人們通常認為Scala語言編譯器或嚴重的業務™的後端服務。然而,我們還發現Scala是一個優秀的語言script-like膠水代碼!,我的意思是代碼的子流程,與HTTP api,矯直JSON,等等。而高性能Scala的JVM為腳本運行時並不重要,許多其他平台的好處仍然適用:Beplay体育安卓版本

  • Scala是簡潔。根據您所使用的庫,它可以比“傳統”甚至更簡潔像Python或Ruby腳本語言,和同樣是可讀的。
  • 腳本/膠水代碼往往是最難的單元測試。集成測試,而可能的,通常是緩慢而痛苦的;我們已經不止一次第三方服務節流我們運行集成測試太多!在這樣的環境中,有一個基本的編譯時檢查是天賜之物。
  • 部署好:組裝罐子比Python pex要好得多,例如,當他們更標準,簡單,密封,高性能,等。試圖將Python代碼部署在不同的環境中持續的頭痛,與某人釀造安裝apt-get安裝荷蘭國際集團(ing)的東西會導致我們的部署和測試了Python可執行文件。這不會發生在Scala組裝jar。

Scala為腳本/ JVM並不完美:有一個0.5 1 s JVM啟動開銷對於任何有價值的程序,內存使用率高,和編輯的迭代循環/編譯/運行一個Scala程序是相對比較緩慢的。然而,我們發現,有很多的好處使用Scala像Python這樣傳統的腳本語言,我們引入了Scala的場景自然有人會期待一種腳本語言被使用。甚至Scala的REPL已經被證明是一個有價值的工具,用於與服務交互、內部和第三方,在一個方便的和靈活的方式。

結論

Scala在磚已經被證明是一個堅實的基礎的基礎

Scala並非沒有挑戰或問題,但也不會其他語言或平台。Beplay体育安卓版本大型組織運行的動態語言不可避免地投入巨大的努力加速他們或添加編譯時檢查;大型組織在其他靜態語言不可避免地把精力dsl或其他工具來加速開發。盡管Scala沒有遭受的問題,它有其自身的問題,我們必須付出努力去克服。

感興趣的一點是仿製我們的許多工具和技術。我們的CI係統,devboxes遠程緩存,不Scala-specific測試碎片等。都是我們的戰略管理或產品毛羽的依賴。這些應用的不管語言或平台,造福我們的開發人員編寫的Python或打印稿或c++編寫ScalaBeplay体育安卓版本。結果發現Scala不是特別;Scala開發人員麵臨許多相同的問題開發人員使用其他語言的臉,和許多相同的解決方案。

另一個有趣的是單獨的磚是如何從Scala的其餘部分生態係統;我們從來沒有買到“活性”的心態和“hardcore-functional-programming”心態。我們做事情喜歡cross-building,依賴管理,產品毛羽非常不同於大多數在社區。盡管如此,甚至因為這樣,我們已經能夠規模Scala-using工程團隊沒有問題,獲得的好處使用Scala作為一個跨組織的通用語。

關於Scala磚不是特別的教條。我們首先是大數據工程師,基礎設施工程師和產品工程師。我們的工程師想要更快的編譯時間,更好的IDE支持,或者更清晰的錯誤消息,通常不感興趣推動Scala語言的限製。我們使用不同的語言,他們是有意義的,無論是通過Jsonnet配置管理,機器學習在Python中,或在c++高性能數據處理。隨著業務和團隊的增長,這是不可避免的,我們看到某種程度的差異和碎片。然而,我們收獲的好處一個統一的平台和工具在Scala在JVM上,並希望伸展,盡可能長久受益。Beplay体育安卓版本

磚是最大的Scala的商店在這些天來,越來越多的團隊和業務增長。如果你認為我們的方法一般Scala和發展產生了共鳴,你應該肯定來與我們合作!

免費試著磚

相關的帖子

看到所有工程的博客的帖子
Baidu
map