跳转到主要内容
工程的博客

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的平台,在Scalbob体育客户端下载a中,管理我们的弹性“裸EC2”集群的实例与100年代和10000年代的核心。基本上一个手工詹金斯,但与所有我们想要的东西,没有我们不希望的一切。大约10 k-loc Scala,用来验证所有拉请求合并成砖的主要存储库。

砖Runbot是一个定制的CI的平台,在Scala中,管理我们的弹性bob体育客户端下载

Runbot利用巴泽尔的构建图取决于选择性地拉上运行测试请求代码改变,目标有意义的词结果返回给开发人员尽快。Runbot还集成了其他我们开发的基础设施:

  • 我们故意把Runbot CI测试环境和Devbox远程开发环境尽可能相似,甚至运行相同的ami,试图避免场景的代码在一个或其他表现不同。
  • Runbot巴泽尔的工人实例充分利用远程缓存,允许他们跳过“样板”构建步骤,只重新编译和测试的事情可能是受到拉力要求。

更详细深入Runbot系统可以在博客中找到发展中砖的Runbot CI的解决方案

测试碎片

测试碎片让开发人员轻松地旋转hermetic-ish Databricks-in-a-box,让你通过浏览器或运行集成测试或手动测试API。砖是一种多重云产品支持亚马逊/天蓝色/谷歌云平台,砖的考试碎片同样可以旋转上任何云给你一个集成测试和手工测试的代码更改。bob体育客户端下载

测试碎片让开发人员轻松地旋转hermetic-ish Databricks-in-a-box,让你通过浏览器或运行集成测试或手动测试API。

测试数据砖平台的碎片或多或少包含整个-我们所有的后端服务减少了资源分配和一些简化基础设施。bob体育客户端下载大多数都是Scala服务,虽然我们有一些其他的语言混合在一起。

维护数据砖”测试碎片是一个持续的挑战:

  • 我们的测试碎片是为了准确地反映当前的生产环境尽可能的高保真。
  • 作为测试使用碎片作为迭代开发循环的一部分,创建和更新他们应该尽可能快。
  • 我们有数百名开发人员使用测试碎片,这是不可行的旋转为每一个全尺寸的生产部署,我们必须找到方法来走捷径,同时保留忠诚。
  • 我们的生产环境是快速发展的,新的服务、新的基础设施组件,有时甚至是新云平台,我们的测试碎片必须跟上。bob体育客户端下载

测试碎片需要大规模和复杂的基础设施,我们达到我们意想不到的各种局限性的存在。你会怎么做当你耗尽资源组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为脚本运行时并不重要,许多其他平台的好处仍然适用:bob体育客户端下载

  • 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并非没有挑战或问题,但也不会其他语言或平台。bob体育客户端下载大型组织运行的动态语言不可避免地投入巨大的努力加速他们或添加编译时检查;大型组织在其他静态语言不可避免地把精力dsl或其他工具来加速开发。尽管Scala没有遭受的问题,它有其自身的问题,我们必须付出努力去克服。

感兴趣的一点是仿制我们的许多工具和技术。我们的CI系统,devboxes远程缓存,不Scala-specific测试碎片等。都是我们的战略管理或产品毛羽的依赖。这些应用的不管语言或平台,造福我们的开发人员编写的Python或打印稿或c++编写Scalabob体育客户端下载。结果发现Scala不是特别;Scala开发人员面临许多相同的问题开发人员使用其他语言的脸,和许多相同的解决方案。

另一个有趣的是单独的砖是如何从Scala的其余部分生态系统;我们从来没有买到“活性”的心态和“hardcore-functional-programming”心态。我们做事情喜欢cross-building,依赖管理,产品毛羽非常不同于大多数在社区。尽管如此,甚至因为这样,我们已经能够规模Scala-using工程团队没有问题,获得的好处使用Scala作为一个跨组织的通用语。

关于Scala砖不是特别的教条。我们首先是大数据工程师,基础设施工程师和产品工程师。我们的工程师想要更快的编译时间,更好的IDE支持,或者更清晰的错误消息,通常不感兴趣推动Scala语言的限制。我们使用不同的语言,他们是有意义的,无论是通过Jsonnet配置管理,机器学习在Python中,或在c++高性能数据处理。随着业务和团队的增长,这是不可避免的,我们看到某种程度的差异和碎片。然而,我们收获的好处一个统一的平台和工具在Scala在JVM上,并希望伸展,尽可能长久受益。bob体育客户端下载

砖是最大的Scala的商店在这些天来,越来越多的团队和业务增长。如果你认为我们的方法一般Scala和发展产生了共鸣,你应该肯定来与我们合作!

免费试着砖

相关的帖子

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