分布式存储系统的核心逻辑,就是将用户需要存储的数据根据某种规则存储到不同的机器 上,当用户想要获取指定数据时,再按照规则到存储数据的机器里获取。
为什么要分表和分区?
日常开发中我们经常会遇到大表的情况,所谓的大表是指存储了百万级乃至千万级条记录的表。这样的表过于庞大,导致数据库在查询和插入的时候耗时太长,性能低下,如果涉及联合查询的情况,性能会更加糟糕。分表和表分区的目的就是减少数据库的负担,提高数据库的效率,通常点来讲就是提高表的增删改查效率。
什么是分表?
分表是将一个大表按照一定的规则分解成多张具有独立存储空间的实体表,我们可以称为子表,每个表都对应三个文件,MYD数据文件,.MYI索引文件,.frm表结构文件。这些子表可以分布在同一块磁盘上,也可以在不同的机器上。app读写的时候根据事先定义好的规则得到对应的子表名,然后去操作它。
1、垂直分表
? 将一个表按照字段分成多表,每个表存储其中一部分字段。
用户在浏览商品列表时,只有对商品感兴趣才会去浏览商品的详情。因此商品详情的字段访问次数较低。而由于此类型字段通常比较大,占用磁盘IO和网络IO资源过大,而其他小体积的字段访问频次较高如:商品名,价格,图片等信息,所以可以考虑将一个表垂直的切分开。
1、示例
在获取商品列表的时候直接
SELECT * FROM <商品信息> limit n,m
而点击进入商品详情时
SELECT * FROM <商品详情> WHERE id = xxx
2、带来的提升
- 避免IO争夺并减少锁的几率,查看商品信息和商品描述互不影响。
- 冷热数据分离,商品信息的高效率不会被商品描述低效率影响
为什么大字段IO效率低:
第一是由于数据量本身大,需要更长的读取时间;
第二是跨页,页是数据库存储单位,很多查找及定位操作都是以页为单位,单页内的数据行越多数据库整体性能越好,而大字段占用空间大,单页内存储行数少,因此IO效率较低。
第三,数据库以行为单位将数据加载到内存中,这样表中字段长度较短且访问频率较高,内存能加载更多的数据,命中率更高,减少了磁盘IO,从而提升了数据库性能。
拆分原则
- 表中不常用的字段拆分出来,冷热数据分离
- 将
text
,blob
这种大字段给拆分出来 - 经常组合查询的列放在一起
- 参照业务设计
2、水平分库
? 多个表在同一台数据库上,单机的磁盘IO,内存和运行效率是有限的。当大并发打过来的时候一台数据库很可能会承受不住,而且日益增长的数据量也会让单台数据库很快的吃紧,所以可以采用拆分库的方式来提高数据库的性能。垂直分库是指按照业务将表进行分类,分布到不同的数据库上面,每个库可以放在不同的服务器上,它的核心理念是专库专用。
? 如电商项目中的,卖家库,商品库,积分库。项目启动的时候只是一个小项目,所有的表都是在一台数据库上的,由于项目越做越好,单台数据库的性能和容量很快就达到了瓶颈,这个时候需要将这三个表分别的放到不同的数据库上,由于三个表分别放到不同的物理机上面,所以让IO和内存有了质的飞跃。
? 提升
-
解决业务层的耦合,业务结构清晰
-
能对不同业务的数据库进行分级管理,维护和监控,粒度更细
-
可以承受更高的并发压力
缺点
-
系统构架设计变得复杂
-
Mysql
两个库之间无法直接关联查询,可以通过federated
引擎实现。(不支持事务,不支持DDL) -
分布式的数据库事务开销大,需要通过XA事务或者补偿机制来实现
3、水平分表
以字段为依据,按照一定策略(
hash
、range
等),将一个表中的数据拆分到多个表中。
每个表的结构都一致
表中数据都不一样
所有表的并集是完整数据
库内分表只解决了单一表数据量过大的问题,但没有将表分布到不同机器的库上,因此对于减轻
MySQL
数据库的压力来说,帮助不是很大,大家还是竞争同一个物理机的CPU、内存、网络IO。所以一般的方案是水平切分既分表也分库。
分表键又称为维度,在水平拆分过程通过一定的拆分规则将数据路由到不同库的不同表中,数据表拆分的核心在于要找到数据表中的数据在业务逻辑上的主体,确定大部分的数据库操作都是围绕这个主体进行的,可以用该主体对应的字段作为分表键。业务逻辑上的主体,通常与业务的应用场景相关,下面的一些典型应用场景都有明确的业务逻辑主体,可用于分表键:
- 面向用户的互联网应用,都是围绕用户维度来做各种操作,那么业务逻辑主体就是用户,可使用用户对应的字段作为分表键;
- 侧重于卖家的电商应用,都是围绕卖家维度来进行各种操作,那么业务逻辑主体就是卖家,可使用卖家对应的字段作为分表键;
总结一下,作为分库分表键需要考虑以下几种因素:
- 数据均衡性,保证水平表中的数据量不会差距过大
- 热点数据均衡,一个表中的数据都是热点数据对性能影响很大
- 尽可能找到业务逻辑主体键,对这个键进行分表
- 如果是日志检索类场景,可以根据递增的数字或者时间来进行分
- 数据稳定,当节点出现故障需要移除或者扩容时,不应该出现大范围的数据迁移
- 节点异构性,每个节点的性能可能不一致,需要考虑性能有差异的数据库分配的权重
1、多维度查询如何优化
? 一般情况下只对主分表数据进行查询,如果有的场景下需要对表中其他维度的数据进行查询该效率就会比较低,这是因为查询没有在主数据维度上,那么会导致并发请求N个分库,查询M个分表,最终将\(M*N\)个数据进行归并计算出结果,如果数据量较大这会引起应用服务的OOM(部分服务端代理的分表分库中间件不会导致如MyCat
,阿里的DRDS
)下面是几种解决方案:
1、多维度数据冗余
? 将主维度上的数据通过BinLog
的方式重新同步到辅助维度的库表上一份,在查询辅助维度的时候会落到辅助维度的库表集群上。
适用场景:对于辅助维度和主维度的查询性能要求高,且并发都大时。
2、降维操作
? 如这样的场景,在电商业务中,有一个Order
表,Order
表的主维度是UserID
,副维度是OrderNo
。在业务中经常会使用OrderNo
进行数据查询,可以将UserID
的数值包含到OrderNo
中,比如UserID
为1002111
那么OrderNo
就可以为1002111XXXX
。这样就可以将两个维度进行了融合称之为降维。
? 在路由的时候,如果SQL中含有UserID
那么直接对UserID
进行分片路由,如果是OrderNo
则取OderNo
中UserID
的部分进行路由,这样会精确到路由到某个分库的某个分表的位置,避免了多库并发查询。
适用场景:辅助维度和主维度其实可以通过将主维度和辅助维度的值进行信息共享
3、建立索引表
? 可以建立一个字典表,字典表维护主维度和辅助维度的映射关系。就跟Mysql
查询索引的回表机制类似,先通过索引去查询叶子节点的位置后通过索引中的聚簇索引再去查询聚簇索引中数据的具体内容,
? 例如:SELECT * FROM A WHERE 辅助维度 = 1
这样的查询可以分解成两步来做:
-
SELECT 主维度 FROM 索引表 辅助维度 = 1
先查询索引表,找到辅助维度对应的主维度值 -
SELECT * FROM A WHERE 主维度 = xxx
通过1中查询到的主维度值进行精确路由查询
试用场景:主副维度是一一对应的。
2、如何选择分片数
1、分库数目决策
计算公式:
? 总库数:\((三到五年的存储容量)/单个库建议存储容量(300G)\)
? 一般情况下,会把若干个分库放到一台实例上去。未来一旦容量不够,要发生迁移,通常是对数据库进行迁移。所以库的数目才是最终决定容量大小。最差情况,所有的分库都共享数据库机器。最优情况,每个分库都独占一台数据库机器。一般建议一个数据库机器上存放8个数据库分库。
2、分表数目决策
计算公式:
? 总表数:\((三到五年的数据量)/单表建议数据量(1000万条)\)
? 分库总表数:\(总表数/总库数\)
? 表的数量不宜过多,涉及到聚合查询或者分表键在多个表上的SQL
语句,就会并发到更多的表上进行查询.表的数目不宜过少,少的坏处在于一旦容量不够就又要扩容了,而分库分表的库想要扩容是比较麻烦的。一般建议一次分够。建议表的数目是2的幂次个数,方便未来可能的迁移。
3、分片方式
1、哈希取模
? 对数据进行哈希运算后根据节点数进行取模运算,最后可以得到一个序号,这个序号就是此数据需要操作的节点号,哈希算法的一个优点是,只要哈希函数设置得当,可以很好地保证数据均匀性, 但有一个较为严重的缺点,就是稳定性较差。
如图所示,Node1 将存储数据 D2(300%3=0)和 D5(600%3=0),Node2 将存储数 据 D0(100%3=1)、D3(400%3=1)和 D6(700%3=1),Node3 将存储数据 D1(200%3=2)和 D4(500%3=2)。
? 当数据库的容量快要达到上限时,需要添加数据库时就会出现一个问题:原先的规则已经不再实用,所有节点需要重新计算然后大规模的迁移数据。所以,哈希方法适用于同类型节点且节点数量比较固定的场景。
一致性哈希
一致性哈希是指将存储节点和数据都映射到一个首尾相连的哈希环上,存储节点可以根据 IP 地址进行哈希,数据通常通过顺时针方向寻找的方式,来确定自己所属的存储节点,即 从数据映射在环上的位置开始,顺时针方向找到的第一个存储节点。
实现
? 将key分为2(31)份,将0~(231-1)个结点头尾相连,整个圆环按顺时针方向组织,将服务节点按照一定的哈希算法得到在圆环上的位置.
定位到节点步骤
- 对数据Key进行哈希
- 根据Hash值沿着环找到大于等于这个哈希值且距离最近的一个节点
这样的算法优点就在于,如果某个节点宕机那么只需要迁移部分的数据量。这个设计有一个缺陷,没有考虑到节点数据分布均匀的问题还有节点性能差异的问题,所以引入了带虚拟节点的一致性哈希算法
算法在一致性哈希上改进了,对于每个节点继续按照性能和权重对其分为若干个虚拟节点,然后对虚拟节点进行哈希让其均匀的分布在哈希环上面,这样就解决了数据均匀,稳定和异构三大问腿。
2、按照数据范围分片
? 按照时间区间或ID区间来切分。例如:按日期将不同月甚至是日的数据分散到不同的库中;将
userId
为1~9999的记录分到第一个库,10000 - 20000的分到第二个库,以此类推。某种意义上,某些系统中使用的"冷热数据分离",将一些使用较少的历史数据迁移到其他库中,业务功能上只提供热点数据的查询,也是类似的实践。
优点:
- 单表大小可控
- 便于扩展节点
- 方便范围查询
缺点:
- 热点数据是瓶颈,新数据的访问要比老数据频繁。
优点
- 不存在单库数据量过大、高并发的性能瓶颈,提升系统稳定性和负载能力
- 业务端修改较少
- 可以针对日益增长的业务表进行水平的扩容
缺点
- 事务问题
- 关联查询