写点什么

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:0913043

评论

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

史上最全的Java容器集合之equals 和 hashCode

自然

集合 Java core 9月月更

Android 开发环境的搭建(Android Studio + 逍遥模拟器)

Android Studio 安卓开发 9月月更

Python语法之模块和包(2)

芯动大师

9月月更 模块创建 包的应用

Python语法之异常处理

芯动大师

异常处理 9月月更 Python异常处理方法

挑战Python的语法练习

芯动大师

项目实战 9月月更 模块创建

天天都在谈的防火墙到底是个啥,有哪些分类?如何选择防火墙?

wljslmz

网络安全 防火墙 9月月更

作业一

小虎

架构实战营

【云原生 | 从零开始学Kubernetes】十二、k8spod的生命周期与容器钩子

泡泡

Docker 云计算 云原生 k8s 9月日更

APICloud可视化编程(二)

YonBuilder低代码开发平台

前端开发 APICloud 多端开发

Java | abstract关键字【面向对象的第三大特征——多态】

Fire_Shield

Java 9月月更 abstract

架构实战训练营模块 1 作业

atcgnu

信我,Java面试的苦只吃一次,看完这八股文合集,渣渣也能进大厂

钟奕礼

编程 java;

什么是地址转换协议ARP?工作流程是什么样的?

wljslmz

9月月更 ARP

vivo鲁京辉:数据安全与隐私保护是消费者的基本权利,是企业的“铁律”

极客天地

极客时间-架构师训练营作业-模块一

沐の爹

架构实战营模块一作业

π

架构实战营

面试中经常问到的几个问题,快来看看能答对几道吧(Java)

钟奕礼

java; 编程、

这些Java基础知识,诸佬们都还记得嘛(学习,复习,面试都可)

钟奕礼

编程 java;

实战案例,个个是经典,不愧是阿里P7私传“并发编程核心讲义”,竟能讲的如此通透

程序知音

Java 并发编程 多线程 高并发 后端技术

01简单架构分析

神奇的叶叔叔

开发者有话说|要不是家里穷,我也不想当码农

三掌柜

个人成长 个人感悟

程序员未来职业规划路线,请收藏

收到请回复

Java 程序员 架构 职业规划 语言 & 开发

【编程实践】详解MySQL在Python中的使用(1)

迷彩

Python MySQL 数据库 pymysql 9月月更

史上最全的Java容器集合之HashMap(源码解读)

自然

集合 Java core 9月月更

详解数据计算能力的四种类型

穿过生命散发芬芳

9月月更 数据计算

【编程实践】详解 MySQL 在 Python 中的使用(2)-pymysql的使用

迷彩

MySQL 数据库 增删改查 pymysql 9月月更 数据库操作

Redis进阶知识点(可学习,可复习,可面试)

钟奕礼

java; 编程、

【最右】面向TS生态的新型Flutter框架

刘剑

typescript 小程序 移动端 动态化 flutter for web

2022-09-28:以下go语言代码输出什么?A:1 1;B:1 2;C:2 2;D:不确定。 package main import ( “fmt“ ) func main() { var

福大大架构师每日一题

golang 福大大 选择题

后疫情时代,RTE“沉浸式”体验还能这么玩?丨RTE 2022 编程挑战赛赛后专访

RTE开发者社区

人工智能

Groovy closure 与Java function转换

FunTester

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