郑州网站建设设计公司,郑州开发公司,海拉尔建设局网站,中国建设银行公积金网站Bazel 为了正确性和高性能#xff0c;做了很多优秀的设计#xff0c;那么我们如何正确的使用这些能力#xff0c;让我们的构建性能“起飞”呢#xff0c; 我们将从本地研发和 CI pipeline 两种场景进行分析。
本地研发
本地研发通常采用默认的 Bazel 配置即可#xff0c…Bazel 为了正确性和高性能做了很多优秀的设计那么我们如何正确的使用这些能力让我们的构建性能“起飞”呢 我们将从本地研发和 CI pipeline 两种场景进行分析。
本地研发
本地研发通常采用默认的 Bazel 配置即可无需为增量构建和 repository_cache 做额外配置Bazel 默认就处理的很好。
使用时应该信任 bazel 的增量构建机制即便是从远端仓库同步了代码也可以直接 build无须先通过 bazel clean 清理环境。
至于 Remote Cache 和 Remote Execution则需要结合网络状况和 Action 的执行开销决定是否开启参数是 --remote_cache 和 --remote_execution。
正确开启 bazel 的 remote 能力
正确开启 remote_cache 和 remote_execution 对构建效率有显著作用但网络或 Action 特性也可能导致收益不明显甚至劣化。
举个例子说明使用 remote_cache 的利弊
我们假设 Action 的执行时间是 a上传缓存和下载缓存的时间分别是 b 和 c 缓存命中率是μ。如果不使用 remote cache耗时恒定为 a如果使用 remote cache 命中缓存耗时是 c不命中则是 a b 结合命中率可以求出耗时的数学期望是 μc (1 - μ)(a b)。也就是说只有 μc (1 - μ)(a b) a才有收益。
例如 Action 执行时间是 500ms上传产物时间是 200ms下载产物时间是 100ms缓存命中率是 30% 代入到式子中0.3 * (500 200 - 100)ms 180ms
实践中我们不一定能对 Action 做如此精细的数据分析但可以根据网络状况大致估算。Bazel 提供了精细化的控制方式可以控制某一种类型的 Action 是否启用 remote_cache例如 针对 CppLink 禁用 remote_cache
针对 CppLink 类型的 Action 禁用了 remote_cache 能力其他类型则可以正常使用。甚至还可以通过 no-remote-cache-upload设置为只禁止上传缓存不禁止下载缓存。
对于缓存的精细化设置属于比较高级的功能Bazel 暂时没有过多开放相关能力相关的文档也不全。或许我们可以期待一下未来能使用更方便的配置来管理。
缓存命中率调优
上面的例子可以看出Action 的缓存命中率直接决定了 remote cache 的收益如何优化缓存命中率呢
前文介绍原理时我们知道 Action 由 inputs 和 commands 组成inputs 指执行 Action 所需的目录结构和文件内容。而 commands 包括了参数 (args), 执行路径 (workdir) 和环境变量 (envs)。
当缓存命中率不符合预期时我们需要对 Action 的详情进行调试。
bazel 的 --execution_log_binary_file 参数可以把 Action 的详细信息打印到文件里。
对比两次构建的 Action 详情就可以知道是什么参数发生了变化。
该参数导出的原始信息是二进制格式有一些特殊字符如下图所示 execution_log_binary_file 文本
可以借助 bazel 的 execution_log_parser 工具把它变成更可读的形式
该工具需要源码编译 bazel 使用 parser 工具把 log 变成可读形式
转换后的文件如下图所示 转换后的 execution_log
之后就可以用文本对比工具对两次构建生成的 execution_log 进行对比。
CI pipeline
再来看到 CI 场景如果你在公司里搭建了持续集成流水线则需要考虑更多的东西。在公司内网的模式下CI 的网络往往不再是瓶颈我们应该完整的使用 Remote Cache 和 Remote Execution 的能力。
搭建 Remote Execution 服务
使用 Remote 能力的前提是部署支持 Remote Execution 协议的服务一般来说开源产品 buildfarm 或 buildbarn 就足够使用了如果对性能和数据分析有更加极致的要求可以考虑企业版产品或者基于 Remote Execution API 协议自研。
Remote Execution 服务的架构设计是一个很大也很有趣的话题。篇幅关系本文不过多深入细节但提供几点设计要求可以参考
Remote Execution 服务通常包括 scheduler 和 worker 组件集群规模较小时单 scheduler 可以调度所有 Action而规模较大时需要多 scheduler 协同这是一个很大的挑战。
scheduler 的职责是把 Action 调度给 最合适 的 worker并且分派的过程 越快越好。
如何衡量任务调度的好与坏一方面尽量让 Action 均匀分布避免排队时间过长另一方面尽量利用 worker 的本地文件缓存减少重复的文件下载。
不同客户端发来的相同 Action可以考虑在服务端进行合并。
不同类型的 worker需要根据系统的负载进行弹性伸缩以确保资源的高效利用。
客户端调度增强
除了 Remote Execution 服务另一块需要注意的地方是客户端调度。不同于本地构建CI 场景为了追求强隔离性往往以实时运行 Docker Container 的方式提供构建环境。也就是说构建环境不包含上一次构建的数据。
这种模式对于 Bazel 构建很不友好不仅外部依赖要重新下载而且增量编译功能也无法使用。但我们也有办法尽可能的加快构建速度。 CI 环可复用的要素
首先是使用 Remote Cache 和 Remote Execution 服务在没有增量构建的场景下Remote Cache 和 Remote Execution 提供的优化效果是非常夸张的根据我的观察提速普遍在 70% 以上甚至能达到 90%。
其次是缓存本地数据例如 trivas CI 这样的流水线编排系统就支持对特定目录进行缓存。它的原理是把目录打包上传到对象存储下次构建时再下载下来。我们可以将 Bazel 的 repository_cache 和 action_local_cache 相关的目录进行缓存下次构建就可以直接复用。
如果条件允许的话甚至可以要求流水线提供常驻容器这样 Bazel 的进程都可以长期保留着下次构建时直接 Attach 到已有的容器上执行命令即可。这种方式有望在 CI pipeline 场景实现秒级构建这是多么酷的一件事情啊
不过常驻容器对安全性也带来了一定的挑战企业具体采用那种方案也应该因实际情况而异。 本文属于如下文章中的子章节
bazel学习系列章节汇总_m0_74043383的博客-CSDN博客