【ArchSummit架构师峰会】探讨数据与人工智能相互驱动的关系>>> 了解详情
写点什么

我们如何在 30 项关键服务任务中节省 70K 内核

  • 2022-02-09
  • 本文字数:3128 字

    阅读完需:约 10 分钟

我们如何在30项关键服务任务中节省70K内核

引言

作为 Uber 工程实现盈利的众多努力的一部分,最近我们的团队致力于通过提高效率来降低算力成本。其中最有影响力的一些工作是围绕 GOGC 优化展开的。在这篇博客,我们想分享我们在高效、低风险、大规模、半自动化 Go 垃圾回收调优机制方面的经验。

 

Uber 的技术栈由数千个微服务组成,由云原生的基于调度的基础设施支持。这些服务中的大部分都是用 Go 编写的。我们的团队——地图制作工程组,以前曾在通过调优 GC 来显著提高多个 Java 服务的效率方面发挥过重要作用。在 2021 年初,我们探讨了对基于 Go 的服务进行性能调优的可能性。我们运行了几个 CPU 配置文件来评估当前的状态,发现 GC 是大多数关键任务服务的最大 CPU 消费者。下面是一些 CPU 配置文件的代表,其中 GC(由 runtime.scanobject 方法标识)消耗了分配的计算资源的很大一部分。

 

Service #1



图 1:示例服务 #1 的 GC CPU 消耗

 

Service #2



图 2:示例服务 #2 的 GC CPU 消耗

 

由于这一发现,我们开始为相关服务进行 GC 调优。令我们高兴的是,Go 的 GC 实现和简单的调优使得我们能够自动化大部分检测和调优机制。我们将在后续部分详细介绍我们的方法及其影响。

 

GOGC 调优器

Go 运行时环境以周期性的间隔调用并发垃圾回收器,除非之前有一个触发事件。触发事件基于内存背压。因此,受 GC 影响的 Go 服务受益于更多的内存,因为这减少了 GC 必须运行的次数。另外,我们意识到我们的主机级 CPU 与内存的比率是 1:5(1 core: 5 GB 内存),而大多数 Golang 服务的配置比率是 1:1 到 1:2。因此,我们相信我们可以利用更多的内存来减少 GC CPU 的影响。这是一种与服务无关的机制,如果应用得当,会产生很大的影响。

 

深入研究 Go 的垃圾回收超出了本文的讨论范围,但以下是这项工作的相关内容:Go 中的垃圾回收是并发的,需要分析所有对象来确定哪些对象仍然是可访问的。我们将可访问的对象称为“实时数据集”。Go 只提供了一个工具——GOGC,用实时数据集的百分比表示,用来控制垃圾回收。GOGC 值充当数据集的乘数。GOGC 的默认值是 100%,这意味着 Go 运行时环境将为新的分配保留与实时数据集相同的内存量。例如:硬目标 = 实时数据集 + 实时数据集 * (GOGC / 100)。

 

然后,pacer 负责预测触发垃圾回收的最佳时间,从而避免击中硬目标(和软目标)。



图 3:使用默认配置的示例堆内存

动态而多样:没有万能的方法

我们发现,基于固定的 GOGC 值的调整不适合 Uber 的服务。其中一些挑战是:

  • 不知道分配给容器的最大内存,可能导致内存溢出问题。

  • 我们的微服务具有显著不同的内存使用量组合。例如,分片系统可以有非常不同的实时数据集。我们在其中一个服务中遇到了这种情况,其中 p99 的使用量是 1GB,而 p1 的使用量是 100MB,因此 100MB 的实例对 GC 有巨大影响。

 

自动化案例

前面提到的痛点是提出 GOGCTuner 概念的原因。GOGCTuner 库简化了服务所有者优化垃圾回收的过程,并在其上添加了一个可靠性层。

 

GOGCTuner 根据容器的内存限制(或服务所有者的上限)动态计算正确的 GOGC 值,并使用 Go 的运行时 API 进行设置。以下是 GOGCTuner 库功能的详细信息:

  • 简化配置来便于推理和确定性计算。GOGC 的 100%对于 GO 初学开发者来说并不明确,也并不确定,因为它仍然依赖于实时数据集。另一方面,70%的限制可确保服务始终使用 70%的堆空间。

  • 防止 OOM(内存溢出):这个库从 cgroup 读取内存限制,并使用默认的硬限制 70%(这是我们经验中的安全值)。

  • 值得一提的是,这种保护是有限度的。微调器只能调整缓冲区分配,因此如果您的服务的存活对象高于微调器的限制,微调器会将比较低的存活对象的使用量的 1.25 倍设置成默认的限制值。

  • 对于以下情况,允许更高的 GOGC 值:

  • 如上所述,手动 GOGC 是不确定的。我们仍然依赖实时数据集的大小。如果实时数据集是我们上一个峰值的两倍怎么办?GOGCTuner 将使用更多的 CPU 来强制执行相同的内存限制。相反,手动调整会导致内存溢出。因此,服务所有者过去常常为这些类型的场景提供大量的缓存。请参见下面的示例:

 

