10億レベルの大きなテーブルを分割する-テーブル分割作業の精神的な旅を記録する

序文

著者は2年前に会社の金融システムの開発と維持を引き継ぎました。システムハンドオーバーの初期段階で、作成者とチームは、システムに5000W+の大きなテーブルがあることを発見しました。追跡コードは、このテーブルが多くのファンクションポイントに関連付けられている資金の流れを格納するために使用されるテーブルであり、このテーブルのデータを使用する多くのダウンストリームシステムも存在することを発見しました。さらに観察すると、このテーブルはまだ月間600W以上の速度で成長していることがわかりました。つまり、半年以内に、このテーブルは1億に成長します。

著者の心:

fcd576b9-09dc-4645-b5ab-1dcafb857b89.gif(麻痺)

この量のデータは、MySQLデータベースでは完全に保守できないため、システムを引き継いだ2か月後に、大きなテーブルの分割に関する特別な作業を開始しました。(実際には、2か月は主にシステムに精通し、蓄積要件を消化するために使用されます)

解体前のシステムステータス

  • 流量計と水に関連するインターフェースは頻繁に残業しており、一部のインターフェースは基本的に利用できません。
  • 主にデータベースに挿入するときに非常に遅いため、毎日の新しいフローは遅くなります
  • 1つのテーブルが占有するスペースが多すぎるため、DBAのデータベース監視でアラームが発生することがよくあります。
  • テーブルを変更することはできません。変更操作を行うと、マスターとスレーブの待ち時間が長くなり、テーブルが長期間ロックされます。

テーブルを分割する目的

  • 大きなフローテーブルのデータをさまざまなサブテーブルに分割して、各サブテーブルのデータが約1000Wになるようにします(経験上、単一のテーブルの1000Wの量はmysqlにとってそれほどストレスがないことがわかります)
  • テーブルを分割することを前提として、さまざまなインターフェイスのクエリ条件を最適化して、各外部インターフェイスと内部インターフェイスの可用性を確保します。mysqlの遅いクエリを完全に強制終了します。

難易度分析

  • この表のデータは、金融システム全体の中で最も基本的なデータと言え、関連する機能や下流のシステムがたくさんあります。これには、非常に厳密な開発、テスト、および稼働プロセスが必要であり、小さなミスがあれば大きな問題を引き起こす可能性があります。
  • 関係する多くのシナリオがあります。統計によると、合計26のシーンがあり、32のマッパーメソッドを変更する必要があり、変更する必要のある特定のメソッドはさらに多くなります。
  • データの量は非常に多く、データを移行するプロセスでは、システムが安定していることを確認する必要があります。
  • 多くのユーザーがいて、機能が重要です。テーブル分割機能を起動するときは、システムが使用できない時間を可能な限り短縮し、システムの可用性を保証する必要があります。これには、チームが完全で信頼性の高いロールアウトプロセス、データ移行計画、ロールバック計画、およびダウングレード戦略を設計する必要があります。
  • 前述のように、テーブルの分割は必然的に一部のインターフェースに変更をもたらし、インターフェースの変更は他のシステムの変換をもたらします。他のシステムの変革をどのように促進するか、そして複数政党による協力の開発、テスト、立ち上げをどのように調整するかは、もう1つの困難です。

全体的なプロセス

drawing3.png

詳細

サブテーブルミドルウェアの調査

分表插件:采用sharding-jdbc作为分表插件。其优势如下:1、支持多种分片策略,自动识别=或in判断具体在哪张分表里。2、轻量级,作为maven依赖引入即可,对业务的侵入性极低。

为提升查询速度,在整个项目的初期,团队成员考虑引入ES存储流水以提升查询速度。经过与ES维护团队的两轮讨论,发现公司提供的ES服务对于我们的业务场景并不匹配(见表),经过反复考量,最终我们放弃了引入ES的计划,直接从数据库查询数据,采用每张表设置一个查询线程的方式提升查询效率。

