写点什么

Uber 如何实现 Go 代码中的动态数据竞争检测

  • 2022-07-12
  • 本文字数:2839 字

    阅读完需:约 9 分钟

Uber 如何实现 Go 代码中的动态数据竞争检测

本文是 Uber 在 Go 代码中数据竞争经验两篇文章中的第一篇。详细版本将在 2022 年 ACM SIGPLAN 编程语言设计与实现(Programming Languages Design and Implementation,PLDI)中发表。在本文系列的第二部分,我们将介绍关于 Go 中竞争模式的学习。


Uber 已将 Go 作为主要编程语言,广泛用于开发微服务。我们的 Go 单体仓库由大约 5000 万行代码组成,包含大约 2100 个独特的 Go 服务。Go 使并发性成为一流公民;在函数调用前加上 go 关键字,就会异步运行调用。在 Go 中,这些异步函数调用被称作 goroutines。开发人员通过在单个运行的 Go 程序中创建 goroutines,从而隐藏了延迟(例如,对其他服务的 IO 或 RPC 调用)。goroutines 被认为是 “轻量级的”,Go 的运行时上下文在操作系统(OS)线程上切换它们。Go 程序员经常随意使用 goroutines。两个或多个 goroutines 可以通过消息传递(通道)或共享内存进行数据通信。共享内存恰好是 Go 中最常用的数据通信方式。


在 Go 中,如果两个或更多的 goroutines 访问同一个内存地址时,那么至少有一块是写入的,而且它们之间没有排序,这就是 Go 内存模型所定义的数据竞争。在我们的微服务中,由于数据竞争而导致的 Go 程序的中断是一个反复出现的、令人头疼的问题。由于上述问题,我们关键的、面向客户的服务总共瘫痪了数个小时,造成客户的不便,也影响了我们的收益。在本文中,我们将会讨论 Go 的一个默认动态竞争检测器,它将会在 Go 的开发环境中不断检测数据竞争。这一部署实现了对 2000 多个竞争的检测,使两百多名工程师修复了约 1000 个数据竞争。


动态检测数据竞争


动态竞争检测包括通过检测共享内存访问和同步构造来分析程序的执行。在 Go 中进行单元测试,生成多个 goroutine,这是一个很好的开始,可以进行动态竞争检测。Go 有一个内置的竞争检测器,可以用来在编译时检测代码,以及检测执行过程中的数据竞争。在内部,Go 的竞争检测器采用了 ThreadSanitizer 运行时库,通过结合锁集和基于之前的算法来报告数据竞争。


与动态竞争检测相关的重要属性如下:


  • 由于动态竞争检测依赖于分析的执行,所以不会报告源代码中的所有竞争。

  • 检测到的竞争集依赖于线程交错,甚至程序的输入没有变化,但会在多次运行中发生变化。

何时部署动态数据竞争检测器?


我们在仓库中使用了超过 10 万个 Go 单元测试来执行代码和检测数据竞争。然而,我们面临着一个具有挑战性的问题,即何时部署竞争检测器。


在拉取请求(pull request,PR)时,运行动态数据竞争检测器存在以下问题:


  • 竞争检测具有不确定性。这样,拉取请求所引起的竞争可能不会被曝光,并且可能不会被检测到。这种行为的后果是,随后的良性拉取请求可能会受到检测到的休眠竞争的影响,从而被错误地阻止,进而影响开发人员的生产力。此外,由于在我们 5000 万行的代码库中存在预先存在的数据竞争,这也是一件不可能的事情。

  • 动态数据竞争检测器占用的空间是 2~20 倍,内存开销是 5~10 倍,这可能导致违反我们的 SLA,或者增加硬件成本。


图 1:动态竞争检测工作流的架构


基于这些考虑,我们决定在事后定期在代码快照上部署竞争检测器,这包括以下步骤:


(a) 通过执行仓库中的所有单元测试来进行动态竞争检测。

(b) 通过向适当的 bug 所有者提交任务来报告所有未解决的竞争。