正常流量(实时数据集是 150M)



图 4:正常操作。左边是默认配置,右边是手动调整。

 

流量翻倍(实时数据集是 300M)



图 5:负载翻倍。左边是默认配置,右边是手动调整。

流量翻倍且 GOGCTuner 设置为 70%(实时数据集是 300M)



图 6:流量翻倍,但使用微调器。左边是默认配置,右边是 GOGCTuner 调整。

 

  • 使用MADV_FREE内存策略的服务会导致错误的内存度量。例如,我们的可观测性指标显示了 50%的内存使用量(实际上它已经释放了这 50%中的 20%)。然后,服务所有者只使用这个“不准确的”指标来调整 GOGC。

 

可观测性

我们发现,我们缺乏一些可以让我们对每个服务的垃圾回收有更多了解的关键指标。

  • 垃圾回收之间的间隔:这可以使我们了解是否还可以调整。如果你的服务仍然有很高的 GC 影响,但你已经看到了这个图 120s,这意味着你不能再使用 GOGC 进行调整。在这种情况下,您需要优化分配。

 


图 7:GC 之间的间隔图。

 

  • GC CPU 影响:让我们知道哪些服务受 GC 影响最大。



图 8:p99 GC CPU 消耗图。

 

  • 实时数据集大小:帮助我们识别内存泄漏。服务所有者注意到的问题是,他们看到了内存使用量的提高。为了向他们表明没有内存泄漏,我们添加了“实时使用量”指标,展示了稳定的内存使用量。



图 9:p99 实时数据集预估图。

 

  • GOGC 值:对于了解调整的效果非常有用。



图 10:微调器给应用程序分配 min、p50、p99 GOGC 值的图。

 

实现

我们最初的方法是,让一个计时器每秒运行一次来监控堆指标,然后相应地调整 GOGC 值。这种方法的缺点是,开销开始变得相当大,因为为了读取堆指标,Go 需要执行一次 STW(ReadMemStats),这还不怎么准确,因为我们每秒可能会多次进行垃圾回收。

 

幸运的是,我们找到了一种替代方案。Go 有 finalizers(SetFinalizer),它们是在垃圾回收对象时运行的函数。它们主要用于清理 C 代码或其它资源中的内存。我们可以使用一个自引用的 finalizer,在每次 GC 调用时重置自己。这能够使我们减少任何 CPU 开销。例如:



图 11:GC 触发事件的示例代码。

 

调用运行时。在 finalizerHandler 中的 SetFinalizer(f, finalizerHandler)允许应用程序在每个 GC 上运行;它基本上不会让引用消亡,因为它不是一个代价高昂的资源(它只是一个指针)。

 

影响

在我们的几十个服务中部署了 GOGCTuner 之后,我们深入研究了其中一些在 CPU 使用量上有显著的两位数提升的服务。仅这些服务就累积节省了约 70K 内核。下面是 2 个这样的例子:



图 12:在数千个计算内核上运行,实时数据集的标准差很高(最大值是最小值的 10 倍)的可观测性服务,显示 p99 CPU 的使用降低了约 65%。

 


图 13:运行在数千个计算核心上的关键任务 Uber eats 服务,显示 p99 CPU 的使用降低了约 30%。

 

由此导致的 CPU 使用的减少在战术上优化了 p99 的延迟(以及相关的 SLA、用户体验),并在战略上优化了性能成本(因为服务是根据他们的使用量进行扩展的)。

 

结语

垃圾回收是影响应用程序性能的最难以捉摸且被低估的因素之一。Go 强大的 GC 机制和简化的调优,我们多样化的大规模的 Go 服务足迹,以及强大的内部平台(Go、计算、可观测性),共同让我们能够产生如此大规模的影响。由于技术和我们能力的变化,问题本身正在演变,我们希望继续改进 GC 调优的方式。

 