技术方案 开发成本 弊端
sharding-jdbc+es es 数据库+es 1.向sharding数据源中写数据,向es推送数据2.针对查询需要开发从es读取数据的api 1.es针对资金特有的多字段匹配搜索支持的不是很好2.es 支持每次返回的条数有限,个别大批量查询的功能无法支持3.es不提供特定地集群存储资金流水数据4.es支持分页查询效果不好,有可能比使用数据库查询速度更慢
sharding-jdbc 数据库 数据库 1.向sharding数据源和原有数据源中写数据2.根据查询条件判断从sharding数据源读取数据还是从原有数据源读取数据 1.资金业务场景下sharding支持分页查询不够友好,需要自己实现分页查询逻辑2.sharding需要自己定义数据源,涉及到多数据源事务处理问题

分表依据的选择

分表的方式有很多种,有纵向分表,有横向分表,有分为固定的几个表存储然后取模进行表拆分等等。总的来说,适合我们具体业务的分表方式只有横向分表。因为对于资金流水这种特殊数据来说,是不能清理数据的,那么纵向分表和拆成固定的几个表都不能解决单表数据无限膨胀的问题。而横向分表,可以把每张表的数据量恒定,到一定时间后可以进行财务数据归档。

分表的依据一般都是根据表的某个或者某几个字段进行拆分,最终其实是对数据和业务分析综合出来的结果。总的来说,原则有这几个:

  • 尽可能选择查询条件里最常出现的字段,这样能够减少方法改造的工程
  • 需要考虑根据某个字段拆分数据是否能够均匀分布,是否能够满足单表1000W左右的要求
  • 该字段必须是必现字段,不允许出现空值

综合分析我们的数据以及业务需要,“交易时间”这个分表依据就呼之欲出了。首先,这个字段作为流水最重要的字段之一一定会出现;第二,如果按照交易月份进行拆表,每张表大概也就是600W-700W的数据;最后,有70%的查询都附带“交易时间”作为查询条件。

技术难点

  1. 多数据源事务问题

sharding-jdbc在使用的时候是需要用自己的独立数据源的,那么就难免出现多数据源事务问题。这个我们通过自定义注解,自定义切面开启事务,通过方法栈逐层回滚or提交的方式解决的。出于保密原则,具体代码细节不再展开。

  1. 多表的分页问题

拆表一定会引起分页查询的难度增加。由于各个表查出来的数据量不等,原始的sql语句limit不再适用,需要设计一个新方法便捷的获取分页信息。在此介绍一个分页的思路供大家参考(团队共同的成果,笔者不敢私自占有):

综合考虑业务实际与开发的复杂程度,项目团队决定在出现跨表查询的情况下,每一张表采用一个线程进行查询,以提高查询效率。这个方案的难点在于分页规则的转换。例如,页面传入的offset和pageSize分别为8和20。各分表中符合条件的数量分别为10,10,50。那么我们需要将总的分页条件转化为三个分表各自的分页条件,如图

pagination.png 通过上图可以看到,大分页条件(offset=8,pageSize=20),转换为(offset=8,pageSize=2),(offset=0.pageSize=10),(offset=0,pageSize=8)三个条件。整个计算过程如下:

1)      多线程查询各个分表中满足条件的数据数量

2)      将各个表数量按照分表的先后顺序累加,形成图 8的数轴

3)      判断第一条数据和最后一条数据所在的表

4)      除第一条和最后一条数据所在表外,其他表offset=0,pageSize=总数量

5)      计算第一条数据的offset,pageSize

计算最后一条数据的pageSize,同时将该表查询条件的offset设置为0

数据迁移方案

在数据迁移前,团队讨论过两套迁移方案:1)请DBA迁移数据;2)手写代码迁移数据,他们各有自己的优缺点:

