技术背景
虽然数据库走索引查询非常快,但在实际的复杂业务场景下,对于超大数据量的单表(超过100G),可能无论如何优化索引,都会有查询不理想的情况存在。比如查询场景非常多,但是不可能针对每一种查询条件都建立索引
PS:虽然可以把数据同步到es里去支持复杂查询,但这只是一种实现方案,分库分表也是一种实现方案,并且可以不引入es,不会提升系统复杂性,实时性也更好。这里插一句,技术方案常常各有优劣,了解技术方案各自的优劣势即可,不必一心追求完美方案,走入死胡同
在制定数据存储方案时,需要考虑到五年内的业务发展,即五年内不需要对存储方案进行扩容,所以需要预估数据量,看是否需要分库分表,需要怎么分
拆分方式
第一步是垂直拆分,按照业务范围,针对原来多个业务共用一个数据库的情况,改为每个业务使用单独的数据库
第二步是水平拆分,如果单表数据量很大,可以分表。如果数据量进一步变大,单库已经承载不了,那么还需要分库
PS:其实对于逻辑上一张表的数据来说,几百G已经很多了,单库应该不会达到存储量的上限(建议数据库单机数据容量控制在4TB以内)。但是大的数据量常伴随着大的访问量,单库可能支撑不了太高的访问量(几百到几千,跟具体机器性能和sql语句有关),所以需要分库
分片策略
水平拆分的话,需要根据业务字段设计分片策略
1.连续分片
按id自增范围或时间范围,每个阶段分一张表
优点是单表大小可控,天然水平扩展,不需要做数据迁移。缺点是无法解决集中写入性能瓶颈的问题
2.取模分片
比如要分成4个库,每个库8张表,那么就按id%4分库,按id/4%8分表
PS:在分表数量上,经常会选择128、1024等2的n次幂张表,而不是选择100、1000张表,这应该是一种约定俗成的结果,而且它也蕴含了扩容时要翻倍扩容的思想,因为翻倍扩容可以只对一半的数据做迁移
3.一致性hash分片
在某个节点宕机时,请求可以无缝路由到下一个节点上去。新增节点时,请求可以无缝打到新节点上,如果需要迁移历史数据,也只需要迁移一个节点的数据到另一个节点即可
优点是增减节点时不需要修改hash算法(所谓”一致性“),需要迁移的数据量少,可以一个节点一个节点的扩容。缺点是增减节点对相邻的下一个节点影响很大,容易造成负载不均衡的问题
数据迁移方案
首次数据拆分或者后续翻倍扩容的时候,都涉及到数据的迁移工作
停机迁移无疑是最简单也最可靠的方案,可以先预估迁移耗时,发布停服公告,使用提前测试过的迁移脚本完成数据迁移,然后切换提前测试好的分片规则,启动服务。但是很多时候业务上是要求不能停机的,需要使用不停机的数据迁移方案
1.双写
细节就不用多说了,总之就是写完老库再写一下新库,这种方案会面临两个数据一致性的问题
第一个问题是老库写成功了新库可能没写成功
第二个问题,两个线程可能会是这样的写入完成顺序,线程A写老库、线程B写老库、线程B写新库、线程A写新库,这样新库老库的数据就可能不一致了
2.挂从库
可以把新库挂成老库的从库,然后停机把从库升级为主库,修改分片策略,重启服务
停机只是为了把从库升级为主库,不涉及到数据迁移,停机时间较短
3.数据同步
使用canal等工具可以消费binlog日志,手动解析binlog数据,把老库的写入操作同步到新库,此方案的优点是可以不用停机
- 编写canal同步代码、历史数据迁移代码、数据完整性校验代码
- 开启canal同步
- 运行历史数据迁移代码,把某个时间戳之前的数据从老库迁移到新库。此时间戳要在开启canal同步之后,防止漏掉部分数据;迁移过程中如果出现冲突,说明新库里已经有更新的数据了,这条数据可以直接忽略。迁移过程中需要记录错误日志,对迁移失败的数据再次进行恢复
- 进行数据完整性校验,看新老数据库数据是否一致
- 将读流量逐步切到新库,在切的过程中做好观察,出现问题及时回滚
- 将写流量逐步切到新库,在切的过程中做好观察,出现问题及时回滚
- 关闭canal同步,删除新老数据库中多余的数据
PS:以上方案对比,如果可以接受短时间停机,推荐用挂从库的方式,比较简单,不引入新的依赖;如果严格要求不停机,推荐用数据同步的方式。不推荐双写方案的原因是,虽然三种方案都可能会有数据不一致的问题,但挂从库和数据同步的方案,利用binlog日志的顺序性,解决了双写中的多线程写入乱序问题。对于老数据库写入成功,新数据库写入失败的问题,三种方案都可以通过记录失败日志、数据完整性校验来解决
其他
此外,分库分表还会涉及到分库分表中间件的选择、分布式id生成方案、跨库join操作、分布式事务等知识点,这些会在后面分别写文章详细讲解