云原生场景下实现编译加速

原生,场景,实现,编译,加速 · 浏览次数 : 70

小编点评

**文章简介:** 文章介绍了在云原生场景中实现流水线的缓存技术,以提高用户体验。通过使用远程缓存管理构建缓存,可以实现相同项目的编译依赖复用,并平均加速流水线3~5倍。 **主要内容:** 1. **云原生流水线缓存技术** - 容器运行完是不会保存数据的,每当流水线重新运行时,又会重新拉取代码、编译代码、下载依赖包等等。 - 在云原生场景下,不存在本地宿主机编译代码、构建镜像时缓存的作用,大大延长了流水线运行时间,浪费很多时间、网络和计算成本。 2. **缓存技术** - 使用 zstd 进行压缩,以提高压缩效率和速度。 - 针对不同语言的编译原子,默认开启缓存。 3. **缓存最佳实践** - 使用自定义镜像进行缓存,可以实现相同项目的编译依赖复用。 - 可以设置缓存key和缓存目录,实现缓存命中率提升。 4. **缓存默认保存时间** - 缓存默认保存 7 天。 5. **缓存增量配置** - 可以自定义缓存开启和关闭,设置缓存key,实现不同语言编译原子增量缓存。 6. **缓存未来规划** - 将支持不同编译原子的配置选项提供给用户。

正文

作者:京东零售 王雷

背景

云原生下的流水线是通过启动容器来运行具体的功能步骤,每次运行流水线可能会被调度到不同的计算节点上。这会导致一个问题:容器运行完是不会保存数据的,每当流水线重新运行时,又会重新拉取代码、编译代码、下载依赖包等等。在云原生场景下,不存在本地宿主机编译代码、构建镜像时缓存的作用,大大延长了流水线运行时间,浪费很多不必要的时间、网络和计算成本。

在许多流水线场景中,同一条流水线的多次执行之间是有关联的。如果能够用到上一次的执行结果,则可以大幅缩短执行时间。为了提高用户使用流水线的体验,我们加入支持缓存的功能,挂接远程储存管理构建缓存,可以实现同一个项目的编译依赖复用,在同一条流水线的多次运行中,共享同一份缓存。

目标

通过实现云原生流水线的缓存技术,实现代码编译的缓存复用,平均加速流水线3~5倍;

实现方案

我们将需要进行缓存的文件,使用zstd的方式进行压缩,通过远程挂载cfs,将构建的缓存持久化到cfs上的指定位置。当下一次构建开始的时候,判断缓存是否被命中,如果命中缓存,我们从cfs上的指定位置pull对应的缓存压缩包,解压到相应目录下。

所用工具-cfs+zstd

非用户自定义镜像,将需要的工具打到引擎的基础镜像中,作为所有镜像的基础工具。

用户自定义镜像,不和用户镜像进行强绑定,如果需要使用缓存功能,可以使用Restore缓存原子和Save缓存原子,设置缓存key和缓存目录,实现缓存功能。

1 cfs远程挂载

•将工具和启动脚本,配置文件打到基础镜像

•在开启缓存的位置,启动脚本,开始挂载cfs

_, err = c.ScriptAction.Sh([]string{
    "sh",
	"-c",
	"modprobe fuse;cd /export/servers/tools/cfs;sudo ./cfs-client-randomwrite -c fuse.json",
})

2 zstd压缩

针对现有的几种压缩方式进行了性能对比,最后选用了zstd进行压缩。

Zstd,全称 Zstandard,是 Facebook 于 2016 年开源的新无损压缩算法。Zstd 还可以以压缩速度为代价提供更强的压缩比,速度与压缩率的比重可通过增量进行配置。与 zlib、lz4、xz 等当前流行的压缩算法不同,Zstd 寻求一种压缩性能与压缩率通吃的方案,而实际上它也确实做到了。在由官方所列出的表格中,可以看到,Zstd 不仅具备优秀的压缩性能,在压缩率上也有非常亮眼的表现。在过去的两年里,Linux内核、HTTP协议、以及一系列的大数据工具(包括Hadoop 3.0.0,HBase 2.0.0,Spark 2.3.0,Kafka 2.1.0)等都已经加入了对zstd的支持。

