一、项目介绍
汽车之家社区于 2005 年上线,作为之家最老的业务之一,十四年来沉淀了亿级帖子、十亿级回复数据,目前每天有千万级 DAU、亿级的访问量,接口日均调用量 10亿+次 。期间经历过架构升级重构、技术栈升级等,但其数据始终存放在SQL Server中,随着数据的不断递增,我们在使用SQL Server 数据库方面遇到了很多瓶颈,以至于我们不得不寻找一个新的数据库替换方案。二、使用SQL Server遇到的瓶颈
随着业务的不断扩大,汽车之家社区的访问量和发表量不断上涨,遇到的数据库问题也越来越多,下面列举两个必须很快要解决掉的问题:历史上,之家社区回复库采用了分库分表的设计,用以解决SQL Server单表过大的时候性能下降等问题。时至今日,回复库有100+个库、1000+张表(根据帖子ID分库分表)。这本身并没有问题,代码写好了,数据该写哪里写哪里,该读哪里读哪里。但是随着应用的发展、需求的变化,我们发现在实现某些需求时,分库分表的结构难以满足。我们需要数据逻辑上在一张表里。
近些年来,随着业务加速成长,数据量突飞猛进,而硬盘容量是有限的,每台服务器上能扩展的硬盘数量也是有限的。致使每隔一段时间都要增加更大容量的存储服务器来应对,而且这个事情一开始是很复杂的,涉及到很多关联项目,即便到现在我们轻车熟路了,每次换服务器的时候依然需要关注它,并且大容量数据库服务器价格昂贵。我们需要让扩容对应用来说,无感知。
在2018年底的时候,公司专门成立了虚拟架构组来调研新的数据来解决之家社区遇到问题,经过各种分析和测试,今年年初确定方向为分布式数据库,一共调研了三款当前比较火的分布式数据库:TiDB(PingCap)、Ignite(ASF-TLP)、CockroachDB。经过无数次测试我们最终选择了TiDB,主要有以下几个原因:
兼容MySQL协议与生态,上手门槛低
跟TiDB官方一直保持比较好的技术沟通
TiDB公司在北京,有问题可以当面解决
TiDB的设计架构更加优秀
官方社区比较活跃,文档丰富
官方的技术人员经常到公司进行交流
TiDB的研发同学到之家进行技术交流
我们去TiDB进行系统的课程培训
下面引用TiDB官方的一段描述: “TiDB 是一款定位于在线事务处理、在线分析处理( HTAP: Hybrid Transactional/AnalyticalProcessing)的融合型数据库产品,实现了一键水平伸缩,强一致性的多副本数据安全,分布式事务,实时 OLAP 等重要特性。同时兼容 MySQL 协议和生态,迁移便捷,运维成本极低。“ 从中我们不难发现,TiDB切实的解决了我们在应用SQL Server时候的痛点:水平伸缩:在当前集群内可以随时加节点,更换节点也轻而易举。
海量数据支持:基于其特性以及业内使用的经验,十亿乃至百亿级别的数据量轻松搞定。
高可用:相较SQL Server的主从模式,TiDB使用基于Raft协议,可以实现100%的数据强一致性,并且多数副本可用的情况下,可实现自动故障恢复。
HTAP:TiDB自身就支持一定程度的OLAP场景,更复杂的OLAP分析可以通过 TiSpark 项目来完成。对于更深度的OLAP应用,我们也已经在实践的路上。
3.2 实践出真知
基于以上理论的支持,我们进行了大量的功能测试、性能测试、异常测试、业务接入测试等。
OLTP测试:2000万数据,500并发线程测试,在OLTP场景测试下TiDB的响应时间99%在16ms以内,满足业务需求。且在数据量级越来越大的情况下,TiDB会体现出更大的优势,后续还可以通过添加TiDB/PD/TiKV节点来提高读写性能。
OLAP测试:50G TPC-H测试,TiDB相较MySQL有很大的速度优势。
Query |
TiDB(second) |
MySql(second) |
1 |
201.89 |
1285.14 |
2 |
30.62 |
35.78 |
3 |
133.73 |
1789.76 |
4 |
31.47 |
311.68 |
5 |
600.01 |
553.70 |
6 |
77.13 |
298.15 |
7 |
78.81 |
818.32 |
8 |
512.43 |
1542.10 |
9 |
309.06 |
3478.29 |
10 |
48.31 |
595.37 |
11 |
37.86 |
201.42 |
12 |
600.01 |
385.05 |
13 |
121.17 |
648.64 |
14 |
68.92 |
336.48 |
15 |
0.01 |
1080.20 |
16 |
90.70 |
192.12 |
17 |
315.73 |
94.43 |
18 |
600.00 |
308.18 |
19 |
94.02 |
139.46 |
20 |
87.58 |
569.54 |
21 |
600.012 |
1410.61 |
22 |
62.07 |
47.89 |
异常测试: 我们测试了PD、TiKV异常宕机情况下的表现,对业务影响很小,可实现自动故障恢复。
SQL Server 和 TiDB 的部分字段类型是不一样的,通过查阅相关文档,将不同的字段一一对应后再在TiDB中建表,例如DATETIME的精度问题。
同步时将分库分表的数据合并到一个表里,值得庆幸的是原有设计中,我们除了自增主键 ID,还有一份业务 ID,其在各个表中均不重复,这样省了不少事情。
一次性导入十亿级数据以及后续增量同步的过程中,如何保证数据的一致性。
如果TiDB在生产时出现了不可预估的问题,一时无法解决,那我们必须立刻切换到SQL Server,保证业务不受影响。换句话说,在TiDB中产生的数据需要实时同步回SQL Server。
因为访问量比较大,切换时间必须控制在秒级。
因为SQL Server是商业数据库,跟开源数据库进行数据同步的方案较少,所以同步方案、架构设计、研发、测试必须我们自己解决。
4.2 整体迁移架构图
下图是我们整个迁移过程的架构图,包含SQL Server 到TiDB的全量同步、增量同步,以及TiDB到SQL Server的反向同步过程。现在,需要确定的是整个项目的迁移流程,有了大的方向,在实施中目标会更明确一些。
以之家社区的业务形态以及数据量级来看,动辄十多个小时的离线迁移是完全不可能接受的,我们只能在凌晨1:00-3:00这个时间窗口来完成迁移,且时间越短越好。
所以我们选择在线迁移的方案,在线迁移稍微复杂一些,流程上有准备全量数据,然后实时同步增量数据,在数据同步跟上(延迟秒级别)之后,采用滚动升级的方式将应用的读流量切换到TiDB上。
观察应用正常运行,进行短暂停机和关停SQL Server写权限,确保没有数据再写入SQL Server, 就可以将写流量指向TiDB,至此迁移完毕。
整个迁移流程中,应用的读数据场景不受影响,写入场景受影响周期为停机(关写权限)到写流量指向TiDB。
下图是我们梳理出来的流程图,我们在整个迁移的过程中必须严格按这些流程执行。
下面会详细介绍全量和增量同步的实施方案。
五、全量同步
首先我们要感谢以下两个开源项目,站在巨人的肩膀上使我们节约了很多时间。https://github.com/alibaba/yugonghttps://github.com/alswl/yugong
愚公是阿里巴巴推出的一款Oracle数据迁移同步工具,而作者alswl在此基础上实现了SQL Server数据源的支持。
在此愚公的使用方法我们不再赘述,感兴趣的同学请自行查看。
在认真拜读了大神的项目,并进行了相关测试后,发现它并不能100%满足我们的需求。
Yugong 数据流是标准 ETL 流程,分别有 Extractor、 Translator、Applier 这三个大类来实现 ETL 过程。
首先讲Extractor,愚公原有的配置方式是将需要导出的库表写在配置文件当中,这对于1000+张表来说,太不现实了。这里我们增了一个新特性,在不配置需要导出的表名的情况下,将数据库中所有的用户表读出来,并通过一个新增的配置项进行正则匹配,以此决定哪些表需要进行数据同步。
#查询表SELECT name FROM sys.databases WITH (nolock) WHEREstate_desc = 'ONLINE'#查询开启CDC的表SELECT name FROM %s.sys.tables t WITH (nolock) JOIN%s.[cdc].[change_tables] ct WITH (nolock) ON t.object_id = ct.source_object_id
其次,合库合表后,原有SQL Server中各个表的自增主键ID冲突,所以新增实现RowDataMergeTranslator,其功能是,读取内存中的 RowData然后进行转换,将从SQL Server中读取的行数据,丢弃其原有的主键列,转而使用TiDB生成。并根据配置文件决定哪些表需要实现这一特性。
record.removeColumnByName(config.getDiscardKey());
最后的Applier并未做改动,处理好的数据直接写入TiDB。
自此合库合表的事情我们解决了。
六、增量同步与实时校验
在实现这部分需求的时候,我们应用了SQL Server的CDC,并在增量同步的基础上增加了延迟验证数据正确性的功能。
更多关于CDC的内容,这里不再赘诉,你只需要知道它能获取到增量数据。
#CDC官方文档https://docs.microsoft.com/en-us/sql/relational-databases/track-changes/about-change-data-capture-sql-server?view=sql-server-ver15
需要注意的是,CDC开启的时机需要在全量同步之前,保证CDC记录可以覆盖全量同步过程中产生的增量数据。
根据以上的流程图可以看到,Producer从SQL Server中读取CDC日志,并将其转化成一条包含表信息、列信息和行数据的消息,投递到Kafka中。下游的消费者在拉取到消息之后,把数据转化成兼容MySQL的SQL语句在TiDB中执行(这里也实现了合库合表),从而实现整个数据增量同步的过程。
这里还有另一个消费者实现数据校验功能,它会延迟五秒消费同一队列,并通过提取主键(或索引)的方式从TiDB中查出该条已经写入的数据,将两侧的整行数据做比较(本实践中去除主键后比较),如果有问题会进行尝试重新写入,如出现异常则向相关人员发送报警。
在实现了这些并进入到测试阶段后,我们发现了一个问题,1000+回复表,对应1000+CDC日志表,一个Producer就需要开启1000+线程,以设计的5s间隔去轮询这些表时,服务器CPU直接就跑满了,产生了大量线程等待,轮询CDC日志的及时性无法保证。通过分析业务和DBA查询得知,其实之家社区每天产生的回复有95%都集中在最新的5%的帖子当中,换言之,我们只有几十张表需要如此高频的去检索CDC日志,其他的表我们通过增加轮询间隔、分批部署等方式,将这个问题解决了。
细心的同学读到这里会发现,校验功能其实逻辑上并不严谨,如果说在五秒钟内上游数据产生了变更,就有可能会产生拿着新数据去校验老数据的问题。这里有两个解决方案,一、采用单partition的topic和单个消费程序,保证增量同步和校验的顺序严格一致,但此种方案性能相对较低,可用性无法保证。二、我们将SQL Server中的表行加入上版本戳(rowversion),将版本戳一并同步到TiDB中,校验时比较该值,如不一致则放弃本次校验,本方案会损失一定的校验样本,但可通过增加Partition和消费者提高性能和可用性。
七、回滚方案
之前我们提到了,当项目切换到TiDB以后,需要预防其出现不可预估的问题,能够随时切回SQL Server才能保障万无一失。TiDB的binlog使得这件事情轻而易举,我们使用官方提供的Pump和Drainer将binlog抽取到Kafka之中,解析数据变更的内容,根据业务ID计算出数据在SQL Server中原本属于哪个库哪个表,然后进行数据同步。
解析binlog(protobuf协议)
通过业务ID决定数据写到哪个库表 八、之家社区业务TiDB迁移改造就业务的改造这一环节,因历史积淀,需修改的地方很多,分布于各个项目之中,我们采取通过接口查找实现、搜索代码、DBA帮助抓取SQL的方式,保证涵盖了100%的相关业务,只有这样才能保障上线后无故障。
数据访问层增加对MySQL语法的支持。
去掉SQL Server中的存储过程。
SQL Server和TiDB(MySQL)的语句和函数支持不尽相同,逐个改造、测试并优化。
根据TiDB索引的原理以及梳理出来的SQL语句,重新建索引。
与此同时,我们针对每一条改造后的SQL都进行了优化,使可以精确的命中最优的索引,从而实现了在十亿级数据量下,TP业务99%的响应时间在12ms,99.9%的响应时间在62ms。
九、TiDB周边体系建设除以上迁移流程所涉及到的功能点以外,我们还制定了一些开发规范和一些实用工具的研发,用以保障TiDB在汽车之家更好的应用。
我们建立了完善的TiDB开发规范、运维规范、上线规范,并在公司内部对开发同学进行相关的培训。
开发了实时慢SQL分析工具——TiSlowSQL,该工具可以提供实时、多维度、全视角的SQL报告,帮助我们快速定位慢SQL导致的集群级故障。
为解决监控单点问题,我们自己开发了一套监控工具,对TiDB核心组件进行监控,后续会将监控系统统一迁移到之家云平台。
定期在汽车之家大学举行技术培训,定期在组内进行技术分享,经验总结。
十、总结与展望
汽车之家社区已于9月底正式上线分布式数据库TiDB,目前运行稳定。在其他业务迁移完成之后,之家社区的SQL Server服务会逐步下线。对于本次迁移的过程我们做了以下几点总结:
通过不断的优化SQL,目前线上TP99 稳定,与迁移之前并无太大差别,跟测试效果相符。对用户和业务都无感知。
随着业务的不断扩大,可以更好的应对数据的暴增,再扩容集群就不需要找昂贵的大存储机器,而且可以在线不停业务随时扩容。
本次迁移我们积累了SQL Server转TiDB的很多经验,可以为其他团队使用分布式数据库TiDB提供技术支持,让其他团队在迁移过程中节省时间。
目前正在与TiDB官方沟通,准备把迁移方案和与业务无关的迁移逻辑放到开源社区。