我于2019年7月底从腾讯公司TEG计费平台部离职,这两个月一天都没闲着,在忙着开发自己的项目。最近很多人问起我的近况和/或去向,包括同学朋友,以及各个公司的猎头和HR。不仅有必要回应这些关心我的朋友们的疑问,同时在腾讯这几年丰富的工作经历和收获,也很有必要总结一下,故有此文。我会分作3篇来发布,第一篇是我的工作总结与技术方面的感悟,第二篇作为一个比较有趣的话题,我会用几个片段记录一下我在腾讯感受到的企业文化的特点,以及与外企(Oracle等)的区别等,这些是我的一些美好的或者有趣的回忆。第三篇我会把我正在并将要继续做的项目向大家做一个介绍,相信会有同行很感兴趣,或许未来对VC也会有参考意义。同时也希望有志同道合的同行能与我一起推进这个项目。合作相关事宜请随时微信联系或者留言给我。另外想把我搬到其他公司的猎头们就不要激动了,因为腾讯让我签了一个竞业协议,这个协议禁止我在2020年7月31日之前在国内主要的几家互联网公司工作,并且这个列表是开放的,也就是“包括但不限于A,B,C,D,等”。而且我也希望把我的想法完全实现,这个在第三篇会详细介绍。
第一篇工作总结与感悟
我于2015年10月加入腾讯计费平台部,负责TDSQL的数据库内核研发,这几年我主要做了这么三大类事情:TDSQL分布式事务处理的设计和开发,TDSQL的数据库内核(也就是percona-mysql和mariadb)的各种功能开发和BUG修复。下面就分别总结这三大类事情,第一类为主,后两类简单带过。之前的文章中也有过功能开发和bug修复方面的细节介绍,感兴趣的读者可以这我的公众号文章中找到。在第一类事情之后还会介绍一下我对软件开发流程和方法论的理解以及在Oracle以及腾讯两大公司之间的对比。
TDSQL的分布式事务处理的设计和开发
我在入职面试的时候,部门的领导和同事就问了我多个分布式事务方面的问题,进入TDSQL团队后,harly也问过我分布式事务的开发难度,预期的性能等。所以这块功能,他们已经早有考虑想要做,这也是腾讯招聘我的重要原因。现在想来,这也是最让我有自豪感和成就感的一大块事情,所以会在这部分多说几句。
于是,从2016年8月份开始,我主导设计了TDSQL的分布式事务处理功能,并且带着我的两个徒弟实现了TDSQL的分布式事务处理功能。做分布式事务处理,我一直认为最适合的就是数据库理论中最成熟的两阶段提交(2PC)算法。我前面写过文章分析了为什么google 的percolator等算法为什么不适合分布式数据库系统,感兴趣的读者可以去参考。所以我就基于2PC做了一版设计,然后根据同事们反馈做了修订,最终定稿。然后就开始原型实现,大boss的要求是3个月完成原型开发,看到功能和性能测试结果。最终我带着我的两个刚毕业入职的徒弟,3个人提前一个月,在2016年11月底完成了原型开发。这个原型在功能和性能方面,证明我的这个设计思路是可行的。于是我们紧接着在2017年上半年,又把这个原型系统进一步优化改进,最终成为正式发布的版本。然后在后面1年时间内,通过持续的容灾测试,我们又陆续发现了mysql在分布式事务处理方面的诸多空白和漏洞,然后我经过艰苦的努力,把mysql的所有这些空白,漏洞和问题全部解决了,填补了mysql在分布式事务处理方面的诸多空白。回想起那些‘峥嵘岁月’,现在还是历历在目。比如,当时为了赶在2017年3月底的工行选型测试之前出一个正式版本,我曾经连续3天住在公司赶工做开发,晚上干到2点多,不回家直接在办公室的行军床上睡觉,第二天接着干。在我之前的职业生涯中还没有过这样的经历。后来也有几次深夜突击解决问题的经历,现在想来也算是可以拿来作为“想当年”系列的真实故事。
TDSQL的分布式事务处理主要涉及到TDSQL的3个模块:
数据库内核:在原型开发时使用的还是mariadb-10.1.9,后来我决定改用Percona-mysql-5.7.17-11版本,因为我发现mariadb10.1.9的分布式事务支持问题太大太多了,而mysql-5.7.17要好很多,尽管仍然有不少问题必须解决。
网关模块:需要实现全局事务管理器,也就是分布式事务协调器,协调推进两阶段提交过程
agent模块:若干辅助功能
其中网关和agent模块中的任务由我的两位徒弟分别完成开发,我就集中精力全局统筹并且完善mysql的分布式事务功能,在mysql分布式事务处理的容灾和事务一致性方面做了大量工作。在2016年底到2018年这两年多,通过大量的容灾测试,我们发现了mysql在分布式事务方面有很多问题,这些问题都全部解决了,最后容灾测试可以长期稳定运行。我向mysql官方提交的几十个相关的bug报告,每一个都带有解决该bug的patch。没有这些patch的话,mysql虽然仍然可以执行分布式事务,但是不具备可靠的容灾和故障恢复能力。在发生诸如mysql进程异常退出(比如机器断电,crash,被kill掉等),主备切换,备机断开重连,等情况时,会出现数据不一致,丢失已提交的事务的改动,主备机数据不一致,备机复制卡死等故障。这些故障,一旦发生,哪怕只有一次,对金融类型的用户来说,都将是巨大的损失,不仅面临资金损失,还面临监管问责。因此,必须严格确保任何灾难故障情况下正确无误,达到一流的服务质量。特别是‘主备切换’和‘备机断连’ 的发生几率并不低,所以绝不能抱有侥幸心理。
有了分布式事务处理,TDSQL就不再需要限制每个事务只写入一个后端set了(每个set就是一个一主两备的mysql binlog 复制集群,下同)。在事务处理层面,用户可以透明地把tdsql当作一个单机数据库来使用。tdsql就从一个简单的分库分表的sharding solution,变成了一个分布式数据库。后来,我又指导我的徒弟Hotz同学实现了跨set的多表连接,这块功能经过Hotz两年多的逐步开发,目前也能满足大多数用户需求了,当然在性能和资源消耗方面还有较大的改进空间。
有了完备的分布式事务处理和基本好用的分布式查询处理后,TDSQL获得了越来越多的银行等金融类用户的青睐,前段时间tdsql在张家港银行上线了,受到了很多业内同行的关注。其实在此之前TDSQL在很多中小银行已经得到了应用,并且在腾讯云上面也有很多保险,证券等金融行业的用户。
TDSQL适合金融用户的另一个功能是‘强同步’,也就是mysql主机上面要提交的任何一个事务,其binlog都必须在至少一个备机上被可靠地存储下来了。这块功能是我来腾讯之前就由harly完成了开发,我接手TDSQL的数据库内核后,对这块功能做了些性能方面的改进,主要是在备机上做relay log的成组刷盘和确认(group flush&ack),从而降低备机ack的开销,提升主机事务提交的速度。
TDSQL依靠分布式事务处理,分布式查询处理和强同步这几块功能,以及完备的数据一致性保障和容灾及故障恢复能力,以及较好的性能,成为国内非常适合金融类业务的分布式数据库系统。特别是,与OceanBase相比,TDSQL虽然在性能方面略处下风,但是由于TDSQL使用开源的MySQL数据库这一点,也得到了很多用户的肯定:有些用户表示他们不希望从闭源的Oracle迁移到另一个闭源的数据库系统上面,没有一个强大的开源社区做后盾,缺乏安全感。而与风头正劲的TiDB相比,TDSQL的性能大幅度领先TiDB。不论是我们团队内自测(sysbench,tpcc),还是外部很多用户的自测都是这样的结论。当然,TiDB仍然是一个有特色的开源数据库产品,它有它擅长和适用的领域。如果没有TiDB团队的成功,我也不会想过要出来创业做产品,TiDB团队开创了基础技术创业的一个先河。
当然,TDSQL的成功,还有一个最重要的原因,就是腾讯的巨大的数据和业务流量,以及腾讯这块金字招牌。计平有很多业务本来就是金融类型的,而TDSQL的所有功能都是部门内部先使用,逐步扩大灰度范围,扩大到其他部门和BG,再扩大到外部用户,所以tdsql开发者有大量的用实际业务流量来“灰度”的机会。在这个灰度过程中,如果设计和编码有问题和bug,都会被暴露出来,这就有了充分的试错机会。
灰度与软件开发方法论
没有大型互联网公司的业务流量和规模优势,小的技术团队怎么才能弥补自己的劣势呢?那就是要执行科学严谨的软件开发流程,做彻底的完备的单元测试和功能测试,以及需要开发者有更强的技术能力,在功能设计和实现过程中,不会犯低级错误或者重大失误。同时,通过免费使用来扩大用户群,这样也就达到了类似的效果。从TiDB这几年的历程来看,通过这些方法是可以建立用户信任的。
对于纯后台技术产品来说,灰度是一个‘边做边用’的方法论,特别适合互联网类的公司。对于互联网公司来说,软件只是他们的生产工具,而不是最终产品。所以只要这个生产工具能够满足自己的互联网服务的正常运行就足够了,最重要的是服务要持续可用,延时和TPS能够达标。
用我在Oracle和腾讯的工作经历对比可以感受到,Oracle MySQL那边的同事的软件开发能力明显更加全面和深入,并且软件开发的工作效率更高很多,因为我们总是坚持使用科学的软件开发流程和方法论,因而看似并不拼命,看似进展不快,但是其实是稳扎稳打,步步为营,稳步推进。但是他们的弱点是没有天天沉浸在高强度的软件使用场景中,因而对用户的真实需求的理解不够具体,全面和深入,见过的一些由各种诡异的软硬件问题,网络问题或者使用方法问题导致的疑难杂症没那么多。而腾讯这边的高手同事们则擅长对互联网业务的技术需求的理解和相关技术的掌握。对互联网公司的技术人员来说,最重要的是让自己写的程序持续不间断地运行,规避运行期间各种可能发生的状况,吞吐量和响应时间达到要求,为公司的互联网服务的持续运行提供可靠的技术保障。
在tdsql分布式事务处理的开发过程中,我在设计过程中就经过了周密而深入的思考和实验,再加上harly等多位组内高手的献计献策,并且我们在上线之前做了非常充分的功能测试和容灾测试,从而确保了实际上线后的逐级灰度过程中,并没有出现任何严重的问题。这里要感谢我的徒弟东志同学,他写了一个非常强大的容灾测试脚本,可以模拟各种故障,然后还能通过打印binlog,查找出错的事务等办法辅助分析问题,这对我们分析和解决相关的bug有很大的帮助。因此,这一块功能在现网上面并没有出现过数据不一致的情况。
没有业务流量来试错的传统软件公司,比如Oracle和微软,仍然通过执行科学严谨的软件开发流程,做彻底的完备的单元测试和功能测试,以及开发者有更强的技术能力,做到了mysql以及其他核心产品的高质量。反过来说,从实际情况可以看到,由于有业务流量可以试错,反而会导致开发者对灰度的依赖,容易忽视了标准的软件开发方法论。
有些开发者可能觉得测试是一个‘低级’的工作,其实在Oracle 这类经验老到的传统软件公司,测试工作和人员都是非常重要的,测试人员可以决定开发人员的工作最终能否完成。测试人员从功能开发的起始阶段就开始跟进了,开发者做功能设计的时候,在总体设计和详细设计中要写清楚功能总体描述,细节描述和接口定义,也就是这个功能是干什么用的,用户和测试者如何使用这个功能(通常是一些sql语句,命令或者函数)。然后测试人员在理解了功能需求后就开始着手测试的设计了。测试脚本的开发是与功能开发基本同步进行的。到了开发者做完一个功能点后,测试脚本很快就可用了,这个开发过程中开发人员和测试人员是密切沟通的。并且,任何一块功能,开发者的代码要提交到主干代码分支,必须有对应的测试人员放行才可以,否则你的代码都进不了主干,你的工作成效就是0。当你开发的功能通过了所有已有测试和新增的测试之后,作为代码合并到主干的最后关卡之一(另一个最后关卡是代码review通过),测试人员会做若干次coverage统计,确保你增加的每一行代码都是被测试实例执行过的。开发者需要检查代码覆盖检查报告中没有执行到的每一行代码,要么自己增加脚本测试用例或者单元测试用例来覆盖到这些代码行并且通过所有测试用例,要么用充分的理由说明为什么无法覆盖到的理由。这个coverage检查有时候需要来回几轮,最终测试人员才会放行。写到这里,我又想起了我在Oracle MySQL团队工作的那些年,先后与我合作,为我开发测试用例的那几位印度小哥,他们的耐心和细致真的是很极致的,他们帮到了我很多。
这个开发过程虽然辛苦和耗时,但是好处非常明显:无论谁对mysql的代码做了无论多大或者多小的修改,加上针对该功能的充分的测试用例后,只要运行全部自动化测试包,并且全部通过了,那么他的改动就是几乎可以确定是没有任何问题的,因为产品的每一个功能点,代码的每一行,每个模块都经过了无数次测试的执行,验证了其正确性。这样所有开发者的效率都提高了,而且不会出现BUG像按下葫芦浮起瓢一样的情况,也就是regression。毕竟像数据库系统这样复杂的软件,内部的代码模块都是紧密关联和协作的有机整体,修改或者新增某个功能模块,通常需要修改其他若干个模块来紧密配合。由于其复杂性和分工,没人能够完全理解mysql全部的代码,精确地理解自己的某个改动对其他模块的所有各种影响。不仅是mysql,所有复杂软件都是这样的。这时只有完整的测试包能够让所有人的工作效率大大提高,而且它也是保持高水平的软件质量的坚强后盾。
TDSQL数据库内核功能开发
我在腾讯做的第二大类工作就是在TDSQL数据库内核(也就是percona-mysql和mariadb)中开发各种功能,这方面具体的功能细节不在此赘述,我之前写的很多文章中,有不少就是介绍了我所做的TDSQL数据库内核功能的。同时,大量的功能是需要与tdsql其他模块配合来完成整体功能的,单独介绍比较突兀,展开讲篇幅不够。所以除了表分区屏蔽功能,其他功能就此掠过。这里只大致罗列几个独立的,主要的和重要的功能。
1. 表分区屏蔽功能:这个功能是在2017年底,听同事讨论TDSQL的扩容流程时,我想到了TDSQL的扩容功能有一个严重的漏洞。具体细节和影响这里就不说了。有了这个分区屏蔽功能后,这个漏洞就彻底堵上了。
2. 库表回收站:用户执行了drop database,drop table后一段时间之内能够找回删掉的库表
3. Oracle sequence:实现了一个与oracle 的sequence功能完完全全相同的功能,这个功能是很多之前使用oracle的金融用户特别需要的。
4. Update/delete returning:返回更新或者删除的行的全部或者部分字段或者用这些字段做计算得到的行
5. 表删除限速:防止突然删除大表导致的io系统负载突增
6. 线程池性能优化:比percona官方线程池性能有较大幅度的提升
7. 安全类功能:这是一组功能,主要目标是确保用户数据安全,以及确保数据库实例运行安全稳定等
MySQL/MariaDB的bug修复
可以说,tdsql在腾讯公司内部使用的强度,远远大于tdsql的外部用户。毕竟腾讯的业务流量是很少有其他公司可以接近的,国内除了阿里,京东,美团等之外。在这样的使用场景下,mysql即使是经过了oracle测试团队以及全世界无数用户的千锤百炼,也仍然被我们首先发现并解决了多个问题。下面列举几个有代表性的:
1. innodb坏块处理错误
某天DBA同学找我说,某个mysql实例反复重启,经过分析我发现,是innodb处理坏块的代码有个bug,导致mysqld反复crash;详见https://bugs.mysql.com/bug.php?id=90402
2. 反复加载插件导致mysqld内部发生死锁失去响应
执行加载插件命令时,获取的多个mysqld内部数据结构的mutex的获取顺序可能导致等待环路,从而导致mutex死锁。这样的死锁是innodb的死锁检测程序无法检查到的,于是mysqld就永久僵死了。详见 https://bugs.mysql.com/bug.php?id=88693
3. Stop slave卡住无法返回
当备机sql线程读取的relay log末尾有一个不完整的事务的时候,stop slave命令可能卡死无法返回。对于TDSQL来说这是个非常严重的错误,因为stop slave无法返回的话,加入当前这个备机是要升级为主机的,那么主备切换就无法成功,这样TDSQL集群的这个set就无法提供服务了。详见https://bugs.mysql.com/bug.php?id=95249
还有很多,这里就不一一列举了。这类问题,只有在极端情况下才会出现,比如非常重的负载,或者非常长期的使用导致磁盘坏块,数据发生错误等情况,所以它们才是只有大型互联网公司才可能在实际中遇到的问题(当然,现在也有类似问题的故障模拟模块可供测试人员使用)。通过发现和解决这类问题,我很自豪自己可以帮助保障公司业务顺利运转,并且还帮助全世界的开源社区提升了mysql的质量。mysql能经受得住腾讯,阿里等国内顶级互联网公司的业务负载的考验,那么在全世界任何其他公司任何业务负载都不会有问题。
下面是我这几年修复的所有mysql bug的列表: