作者:京东零售王磊背景云原生下的pipeline通过启动容器来运行特定的功能步骤,pipeline每次运行都可能被调度到不同的计算节点。这会造成一个问题:容器运行后不会保存数据,每次重新运行pipeline都会重新拉取代码,编译代码,下载依赖包等等。在云原生场景下,本地主机在编译代码和构建镜像时没有缓存的功能,大大延长了流水线的运行时间,浪费了很多不必要的时间、网络和计算成本。在许多流水线场景中,同一条流水线的多次执行是相互关联的。如果可以利用上次的执行结果,执行时间可以大大缩短。为了提高用户使用流水线的体验,我们增加了支持缓存的功能,附加远程存储管理和构建缓存,可以实现同一个项目的编译依赖复用,在多个共享同一个缓存运行相同的管道。目标是通过实现云原生pipeline的缓存技术,实现代码编译的缓存复用,将pipeline平均提速3~5倍;实施方案,我们将使用zstd压缩需要缓存的文件,远程挂载cfs。构建的缓存持久化到cfs上的指定位置。下次构建开始时,判断缓存是否命中。如果缓存命中,我们从cfs上的指定位置拉取对应的缓存压缩包,解压到对应的目录下。使用的工具-cfs+zstd不是自定义镜像,需要的工具都放入引擎的基础镜像中,作为所有镜像的基础工具。用户定义的图像与用户图像没有强绑定。如果需要使用缓存功能,可以通过Restorecacheatom和Savecacheatom设置缓存key和缓存目录来实现缓存功能。1cfs远程挂载?将工具、启动脚本、配置文件放入基础镜像?在启用缓存的位置启动脚本,开始挂载cfs_,err=c.ScriptAction.Sh([]string{"sh","-c","modprobefuse;cd/export/servers/tools/cfs;sudo./cfs-client-randomwrite-cfuse.json",})2zstd压缩对比现有几种压缩方式的性能,最后选择zstd进行压缩。Zstd,全称Zstandard,是Facebook于2016年开源的一种新型无损压缩算法。Zstd还可以以压缩速度为代价提供更强的压缩比,速度与压缩比的比率可通过增量配置。与目前流行的zlib、lz4、xz等压缩算法不同,Zstd寻求的是兼顾压缩性能和压缩率的解决方案,事实上也确实如此。在官方列出的表格中可以看出,Zstd不仅具有出色的压缩性能,在压缩比上也有着非常可观的表现。近两年Linux内核、HTTP协议、一系列大数据工具(包括Hadoop3.0.0、HBase2.0.0、Spark2.3.0、Kafka2.1.0)都加入了对zstd的支持。常用压缩算法性能对比:压缩包大小对比:取决于包大小465M压缩效率tar压缩423M14s左右Zstd压缩205M1s左右缓存实现我们借鉴了github缓存action、zadig、gitlab等缓存处理方式,并结合服务本身的特点,整体分为三个步骤。检查缓存是否命中:根据缓存key,判断缓存是否命中缓存key。缓存key的唯一标识不同语言编译原子根据下载代码的码库地址自动获取设置的缓存key:home_auth/home-auth-center用户自定义图片自定义缓存keypullcache缓存命中时,查找缓存压缩包根据缓存路径挂载到cfs上,解压到指定的缓存目录。pushcache:压缩依赖包,放置在cfs挂载目录下,依赖包大小为465Mtar压缩,423Mzstd压缩,205M缓存使用限制及回收策略。存储缓存的数量目前没有限制,仓库中所有缓存的总大小限制是根据申请的cfs的大小限制:20G。回收政策我们会删除7天内未访问过的所有缓存。利用etcd的watch机制实现缓存回收。etcd可以监视指定key和prefix目录的变化,并通知变化时间。在BASE引擎中,缓存清除策略是借助etcd实现的。缓存过期策略:在编译加速的实现中,每一个需要缓存的item都有一个对应的缓存key,通过etcd监控key,设置过期时间,比如7天,如果在7天内再次命中key天,通过租约续约;如果密钥在7天内未被使用,密钥将过期并被删除。通过监听对应的前缀,当key过期删除时,会调用删除缓存的方法。storage.Watch("cache/",func(idstring){//donothing},func(idstring){CleanCache(id)})不同技术栈的最佳实践1Java以Maven构建工具为例,其默认配置文件位于conf/settings.xml文件中,默认指定环境变量$M2_HOME设置缓存目录,这样同一个pipeline可以复用${M2_HOME}/.m2目录(缓存directory)用于多次执行,甚至是同一个应用程序下的多个分支之间可以使用相同的缓存目录,就像本地构建一样。无缓存的BASE执行平均时间:5.26min有缓存平均时间:41.462s提高效率87.3%缓存命中率接近100%2NodeJs在nodejs编译中,我们的缓存目录是当前用户空间,压缩打包node_modules文件,push到cfs;如果缓存命中,从cfs拉取解压到当前用户空间,恢复缓存。示例BASE执行无缓存平均时间:58s有缓存平均时间:29s效率提升50%缓存命中率接近100%3Golang编译Golang缓存路径由$GOCACHE环境变量控制,$GOCACHE的内容压缩成zstd包上传到cfs的指定路径。拉取缓存时,拉取到对应的$GOCACHE。BASE无缓存平均执行时间:117s有缓存平均时间:18s效率提升84.6%缓存命中率接近100%4GCC编译我们使用ccache进行缓存实现。ccache(“compilercache”的缩写)是编译器缓存,是一种缓存编译产生的信息并在编译的特定部分使用缓存信息的工具。ccache的缓存目录:CCACHE_DIR,我们把这个目录下的文件压缩,push到cfs,当第二次运行命中缓存的时候,从cfs中拉取解压到CCACHE_DIR指定的目录。总结在不同语言的编译原子中,缓存设置是默认开启的。pipeline第一次运行时会下载依赖,第二次运行pipeline时会命中缓存,不需要下载依赖,提高pipeline执行效率。缓存默认保留7天。自定义镜像用于缓存的最佳实践为了满足用户使用自定义镜像触发流水线的需求,我们新增了两个通用的缓存原子。Restorecache:恢复缓存Savecache:保存缓存编译前添加Restorecacheatom编译后添加Savecacheatom使用示例在maven编译atom中,默认开启maven编译缓存;同时还有nodejs的编译构建,所以我们加入了restoreatom和saveatom。BASE执行无缓存平均时间:21min57swheremaven:17min83snodejs:4min19swithcacheaveragetime:4min20swheremaven:1min10snodejs:2min36s缓存效率提升maven:93.7%nodejs:39.8%(单元测试包含在nodejs编译中)缓存命中率接近100%未来规划?不同编译原子,向用户开放配置,如是否开启缓存,设置缓存key?实现不同语言编译原子的增量推送缓存功能
