前言
分库分表是一个老生常谈的问题,但网上的大多数文章只是概要式讲述,一些核心步骤一笔带过。 这篇文章给大家讲述所有可行性方案的同时,系统性、完整性阐述其思想内核。
1. 拆分形式
分库分表拆分形式分为垂直分库分表和水平分库分表,这篇文章将聚焦于水平分库分表
。
原因在于组织、业务划分的之初或者大数据量之前,在一些开发人员素质比较高的公司,库、表先天的就垂拆分的比较清晰了,剩下只是数据字段微调。 水平分库分表是侧重于技术的问题,垂直分库分表是侧重于业务划分问题。
注意是侧重,为啥这么说了,以订单表为例,用户下单是订单补充信息过长,把它拆分出来,放在其它数据库里,用订单号关联,这是垂直拆分的一种形式,但显然对业务操作没什么影响。
从上我们可以得出,大数据量下必然要水平分库分表,不一定要垂直分库分表。
2.切分策略
2.1 取模切分(Hash取模)
我们一般会用对某个路由Key的Hash值取模,使数据分散到各个表中更加均衡。
比如原始订单表信息,我们把它分成4张分表:
优点:可以较为均匀的将数据切分,不会有热点问题
缺点:扩容时需要重新计算切分后的值,涉及到大量的数据迁移
2.2 Range切分
优点:单表大小可控,天然水平扩展。
缺点:无法解决集中写入瓶颈,有热点问题。
2.3 查询切分
查询切分的好处,是可以解决Hash取模的水平扩展问题和Range切分的热点问题。
2.3.1基于查询配置结合ShardGroup方式解决水平扩展问题和热点问题
(1)表数据演示
总得来看表结构呈现share group->share db->share table的树形结构
(2) 核心流程
step1:通过id所处的范围找到group_id
step2:通过id取模和groupid找到db_id
step3:通过id所处的范围和db_id找到table_id
优点:理论上
根据数据实际增长情况,调整ID和库、表的Mapping关系,将hash和range结合起来可以解决热点和水平扩展问题。
缺点:引入额外的配置表,降低性能,有漏配、错配的风险。
2.4 一致性Hash切分
- 建立一个2^32 hash节点环,计算database的hash值(常用:hash(节点前缀 +(ip+端口))%2^32),并映射到hash环的具体位置
- 将待切分数据取值hash(sharding key) % 2^32,映射到hash 环上,并按照规则从root节点开始顺时针查找
if hash(node_0) %2^32 < hash(sharding key) %2^32 <= hash(node_1) %2^32 ;该数据归属 node_1(database1)
if hash(node_1) %2^32 < hash(sharding key) %2^32 <= hash(node_2) %2^32 ;该数据归属 node_2(database2)
if hash(node_2) %2^32 < hash(sharding key) %2^32 <= hash(node_3) %2^32 ;该数据归属 node_3(database3)
...
- 扩容时迁移数据时,如新增节点node_4(database4) 在node_1和 node_2之间,则需要将data_key落在node_1到 node_4的数据从原node_2(database2)迁移到node_4(database4)
优点:扩容时只需要迁移小部分的数据
缺点:如果节点不够分散,会出现Hash环的倾斜,导致节点数据不均匀
增加虚拟Hash节点解决数据倾斜问题
基于一致性Hash 增加了虚拟节点,这些节点会映射到真实的物理节点,这样就可以调控节点数据分配
优点:减少了Hash环倾斜带来的影响
缺点:增加复杂度
3.数据迁移
数据库拆分一般是业务发展到一定规模后的优化和重构,为了支持业务快速上线,很难一开始就分库分表,这时候就需要进行数据迁移了。
3.1同步脚本停机迁移(不建议)
写个脚本老库的数据写到新库中。比如,在系统使用的人数非常少的时候,凌晨1点,挂一个公告说系统要维护升级预计N小时。个脚本将老库的数据都同步到新库中。
优点:操作简单
缺点:一但数据量大,加上数据核对时间,迁移时间会超出公告时间。
3.2 双写数据迁移
3.2.1 在业务代码中直接双写
Step1.数据库双写(事务成功以老模型为准),查询走老模型。
旧数据如何迁移到新库?
通过job导历史数据写入到新库,同时建立映射关系。
如何保证新库里的数据是最新且完整的?
我们对老库的更新操作(增删改),同时也要写入新库(双写)。如果操作的数据不存在于新库的话,需要插入到新库中。同时每日job数据对账,并将差异数据补平。这样就能保证,咱们新库里的数据是最新且完整的。
不在同一库如何保证事务?
只保证旧库写入事务,差异数据由数据对账服务补偿。
Step2.数据库双写,但是事务成功与否以新模型为准,在线查询切新模型。
在历史数据导入完毕并且数据对账无误后,执行该步骤。
如何查询旧订单?
通过order mapping数据找到新库id,在新库中查询。
Step3.老模型不再同步写入,在线查询切新模型。
在查询无异常后,执行该步骤。
在业务代码中直接双写方案
优点:平滑迁移
缺点:双写影响响应时间,减少吞吐量
3.2.1 通过数据同步工具双写
数据同步数据同步整体方案见下图
上述方案中不难发现,业务写一条数据到旧实例的一张表,于是产生了一条binlog;data-sync中间件接到binlog后,将该记录写入到新实例,于是在新实例也产生了一条binlog;此时data-sync中间件又接到了该binlog......不断循环
如何解决双向同步时的binlog循环消费问题?
采用数据染色方案解决,只要能够标识写入到数据库中的数据使data-sync中间件写入而非业务写入,当下次接收到该binlog数据的时候就不需要进行再次消息流转。所以data-sync中间件要求,每个数据库实例创建一个事务表,该事务表tb_transaction只有id、tablename、status、create_time、update_time几个字段,status默认为0。操作如下:
# 开启事务,用事务保证一下sql的原子性和一致性
start transaction;
set autocommit = 0;
# 更新事务表status=1,标识后面的业务数据开始染色
update tb_transaction set status = 1 where tablename = ${tableName};
# 以下是业务产生
binloginsert xxx;update xxx;update xxx;
# 更新事务表status=0,标识后面的业务数据失去染色,后面业务正常继续消费
update tb_transaction set status = 0 where tablename = ${tableName};
commit;
数据同步工具DTS
4.平滑扩容
4.1 全局增量分区局部散列扩容方案
对于Range和查询切分切分策略,增加数据库实例和映射配置即可
4.2 基于主从同步的扩容方案
核心思想是,启动更多的从服务器(取决于希望扩容的服务器量),当从服务器从主服务器同步完成之后,将所有从服务器升级为主服务器,然后调整路由规则,再根据路由规则删除每个主服务器中的冗余数据
。
4.3 基于数据迁移的扩容方案
上文数据迁移中,通过数据同步工具双写就是此方法。数据迁移的过程中就涉及到了平滑扩容,我在这里把它拿出来,是为了大家更好理解它。
4.查询问题
4.1 数据多维度查询
水平切分后,查询的条件一定要在切分的维度内,比如查询具体某个用户下的各位订单等
;禁止不带切分的维度的查询,即使中间件可以支持这种查询,可以在内存中组装,但是这种需求往往不应该在在线库查询,或者可以通过其他方法转换到切分的维度来实现。
但是我非要多维度查询怎么办?
4.1.1 多维度查询双写
比如,对于订单表我可以同时以user_id、order_id为sharding key冗余分库分表数据。
优点:查询实时性
缺点:写入性能有影响,数据冗余
4.1.2 增加全局二级索引库
每个全局二级索引库对应一张分布式索引表,和其他分布式表一样,按照指定的分区规则水平拆分为多张物理表。
优点类似ES 倒排索引,首先通过value 找到id,在通过id找到完整数据。
优点:查询实时性,相较于数据冗余
缺点:写入性能有影响,数据冗余
4.1.3 通过binlog数据异构查询
比如,可以把mysql通过binlog写入到OLAP数据库,比如Elasticsearch、 Clickhouse查询。
优点:读写分离,方便支持多维度或者全字段索引
缺点:查询会有实时性问题,可以用于非实时场景
4.2 order排序查询
基于上述分片聚合方式我们清晰的了解到如何可以进行跨分片下降数据获取到内存中,但是通过图中结果可以清晰的了解到返回的数据并不像我们预期的那样有序,那是因为各个节点下的所有数据都是仅遵循各自节点的数据库排序而不受其他节点分片影响。 那么如果我们对数据进行分片聚合+排序那么又会是什么样的场景呢
4.2.1 内存排序
todo
4.2.2 流式排序
todo
5.灰度发布
后续在补充。。。
6.总结
分库分表要考虑的细节还是很多的,所有才有了NewSql数据库,但是上面这些解决方案,还是需要掌握的。
参考
https://blog.csdn.net/mouzeping123/article/details/120061298
https://tech.meituan.com/2016/11/18/dianping-order-db-sharding.html
https://cloud.tencent.com/developer/article/2124399
https://mp.weixin.qq.com/s?https://juejin.cn/post/7085132195190276109
https://bbs.csdn.net/topics/605500717
https://blog.51cto.com/u_11475121/2954464
https://zhuanlan.zhihu.com/p/635219596