重申我们在引言中提到的:没有万能的解决方案。我们认为,由于公共云和运行在其中的容器化负载的性能高度可变,在云原生设置中 GC 性能也是变化的。再加上我们使用的绝大多数 CNCF 落地项目(Kubernetes、Prometheus、Jaeger 等等)都是用 Golang 编写的,这意味着任何外部的大规模部署也可以受益于这些工作。


 作者介绍:

Cristian Velazquez 是 Uber 的地图制作工程团队的高级二级工程师。他负责多个效率倡议,这些倡议跨多个组织,其中最相关的是 Java 和 Go 的垃圾回收调优。


原文链接:

How We Saved 70K Cores Across 30 Mission-Critical Services (Large-Scale, Semi-Automated Go GC Tuning @Uber)

2022-02-09 19:152891

评论 1 条评论

发布
用户头像
把core翻译成内核实在不应该,这里指cpu的核数,不是内核。
2022-04-08 19:08
回复
没有更多了
发现更多内容

软件测试/人工智能|教你轻松掌握Python输入与输出

霍格沃兹测试开发学社

超赞!让vue开发效率翻倍的工具分享

秃头小帅oi

Vue 前端

第16届中国R会议暨2023X-AGI大会开幕,和鲸科技分享ModelOps在数据科学平台中的实践与应用

ModelWhale

R语言 数据科学 算法模型 ModelOps 计算平台

Ableton Live 12 for Mac(音乐制作工具)激活版

iMac小白

获取体育比分、赛事直播源的途径,以及数据API接口的应用

软件开发-梦幻运营部

C# 泛型编译特性对性能的影响

快乐非自愿限量之名

c 编程语言 编译

滴滴遭遇重击:12小时内损失千万订单量与超4亿成交额,背后有何启示?

EquatorCoco

滴滴 崩溃分析 滴滴出行

人工智能的技术研究与安全问题的深入讨论

不在线第一只蜗牛

人工智能 安全 信息安全

全球79%的程序员都在考虑跳槽,你呢?

秃头小帅oi

程序员 低代码 开发

云边协同的RTC如何助力即构全球实时互动业务实践

ZEGO即构

边缘计算 实时音视频 MSDN 云边协同 音视频质量

如何有效的进行 E2E

优测云服务平台

测试 测试技术

井然有序 | AIRIOT智能安防系统解决方案

AIRIOT

物联网 智能安防

KubeBlocks与OceanBase完成产品兼容互认,可以使用KubeBlocks管理你的OceanBase集群啦!

小猿姐

企业怎样用数字人实现降本增效?

青否数字人

虾皮Shopee店铺详情API接口返回的数据包括哪些信息?

技术冰糖葫芦

API 开发

飞书深诺接口自动化测试落地实战

飞书深诺技术团队

质量 AIGC #人工智能

亚马逊云科技:探索未来云计算之窗

快乐非自愿限量之名

云计算 云原生 亚马逊云

软件测试/人工智能|PyCharm常用快捷键指南

霍格沃兹测试开发学社

WPF应用开发之附件管理

快乐非自愿限量之名

开发者 微服务附件 开源WPF项目

2023~2024第三届瑞云渲染3d创造大赛报名方法

Renderbus瑞云渲染农场

云渲染 渲染农场 云渲染平台 3d渲染比赛

如何解决在使用虾皮Shopee店铺详情API接口时遇到的问题?

技术冰糖葫芦

API 文档

[开源更新]企业级身份管理和访问管理系统、为数字身份安全赋能

小狗围观科幻

整车级虚拟标定:降本增效

DevOps和数字孪生

汽车产业 虚拟汽车

Rust std fs 比 Python 慢!真的吗!?

Databend

“降本增效”才是选择低代码开发的主旋律

互联网工科生

软件开发 低代码开发 JNPF

要做职业规划么?我的三个机遇以及一个坚持

非晓为骁

个人成长 职业规划 坚持 目标 工作思考

只需两步生成自己的数字人形象!

青否数字人

和鲸科技与国科环宇建立战略合作伙伴关系,以软硬件一体化解决方案促进科技创新

ModelWhale

软件 服务器 算力 大模型 计算平台

屏幕截图工具 Snagit mac 2023.2.4中文版

iMac小白

数仓实践丨常量标量子查询做全连接导致整体慢

华为云开发者联盟

数据库 后端 华为云 华为云开发者联盟 华为云GaussDB(DWS)

用数字人进行直播带货的详细教程!

青否数字人

我们如何在30项关键服务任务中节省70K内核_开源_Cristian Velazquez_InfoQ精选文章