一个检测到的竞争报告包含以下细节:


  • 冲突的内存地址。

  • 2 个冲突访问的调用链(又称调用上下文或堆栈跟踪)。

  • 与每个访问相关的内存访问类型(读取或写入)。


我们解决了几个问题,通过对报告的堆栈竞争进行散列,并应用启发式方法来确定负责修复该错误的潜在开发人员,这样就可以避免重复的竞争。尽管我们已经选定了这种部署路径,但是,如果所检测到的竞争不会妨碍构建,并作为警告通知开发人员,或者对动态竞争检测进行了改善,使得 CI 时间的确定性检测是可行的,那么 CI 时间的部署是可以实现的。

部署的效果


我们在 2021 年 4 月推出了这一部署,并在 6 个月里收集数据。我们的方法帮助检测了单体仓库中的 2000 个数据竞争,每天有数百名 Go 开发人员提交的数据。在报告的 2000 个竞争中,有 1011 个竞争被 210 个不同的工程师修复。我们观察到,有 790 个独特的补丁来修复这些竞争,这表明了独特的根源数量。我们还收集了 6 个多月期间未解决的故障总数的统计数据,并将其报告如下:


图 2:6 个月内未解决的数据竞争的数量(2021 年 4 月~2021 年 9 月)


在推出的初始阶段(2~3 个月),我们向受让方提供了关于解决数据竞争问题的建议。在这一阶段,未解决的竞争已经出现了明显的减少。后来,随着指导工作的减少,我们注意到,未解决的竞争总数在逐渐增加。该图还表明,未解决的竞争数的波动,这是由于对竞争的修复、新竞争的引入、开发人员对测试的启用和禁用,以及动态竞争检测的基本非确定性。在报告了所有预先存在的竞争后,我们还观察到,工作流平均每天会创建大约 5 个新的竞争报告。


图 3:提交和修复的 Jira 任务的数量说明


就运行我们的离线数据竞争检测器的开销而言,我们注意到,在没有数据竞争检测的情况下,在所有的测试中,95% 的运行时间是 25 分钟,而在启用数据竞争之后,增加了 4 倍,达到约 100 分钟。在数十名工程师的调查中,大约在推出该系统 6 个月后,52% 的开发人员认为该系统有用,40% 的人没有参与该系统,8% 的人认为该系统没有用。

展望未来


我们在这次部署中的经验表明有以下进展:


  • 需要建立可在持续集成(CI)期间部署的动态竞争检测器。这需要新的检测器有效解决由于非确定性和开销带来的挑战。

  • 在这之前,设计算法为检测到的数据竞争寻找根源并确定适当的拥有者,有助于加速数据竞争的修复。

  • 我们已经确定了与 Go 中的数据竞争有关的基本编码模式(在本博客系列的第二部分中将会介绍),而 CI 时间的静态分析检测可能会捕捉到其中一个子集。

  • 所检测的竞争集依赖于输入的测试套件。能够在其他类型的测试(除单元测试外)上运行竞争检测,如集成测试、端到端测试、黑盒测试,甚至生产跟踪,都能帮助检测更多的竞争。

  • 我们还认为,对输入测试套件的时间表进行模糊处理的程序分析工具可以暴露出线程交错,从而增强检测到的竞争集。

  • 最后,目前的方法依赖于通过单元测试的多线程执行的可用性,而手动构建此类测试时,不一定能考虑到所有可能的情况。自动生成多线程执行,其中包含 racy 行为,并且利用检测器来验证竞争,这是一种高效的调试工具。


作者介绍:


Murali Krishna Ramanathan 是一名高级软件工程师,负责 Uber 工程的多项代码质量计划。他是 Piranha 的架构师,Piranha 是一个重构工具,可以自动删除因特性标记过期而导致的代码。他的兴趣是开发工具来解决软件开发的挑战,包括特性标记、自动代码重构和开发人员的工作流,以及自动测试生成以提高软件质量。