迁移方案 方案详细描述 优点 缺点
dba迁移数据 1.迁移数据期间将流量切换至master库2.采用insert into tableA select * from tableB的脚本迁移数据 1.迁移过程遇到问题可以找dba协助 1.访问流量迁移至master库,有可能出现访问数据库网络等未知问题2.主从同步延迟很大,会影响到整个集群3.迁移速度过慢,因为涉及到扫描全表不走索引的情况
代码迁移数据 1.通过接口调用的方式来迁移数据2.采用select * from table where id > ? limit ?的脚本查询数据,逐条插入数据库中 1.迁移数据速度可控2.主从同步延迟不明显3.可提前将历史数据进行迁移 1.容易出现大事务问题2.容易出现操作失误3.迁移的历史数据出现前后不一致的情况4.迁移持续时间较长,实际操作中整个迁移过程长达两个星期

综合考虑时间成本和对线上数据库的影响,团队决定采用两种方案结合的方式:交易时间为三个月前的冷数据,由于更新几率不大,采用代码的方式迁移,人为控制每次迁移数量,少量多次,蚂蚁搬家;交易时间为三个月内的热数据,由于会在上线前频繁出现更新操作,则在上线前停止写操作,而后由DBA整体迁移。这样将时间成本平摊到平时,上线前只有约2个小时左右迁移数据时系统无法使用。同时,除了最后一次DBA迁移数据外,能够人为控制每次迁移的数据量,整体避免数据库实例级别的高延迟。

整体上线流程

为保证新表拆分功能的稳定性和大表下线的稳定,团队将整个项目分为三个阶段:

第一阶段:建立分表,大表数据迁移分表,线上数据新表老表双写,所有查询走分表

(验证观察)

第二阶段:停止写老数据表,其他业务直连数据库改为资金提供对外接口

(验证观察)

第三阶段:大表下线

总结

  • 应再进一步调研分表相关中间件。由于项目分表依据的特殊性,导致sharding-jdbc的很多功能无法利用,其对于简化查询逻辑的帮助低于预期。并且sharding-jdbc独立数据源的特性,引发了多数据源事务问题,反而增加了开发的工作量。
  • 多线程需要仔细分析线程池核心线程的大小,同时分析多线程池同时存在的时候是否会引起核心线程数过多,避免机器线程打满。
  • 如果是一个已有的项目,在进行分表改造时,一定要将各种场景都罗列清楚,将各个场景细化到程序中的每个类、每个方法中,将所有业务场景都覆盖到。
  • 在迁移历史数据时,一定要做好迁移数据方案,以及应对出现数据不一致时的处理方案。要综合考虑时间成本、数据准确性、对线上功能的影响等诸多因素。
  • 在上线一个比较复杂的方案时,一定要提前设计好回滚方案和降级措施,能够极大保证稳定性。

说点儿题外话

为啥说想说点儿题外话呢,主要是对这次延续了5个多月的项目有感而发。项目进行过程中,难免会与其他系统的维护团队有工作上的交集,有需要其他团队配合的地方。这个时候非常考验程序员的沟通能力,最优秀的程序员能够通过话术把对方拉到自己的阵线当中,让对方感到这项工作对自己也是有好处的。这样能够让对方心甘情愿的配合你的工作,达到双赢的目的。如果程序设计和学习能力是程序员的硬实力,那沟通技巧就是程序员的软实力,硬实力能够保障你的下线,而决定上线的恰恰是软实力。因此很多程序员不注重沟通技巧的培养,其实是相当于瘸腿的,毕竟现在凭单打独斗是不大可能做出事情的。

さらに、少なくとも私たちのユニットでは、バックエンドプログラマーの全体的な品質は実際には最高です。バックエンドプログラマーは、ビジネスとテクノロジーを統合します。比較的強力なビジネス管理能力が必要ですが、優れた技術的品質も備えている必要があります。同時に、ほとんどの作業の主な所有者はバックエンドです。通常、バックエンドプログラマーは、フロントエンド、バックエンド、およびQAの開発リズムを制御し、さまざまな時点を調整し、提供します。リスクフィードバック。これには、バックエンドプログラマーがビジネスだけでなく、テクノロジー、および特定の管理機能も理解する必要があります。これは実際、人々にとってかなり多くの運動です。

おすすめ

転載: juejin.im/post/7078228053700116493