Golang In PingCAP

随着 Golang 在后端领域越来越流行,有越来越多的公司选择 Golang 作为主力开发语言。本次 GopherChina Beijing 2016 大会上,看到 Golang 在各家公司从人工智能到自动运维,从 Web 应用到基础架构都发挥着越来越多的作用。可以说 Golang 在这几年间,获得了长足的进步。 PingCAP 是一家由几名 Golang 粉丝创建的数据库公司。在我们的日常工作中,除了对性能有苛刻要求的最底层存储引擎外,大部分都是使用 Golang,算是 Golang 的重度用户。我们从 Golang 语言以及社区中收益颇多,TiDB 在短短半年的时间内,从无到有,从默默无闻到广泛关注,已经成长为 Golang 社区的明星项目。我们在这个过程中也积累了不少工程实践经验,这里想和大家分享一下。 ## Why Golang? 网上已经有无数的文章描述 Golang 的优点,所以没有必要一一列举。我们选择 Golang 并不是因为跟风或者是我们是 Golang 的粉丝,而是经过理性的分析和讨论,认为 Golang 最适合我们的业务场景。 ### 开发效率高 作为技术创业公司,我们期望维护一个精英技术团队,人数不多,但是交付速度快、代码质量高。这样我们需要一门高效的语言,Golang 在这方面令我们非常满意。Golang 易于上手,有过其他语言经验的人,很容易转到 Golang。超强的表达能力、完备的标准库以及大量成熟的第三方库,使得我们可以专心于核心业务。自动内存管理,避免了 c/c++ 中的指针乱飞的情况,易于写出正确的程序。从15年6月写下第一行代码开始,到15年9月我们已经完成第一版的产品,并且达到可开源的要求。开源后我们从社区中获得了不少有价值的反馈以及大量的第三方 Contributor。从 GopherChina 大会上,我们注意到除了大公司处理海量并发时会采用 Golang 外,越来越多的创业型公司也在使用 Golang,我想这和 Golang 的易于上手、开发效率高有很大关系。 ### 并发友好 对于一个分布式数据库,相比较延迟而言吞吐量是一个更关键指标。当然这里并不是说延迟可以无限大,而是在保证延迟相对较低的情况下,尽可能的提高吞吐。TiDB 的设计目标是能响应海量的用户请求,我们期望有一种低成本的方式同时处理多个用户连接。同时数据库内部的一些逻辑也要求在处理用户请求的同时,还有大量的后台线程在做自己的工作。 Golang 在这方面有天然的优势,甚至可以说 Golang 就是一门为了并发而生语言。goroutine 和 channel 使得编写并发的程序变得相当容易且自然,很多情况下完全不需要考虑锁机制以及由此带来的各种问题。单个 Go 应用也能有效的利用多个 CPU 核,并行执行的性能好。与此同时,Golang 运行的性能虽然不如 C/C++,但是还没有数量级的差别,可以满足对延迟的要求。 ### 部署简单 我们把系统部署简单易用作为 TiDB 的一个重要的设计目标。我想部署和维护过其他分布式系统(比如 Hbase)的同学,对这一点一定深有感触。 Golang 编译生成的是一个静态链接的可执行文件,除了 glibc 外没有其他外部依赖。这让部署变得很方便。目标机器上只需要一个基础的系统和必要的管理、监控工具,完全不需要操心应用所需的各种包、库的依赖关系,大大减轻了维护的负担。 ## Good Practice 在使用 Golang 的过程中,我们也获得了一些很好的实践经验,包括语言使用上的,以及工程上的经验。 ### 重视单元测试 Golang 带有一个简单好用的单元测试框架,包括功能测试和性能测试。每个模块都能以非常简单的方式进行测试,以验证功能的正确性,并且避免后续被别人改错。在做 Code Review 时,我们强制要求所有的改动必须有 test case,否则 PR 会被拒绝。对于性能关键的模块,我们会加上 bench test,每次改动后会观察性能的变化。 ### 重视 CI 数据库是一个复杂的系统,单靠单元测试无法保证系统的正确性,我们需要大量的集成测试。受益于 MySQL 的生态,我们可以获得大量可以直接用的测试资源,包括各种 ORM 自带的测试、MySQL 自带的测试、各种 MySQL 应用的测试。 TiDB 除了在提交 PR 时会做最基本的测试之外,还有十几个集成测试随时待命。我们在内部搭建了 jenkins 系统,每次代码有变动,都会自动构建这十几个测试集。如果有任何一个 Fail 了,相关人员必须停下手中的工作,马上去 Fix。另外 jenkins 也可以作为性能监测工具,每次提交后都会记录下运行时间,可以和历史记录中的时间作比较,如果运行时间突然变长,需要立即解决。 ### 重视代码质量 代码是技术型公司最重要的产品,而且我们又是一家以开源方式运作的技术公司,代码的质量相当于公司的招牌,我们在这方面花了很大的力气。 我们制定了严格的 Code Review 制度。任何 PR 都需要有至少两个maintainer 看过,并且认为改动 OK,给出 LGTM 后,才能合并进主干。这两个做 review 工作的人要保证看过、理解每一行代码,并且要到能独立修改。否则提交 PR 的人需要给 reviewer 进行详细的介绍,直到讲懂为止。 另外我们还利用一些第三方工具来检测代码质量。比如 [GoReportCard](https://goreportcard.com/ "GoReportCard"),这个工具会分析代码中的潜在问题,如赋值过的变量在作用域内没有被使用、函数过长、switch 分支过长、typo。项目在这里面的排名在一定程度上反映了代码的质量。目前 TiDB 的代码质量被评为A+级别。 ### 一切自动化 Go 自带完善的工具链,大大提高了团队协作的一致性。比如 gofmt 自动排版 Go 代码,很大程度上杜绝了不同人写的代码排版风格不一致的问题。把编辑器配置成在编辑存档的时候自动运行 gofmt,这样在编写代码的时候可以随意摆放位置,存档的时候自动变成正确排版的代码。此外还有 golint, govet 等非常有用的工具。TiDB 将 golint、govet 的检查加入 Makefile,每次构建时,都会自动测试,这样可以防止一些低级的错误被提交。 ### 善于利用 Pprof 在系统性能调优或者是死锁监测方面,一个 Inspector 机制能极大的提高效率。幸运的是 Golang 自带 profile 工具,简单的几行代码就能方便地提供一个 HTTP 界面,展现当前系统的所有状态。目前在开发过程中,我们会默认打开 pprof,这个机制也不止一次地帮助我们发现系统中的问题。 ## 那些年我们踩过的坑 Golang 是一门很好的语言,但并不是一门完美无缺的语言,我们在实践中也踩过不少坑。 ### interface{} 的性能问题 数据库中有大量的数据类型,所以我们需要一个统一的结构来处理所有的类型。我们最初的方案是选择 interface{},这也是 Golang 中比较自由的选择。所有的数据类型都可以赋值给 interface{},所有的数据类型相关的函数也都以 interface{} 作为参数,然后在内部用 switch 语句判断类型,这样程序写起来比较简单。但是很快我们发现大量的 type assert 拖慢了我们的程序,比如下面这段代码: var val interface{} val = int64(100) 经过我们测试,把一个整数赋值给一个 interface{} 类型的变量,会触发一次内存分配,通常要耗时几十到上百纳秒。在运行 SQL 语句时,会有大量的类似操作,对性能的损耗严重。为了解决这个问题,我们调研了其他数据库的解决方案,最终采用自定的数据包装类型 Datum 取代 interface{},这个 Datum 需要能存放各种类型, 实现 value 对 value 赋值。同时为了减少空间占用, Datum 内部的属性会在多种数据类型之间重用。上面的代码重构后变成: var d Datum d.SetInt64(100) 重构后,在我们的 bench 结果中,表达式计算相关操作的性能,提升 10 倍以上。 ### 包依赖问题 Golang 的包依赖问题一直被人诟病,可以说到目前为止,也没有完美的解决方案。 ### Golang 中隐藏的一些 Bug 相比 C/C++/Java/Python 等语言,Golang 算是一门年轻的语言,还是存在一些 bug。上周我们遇到一个诡异的问题,调用 atomic.AddInt64 时,在64位系统上 OK, 但是在 i386 系统上,会导致 crash。我们通过内部的 CI 发现问题后,经过研究发现这是 Golang 的一个 bug,对于 32 位系统,需要自己来保证内存对齐。 ## Conclusion 相比 C++/Java/Python 等语言,Golang 不支持许多高级的语言特性,但从工程的角度讲,Go 的设计是非常优秀的:规范足够简单灵活,有其他语言基础的程序员都能迅速上手。 TiDB 设计之初,我们定了一个原则: Make it run. Make it right. Make it fast. Golang 很好的满足了我们的原则。高效的开发使得我们很快能做出能 run 的产品,自动的 GC 以及内置的测试框架有利于我们写出正确的程序,方便的 Profile 工具帮助我们进行系统调优。 除此之外,Golang 还有一个成熟友好的社区,Gopher 们在从社区获得收益的同时,很愿意向社区做贡献,大量高质量的第三方库就是最明显的体现。在平时开发遇到 Golang 相关的问题时,很容易借鉴到别人的经验,节省了我们大量的时间。 最近整个 TiDB 团队都在做稳定性和性能相关的事情,也在积极地和国外优秀的开源团队交流协作,在工程和实践方面,有蛮多可以借鉴的经验,等我们11月份忙完 GA 版本的发布之后,会和大家进一步分享。另外非常感谢谢大对整个 golang 社区的贡献,让 PingCAP 从社区中汲取了很多的养分和鼓励,希望大家一起加油,共同推动社区的发展。

3 个评论

很好的总结啊
很好的总结啊
专业的总结

要回复文章请先登录注册