常见的压缩算法性能对比:

1.png

压缩包大小对比:

依赖包的大小 465M 压缩效率
tar压缩 423M 14s左右
zstd压缩 205M 1s左右

缓存的实现

我们借鉴了github cache action,zadig,gitlab等缓存的处理方式,同时结合服务自身的特点

将整体分成三步

•检查是否命中缓存:根据缓存key,判断缓存是否命中

缓存key 缓存的唯一标识
不同语言编译原子 根据下载代码的代码库地址自动获取 设置的缓存key:home_auth/home-auth-center
用户自定义镜像 自定义缓存key

•pull缓存

当缓存命中后,根据缓存路径,找到挂载到cfs上的缓存压缩包,解压到指定的缓存目录下

•push缓存:将依赖包进行压缩,放到cfs的挂载目录下

2.png

依赖包的大小 465M
tar压缩 423M
zstd压缩 205M

3.png

缓存的使用限制和回收策略

使用限制

目前存储缓存数没有限制,存储库中所有缓存的总大小限制是根据申请的cfs的大小限制:20G。

回收策略

我们会删除7天内未被访问的任何缓存。利用etcd的watch机制,实现缓存的回收。

etcd可以Watch 指定的键、前缀目录的更改,并对更改时间进行通知。BASE引擎中,缓存的清除策略借助etcd来实现。

缓存过期策略:在编译加速的实现中,每个需要缓存的项目都有对应的缓存key,通过etcd监控key,并且设置过期时间,例如7天,如果在7天之内再次命中key,则通过lease进行续约;7天之内key都没有被使用,key就会过期删除,通过监听对应的前缀,在过期删除的时候,调用删除缓存的方法。

storage.Watch("cache/",
		func(id string) {
			//do nothing
		},
		func(id string) {
			CleanCache(id)
		})

不同技术栈的最佳实践

1 Java

以Maven构建工具为例,其默认配置文件位于conf/settings.xml文件中,默认指定环境变量$M2_HOME来设置缓存目录,这样同一条流水线多次执行可以复用 ${M2_HOME}/.m2 目录 (缓存目录),甚至同一个应用下的多个分支之间都可以使用同一个缓存目录,就像本地构建一样。

BASE执行
无缓存 平均时间:5.26min
有缓存 平均时间:41.462s
提升效率 提升87.3%
缓存命中率 接近100%

2 NodeJs

在nodejs编译中,我们的缓存目录是当前用户空间,针对node_modules文件进行压缩打包,push到cfs;如果缓存命中,从cfs上pull并且解压到当前用户空间下,恢复缓存。

使用举例

BASE执行
无缓存 平均时间:58s
有缓存 平均时间:29s
提升效率 提升50%
缓存命中率 接近100%

3 Golang编译

Golang 缓存路径通过$GOCACHE环境变量控制,将$GOCACHE的内容压缩成zstd的包,上传到cfs的指定路径下。pull缓存的时候,拉取到对应的$GOCACHE。

BASE执行
无缓存 平均时间:117s
有缓存 平均时间:18s
提升效率 提升84.6%
缓存命中率 接近100%

4 GCC编译

我们使用ccache进行缓存实现。ccache(“compilercache”的缩写)是一个编译器缓存,该工具会高速缓存编译生成的信息,并在编译的特定部分使用高速缓存的信息。ccache的缓存目录:CCACHE_DIR,我们将这个目录下的文件进行压缩,push到cfs,当第二次运行并且命中缓存,从cfs上pull并解压到CCACHE_DIR指定的目录下。

总结

在不同语言的编译原子内部,默认开启缓存的设置。第一次运行流水线的时候,会进行依赖的下载,第二次运行流水线,会命中缓存,无需进行依赖的下载,提高了流水线执行的效率。缓存默认保存7天。

自定义镜像进行缓存的最佳实践

为了满足用户使用自定义镜像的方式触发流水线,我们增加了两个通用的缓存原子。

Restore缓存:恢复缓存

Save缓存:保存缓存