Milind Chabbi 是 Uber 编程系统研究团队的一名员工研究员。他领导整个 Uber 在编译器优化、高性能并行计算、同步技术和性能分析工具方面的研究计划,使大型复杂的计算系统变得可靠和高效。


原文链接:


https://eng.uber.com/dynamic-data-race-detection-in-go-code/

2022-07-12 09:095529

评论

发布
暂无评论
发现更多内容

EMQ X VS RabbitMQ:两大消息服务器 MQTT 性能对比全解(下)

EMQ映云科技

RabbitMQ 物联网 IoT mqtt emq

马萨卡!阿里大佬珍之若宝的最强高并发pdf,竟然被上传GitHub开源

Java 架构 面试 编程语言

网易云信 NERTC 高清画质体验之 H.265的工程实践 | 体验共享技术专题

网易云信

Java 测试 音视频 视频

保持高效学习的 7 个方法

Phoenix

学习方法

从互联网“后来者”到“引领者”:这场IPv6大会上,我读懂了中国式创新

脑极体

2022年最新Java小白学习路线总结,从零基础跟着学习不掉队(PDF+视频分享篇)

Java 编程 程序员 计算机 java面试

为什么网络 I/O 会被阻塞?

编程 架构 操作系统 计算机

Python代码阅读(第36篇):列表偏移

Felix

Python 编程 Code Programing 阅读代码

这还不够全?阿里P8架构师耗时八年时间才整理出来这“Java核心知识PDF(Java高岗)

Java 程序员 架构 面试 后端

Android 资源溢出崩溃轻松解

字节跳动终端技术

字节跳动 移动开发 Mars 火山引擎 MARS-APMPlus

我凭借这份pdf拿下了蚂蚁金服、字节跳动、小米等大厂的offer

Java 编程 程序员 架构

GitHub上首本IntelliJ IDEA操作手册,标星果然百万名不虚传

Java 架构 面试 程序人生 编程语言

阿里P8手抄本惨遭泄露,并出现病毒式传播,致28人斩获大厂offer

收到请回复

Java 面试 阿里 大厂Offer

英特尔举办第十四届物联网峰会,携手中国生态伙伴迈向融合边缘新时代

科技新消息

J2PaaS 低代码平台,正式发布开源版!

J2PaaS低代码平台

低代码 零代码 低代码开发 低代码开发平台 无代码平台

【直播回顾】5步实现精细化用户触达,助力业务增长

极光JIGUANG

消息推送 用户触达

JS的深浅复制,原来如此!

华为云开发者联盟

js 序列化 深复制 浅复制

相约 DTCC 2021 | Tapdata 受邀分享:如何打造面向 TP 业务的数据平台架构

tapdata

Zookeeper 集群部署的那些事儿

牧小农

zookeeper

Java集合核心内容之葵花宝面,搞定90%以上的技术面!建议收藏

程序员小呆

Java 程序员 架构师

极客架构营2期模块5作业

Ping

会声会影和剪映在音频处理功能上的比较

懒得勤快

手把手带你做LiteOS的树莓派移植

华为云开发者联盟

树莓派 系统 LiteOS arm 树莓派移植

堡垒机提供的运维报表有什么用?怎么简单解释?

行云管家

运维 堡垒机 IT运维

网络安全产品之堡垒机应用于金融行业案例讲解

行云管家

云计算 网络安全 等保 堡垒机

理论+实例,带你掌握Linux的页目录和页表

华为云开发者联盟

Linux 内存管理 寄存器 页目录 页表

2021金九银十阿里Java岗7轮技术面经历,险幸上岸

Java 程序员 架构 面试 计算机

Qcon 免费报名 | 融云「实时通信技术专场」议题抢鲜看

融云 RongCloud

开发者 通信云 场景化

程序员常用的工具软件推荐

程序员小呆

Java c++ 程序员 架构师 Go 语言

惊了!网易架构大牛熬夜手敲千页网络协议笔记,竟在Github上标星百万!

Java 架构 面试 程序人生 编程语言

Uber 如何实现 Go 代码中的动态数据竞争检测_架构_Uber Engineering_InfoQ精选文章