分库分表:分库分表是解决数据过大问题的良方,目的是为了缓解数据库的压力,最大限度减轻数据库压力,提高数据处理的效率。建议建数据库就分表,不然后期损耗大有连锁问题。分多少个库需要用并发量来预估,分多少表需要用数据量来预估。分表是因为数据量比较大,导致事务执行缓慢;分库是因为单库的性能无法满足要求。
-
水平分表:本质是将一张大表拆成多个结构相同的子表。
- 采用关键字哈希进行分表,让相同关键字的数据落在同一张表里。如user_id%10。但分表数据不均匀
- 直接根据数据范围进行分表,比如每200w记录,就自动生成一张新表。适用于数据和关键字无关的场景,数据分布均匀但有比较多的场景限制
- 根据时间来进行分表,比如按天、按月、按年。如账单流水表就可以按月分表。思路简单,且很容易清理掉旧数据,整个表能自动变冷。但后期随着业务量突飞猛进,一个月都有千万条甚至上亿条数据,此时又得进行拆分
- 结合数据范围和关键字哈希
-
水平分库: 可以把一个表的数据(按数据行)分到多个不同的库,每个库只有这个表的部分数据,这些库可以分布在不同服务器
-
垂直分表:将一张表的数据,根据场景切分成多张表
-
垂直分库:可以把多个表按业务耦合松紧归类,分别存放在不同的库
-
分表实现方式:
- 公共包实现:本地依赖包,即将分表逻辑写在公共的代码库里,每个需要调用服务的客户方都集成该公共包,就接入了自动分表的能力。不引入组件但不符合开闭原则,服务小可用。
- 中间件实现:最优解,耦合性低、架构清晰。但增加了运维成本。如mycat
-
拆分老项目:分表过程中,是否可以停服?如果不停服,怎么保证数据一致性?
- 双写读老阶段:通过中间件,对write sql同时进行两次转发,也就是双写,保持新数据一致,同时开始历史数据拷贝。本阶段建议施行一周;
- 双写双读阶段:采用灰度策略,一部分流量读老表,一部分流量读新表,读新表的部分在一开始,还可以同时多读一次老表数据,本阶段建议施行至少两周;
- 双写读新阶段:此时基本已经稳定,可以只读新表,为了安全保证,建议还是多双写一段时间,本阶段建议周期一个月;
- 写新读新阶段:已经完成了分表的迁移,老表数据可以做个冷备。
分库分表后引入的问题:
-
多库唯一主键:UUID 是无序且很长且不具有业务能力,基于 Snowflake 算法但存在时钟回拨。基于数据库维护自增ID区间,引入Redis使用原子操作生成 ID,利用 Zookeeper 的 znode 数据版本来生成序列号但要分布式锁。
-
ID严格自增容易被猜测不安全,基于MAC的uuid也不安全
-
时钟回拨解决方案:回拨时间短5ms可以等待,否则
- 匀出少量位作为回拨位,一旦时间回拨,将回拨位加 1,可得到不一样的 ID。
- 启动前检查时间戳合法性
- 在出现任何刻度的时钟回拨时都会直接抛异常给到业务层
- 预生成ID保存+获取时间脱离服务器依赖
- 回拨时多条时间线切换
-
-
分布式事务问题:同一个操作会分散到多个数据库中,涉及跨库执行 SQL 语句,也就出现了分布式事务问题。
-
跨库关联查询问题:针对这种需要跨库访问的业务场景,一般会使用额外的存储,比如维护一份文件索引。另一个方案是通过合理的数据库字段冗余,避免出现跨库查询。
-
跨库跨表的合并和排序问题:数据分散存储到不同的数据库和表中,如果查询指定数据列表,或者需要对数据列表进行排序时,就变得异常复杂,可以依赖开源的分库分表中间件来处理。如Apache ShardingSphere,淘宝的 TDDL