4.png

在编译之前,添加Restore缓存原子

5.png

在编译之后,添加Save缓存原子

6.png

使用举例

在maven编译原子中,默认开启了maven编译的缓存;同时还有nodejs的编译构建,所以我们增加了restore原子和save原子

7.png

BASE执行
无缓存 平均时间:21min57s 其中maven: 17min83s nodejs: 4min19s
有缓存 平均时间:4min20s 其中maven: 1min10s nodejs: 2min36s
缓存效率提升 maven: 93.7% nodejs:39.8%(nodejs编译中有包含单元测试)
缓存命中率 接近100%

未来规划

•不同编译原子,向用户开放配置,如是否开启缓存,设置缓存key

•实现不同语言编译原子增量push缓存功能

与云原生场景下实现编译加速相似的内容:

云原生场景下实现编译加速

云原生下的流水线是通过启动容器来运行具体的功能步骤,每次运行流水线可能会被调度到不同的计算节点上。这会导致一个问题:容器运行完是不会保存数据的,每当流水线重新运行时,又会重新拉取代码、编译代码、下载依赖包等等。在云原生场景下,不存在本地宿主机编译代码、构建镜像时缓存的作用,大大延长了流水线运行时间,浪费很多不必要的时间、网络和计算成本。

多数据源管理:掌握@DS注解的威力

大家在日常后端开发过程,不可避免的会接触到需要用到配置多个数据源的场景,在这里,小编介绍一种简单方便的,只需要简单的配置和一个@DS注解就能实现动态数据源的方式,这种动态数据源底层原理是基于Mybatis-plus来实现的。

通过4种经典应用,带你熟悉回溯算法

摘要:回溯的处理思想,有点类似枚举搜索。 本文分享自华为云社区《深入浅出回溯算法》,作者:嵌入式视觉。 一,如何理解回溯算法 深度优先搜索算法利用的就是回溯算法思想,但它除了用来指导像深度优先搜索这种经典的算法设计之外,还可以用在很多实际的软件开发场景中,比如正则表达式匹配、编译原理中的语法分析等。

云原生容器高可用运维能力应用

摘要:华为云容器SRE在海量集群和容器运维实践中,从智能运维能力、确定性场景恢复等多方面总结出一套确定性运维实践,以应对云原生业务快速增长。 本文分享自华为云社区《云原生容器高可用运维能力应用》,作者:陈勇/刘志超/袁文峰。 云原生场景下,对架构高可用、应用高可用、基础云平台高可用提出了更高的要求,

Kurator,你的分布式云原生解决方案

为大家介绍分布式云原生一站式开源解决方案Kurator,结合典型特性的实操演示,让开发者快速了解Kurator 在分布式云场景下的使用。

微服务13:云基础场景下流量策略实现原理

# ★微服务系列 [微服务1:微服务及其演进史](https://www.cnblogs.com/wzh2010/p/14940280.html "微服务1:微服务及其演进史") [微服务2:微服务全景架构 ](https://www.cnblogs.com/wzh2010/p/15311192.h

从原始边列表到邻接矩阵Python实现图数据处理的完整指南

本文介绍了如何使用Python将原始边列表转换为邻接矩阵,并进行了一系列的扩展和优化,以满足不同场景下的需求。

慢 SQL 优化之索引的作用是什么?

本文针对 MySQL 数据库的 InnoDB 存储引擎,介绍其中索引的实现以及索引在慢 SQL 优化中的作用。本文主要讨论不同场景下索引生效与失效的原因。

[转帖]BPF数据传递的桥梁——BPF Map(一)

https://cloud.tencent.com/developer/inventory/600/article/1644458 这是一个实战系列文章,它是eBPF学习计划里面的应用场景之网络部分,终极目标是源码级别学习云原生环境下使用eBPF的场景,比如Cilium、Falco等(声明:下文提到

Cloud Bursting解决方案,Serverless容器降本增效极致体验

聚焦华为云Serverless容器服务CCI提供的云原生Cloud Bursting解决方案,结合典型场景案例及实操演示,帮助企业和用户快速构建降本增效方案。