互联网发展至今,各个公司企业的数据量都大幅增长,分库分表越来越多的被我们用到,那么我们应该如何针对我们自己的业务场景,对数据进行合理的划分,用最小的代价解决掉性能瓶颈。

1 分库分表的介绍

1.1 什么是分库分表?

分库分表其实是两个词

分库: 将一个库里的数据,分开放在多个库里

分表: 将一张表里的数据,分开放在多张表里

1.2 为什么要分库分表?

至于为什么要分库分表?肯定是单个库或者是单个表不足以满足我们的业务需要,不论是从性能角度出发,还是从数据库存储能力的角度出发,每一种类型的数据库当数据量到达一定程度后,都会出现瓶颈。这时候我们就要通过分库分表,提升数据库的存储能力以及性能。

一般而言,我们数据库出现性能问题,我们都会优先去做数据库的主从复制,读写分离。

主从复制带来的问题:

  • 写入无法扩展
  • 复制延时
  • 锁表率上升
  • 表变大,缓存率下降

分库分表同时也是解决主从复制带来的一些问题。

总结一句话,数据库顶不住了!!!

1.3 怎样分库分表?

对于Java项目来讲,分库分表一般分为两个阶段

  • 数据库重新设计,合理切分数据

  • 引入分库分表的工具,帮助我们做数据库操作

    • Sharding-sphere(当当):jar,前身是Sharding-jdbc;
    • Cobar(阿里巴巴)
    • Mycat(基于Cobar):中间件

2 分库分表的设计

2.1 分库分表的方案

  • 只分库

    分库能够解决的问题是,数据库读写QPS过高,数据库连接数不足

  • 只分表

    分表能够解决的问题是,单表数据量过大,查询、存储性能遇到瓶颈

  • 即分库又分表

    解决上述两种问题

我们应该更据自己系统遇到的问题,灵活的选择分库分表的方案,如果没有性能问题,切记过度设计。

2.2 分库方案

分库主要能够解决读写QPS过高,以及数据库连接不足的问题,我们就从这一点下手。

先分析我们平时业务的QPS峰值,再考虑当大促,或者活动,这种流量暴增情况的峰值,这样的话分库就变成了一道简单的除法题

公式:流量峰值/单个库的承载力=分库的数量。

微服务场景会按照服务拆分,进行数据库的拆分。

2.3 分表方案

分表相对于分库来说更为麻烦,也更为常用。因为一些大数据量的表常常出现,致使数据库的性能瓶颈频繁出现。

分表一般都会进行数据量的增长做出预估。然后根据系统的使用年限,单表数据量做出预估分多少张表。

预估数据时,我们要适当扩大留有足够的空间。

公式:日增长数365系统使用年限/表最大容量=分表的数量

3 如何更科学的切分数据

一般我们在切分数据的时候都是考虑垂直切分或者是水平切分,对于分库和分表来说,水平切分和垂直切分都是一样的设计方案,垂直切分会改变数据的结构,水平切分不会破坏数据的结构,所有切分想要取全量数据都得取切分库、表的并集。

3.1 垂直切分

垂直切分就是将一张表的不同字段切分到不同的表中去。

这种设计方式就是将高频的部分数据拆分出来,最常见的就是商品信息与商品详情,我们经常只关心与自己感兴趣的详情页,不感兴趣的就没必要进行查询。

设计原则:

  • 不常用的单独放在一张表中
  • 经常一起查询的数据需要放在一张表,减少关联查询(关联变复杂就得不偿失了)
  • 大字段(text,blob等)拆分放在附表中

带来的性能提升:

  • 避免IO争抢,减少锁表的概率
  • 高频数据不会受到其他数据拖累(尤其是大字段IO效率极低

3.2 水平切分

水平切分是将一张表中的数据按照一定规则放入不同的表中,目的也是为了解决单表数据量过大的问题。

拆分后数据路由问题变成了我们必须要解决的问题。

设计原则:

  • 字段为依据,按照一定策略(Hash、Range等)拆分。

带来的性能提升:

  • 直接解决单一表数据量过大而产生的性能问题。

4 水平切分依据详解

好的切分方式一定是较小的数据偏移,能够平滑的扩容。

4.1 Hash方式

Hash 是分库分表是最大众最普遍的方案。

4.1.1 误区

误区一:数据偏斜问题

数据偏斜问题就是指我们插入的数据不能均匀的散落在各个库表。出现的更本原因就是库数量和表数量非互质关系

用 Hash 值分别对分库数和分表数取余,得到库序号和表序号。稍加思索一下,我们就会发现,以 10 库 100 表为例,如果一个 Hash 值对 100 取余为 0,那么它对 10 取余也必然为 0。

// 1 算Hash
int hash = id.hashCode();
// 2 总分片数
int sumSlot = DB_CNT * TBL_CNT;
// 3 分片序号
int slot = Math.abs(hash % sumSlot);
// 4 计算库序号和表序号的错误案例
int dbIdx = slot % DB_CNT ;
int tblIdx = slot / DB_CNT ;
复制代码

这就会造成只有 0 库里面的 0 表才可能有数据,而其他库中的 0 表永远为空。 会导致及其严重的数据偏斜问题。

基于Hash的方式我们也不能只考虑库数量和表数量非互质,还需要考虑到扩展性

误区二:扩容难以持续

我们把 10 库 100 表看成总共 1000 个表,将求得的 Hash 值对 1000 取余,得到一个介于[0,999)中的数,然后再将这个数二次均分到每个库和每个表中,这样看似能够解决上述问题,但是这样依赖了总表数据,后续扩容会非常复杂,不仅要改算法,还要做数据迁移。

4.1.2 正解

方案一:标准的二次分片法

错误案例二,大体思路已经正确,但是过于依赖表的总数量,我们就可以根据分配序号重新计算库序号和表序号的逻辑进行调整,就可以实现标准的二次分片法。

// 1 算Hash
int hash = id.hashCode();
// 2 总分片数
int sumSlot = DB_CNT * TBL_CNT;
// 3 分片序号
int slot = Math.abs(hash % sumSlot);
// 4 二次分片法
int dbIdx = slot / TBL_CNT ;
int tblIdx = slot % TBL_CNT ;
复制代码

我们可以通过翻倍(2的倍数即可)扩容,扩容都,我们的表序号一定维持不变,库序号可能在原来库,也可能平移到了新库中(原库序号加上原分库数),完全符合我们需要的扩容持久性方案。

方案弊端:

  • 这种方式,扩容在前期容易,当分库数量过多时,就耗费资源
  • 连续的分片键 Hash 值大概率会散落在相同的库中,某些业务可能容易存在热点库(例如新生成的数据的 Hash 相邻且递增,可能会造成一段时间内生成的新数据会集中在相邻的几个库中)。

方案二:基因法

案例一不合理的主要原因,就是因为库序号和表序号的计算逻辑中,有公约数这个因子在影响库表的独立性。

这也是一种常用的方案,我们称为基因法,即使用原分片键中的某些基因(例如前四位)作为库的计算因子,而使用另外一些基因作为表的计算因子。

// 通过分片键后四位
int dbIdx = Math.abs(id.substring(0, 4).hashCode() % DB_CNT );
int tblIdx = Math.abs(id.hashCode() % TBL_CNT);
复制代码

此种方案使用时,要综合分片键的样本规则,选取的分片键前缀位数,库数量,表数量,四个变量对最终的偏斜率都有影响。

方案弊端:

  • 该方案数据偏斜可能会比较严重,需要做好充分的预估。

方案三:剔除公因数法

基于错误案例一启发,很多场景下我们还是希望相邻的 Hash 能分到不同的库中(计算库序号用 Hash 值对库数量取余)。

为了实现这一需求我们可以想办法去除公因数影响。

int dbIdx = Math.abs(id.hashCode() % DB_CNT);
// 计算表序号时先剔除掉公约数的影响
int tblIdx = Math.abs((id.hashCode() / TBL_CNT) % TBL_CNT);
复制代码

该方案的特点就是需要维持库序号不变。

方案四:关系表冗余法

我们可以通过一张 “路由关系表” 将分片键对应库关系建立起来。

此方案仍需要通过 Hash 算法计算表序号,但是在计算库序号时,从路由表中读取数据。因为每次数据查询时,都需要读取路由表,所以我们需要将分片键和库序号的对应关系记录同时维护在缓存中以提升性能。

int tblIdx = Math.abs(id.hashCode() % TBL_CNT);
// 从缓存获取
Integer dbIdx = loadFromCache(id);
if (null == dbIdx) {// 从路由表获取dbIdx = loadFromRouteTable(id);if (null != dbIdx) {// 保存到缓存saveRouteCache(id, dbIdx);}
}
if (null == dbIdx) {// 此处可以自行设计逻辑dbIdx = selectRandomDbIdx();saveToRouteTable(id, dbIdx);saveRouteCache(id, dbIdx);
}
复制代码

selectRandomDbIdx();方法作用是生成该分片键对应的存储库序号,这里我们可以灵活进行设置,可以自己设置权重,通过权重可以调节数据倾斜问题,这样我们可以在扩容时灵活调整,无需进行任何数据迁移。

该方案虽然看起来很美好,解决了很多问题,但是也会带来新的弊端:

  • 每次读取数据都需要访问路由表,虽然增加了缓存,但是还是有一定的性能损耗。

  • 如果要使用文件 MD5 摘要值作为分片键,由于样本集过大,无法为每个 md5 值都去指定关系(当然我们也可以使用 md5 前 N 位来存储关系)。

  • 饥饿占位问题

    这个在实际的业务场景会出现,一些不活跃的用户可能会浪费掉大量空间

    • 通过在代码上增加一些是否活跃的验证,验证过的才分配空间
    • 前期将多个库放在一个实例上,后期根据业务增长进行迁移

方案五:一致性Hash法

一致性 Hash 算法是一种比较流行的集群数据分区算法,比如 RedisCluster 即是通过一致性 Hash 算法,使用 16384 个虚拟槽节点进行每个分片数据的管理。

正规的一致性 Hash 算法会引入虚拟节点,每个虚拟节点会指向一个真实的物理节点。这样设计方案主要是能够在加入新节点后的时候,可以有方案保证每个节点迁移的数据量级和迁移后每个节点的压力保持几乎均等。

但是对于数据库来说,出现数据库下线的情况很少出现,新增节点也不会从0开始从其他节点迁移数据,所以说没有必要引入虚拟节点来增加复杂度。

为什么没有必要使用过多的虚节点?

  • 花费额外的耗时和内存来加载虚拟节点的配置信息。
  • MySQL具有完备的主从同步方案
  • 虚拟节点主要解决的问题是节点数据搬迁过程中各个节点的负载不均衡问题,通过虚拟节点打散到各个节点中均摊压力进行处理。

方案三:

4.2 Range方式

Range方式就是根据数据范围划分数据的存放位置。

最经典的按照年月进行分库分表,该方案比较朴实无华。

弊端:

  • 数据热点问题无法解决,最新的数据肯定是最活跃的
  • 交叉范围数据处理不方便,尤其是在跨年月的数据难以处理。
  • 新库新表追加问题,追加不及时可能会出现线上故障

5 分库分表带来的问题

5.1 跨库关联查询

未拆分表之前,我们可以使用 join 关联多张表查询数据,但是经过分库分表后两张表可能都不在一个数据库中,无法使用 join

解决方案:

  • 字段冗余:把需要关联的字段放入主表中,避免 join 操作;
  • 数据抽象:通过 ETL 等将数据汇合聚集,生成新的表;
  • 全局表:比如一些基础表可以在每个数据库中都放一份;
  • 应用层组装:将基础数据查出来,通过应用程序组装起来;

5.2 排序、分页、函数计算问题

在使用 SQL 时 order by、limit 等关键字需要特殊处理,

解决方案:

先在每个分片上执行相应的函数,然后再将各个分片的结果汇总,再次计算。

5.3 分布式 ID

我们在使用 Mysql 数据库时,单库单表可以使用自增 id 作为主键,分库分表了之后就行不通了,会出现 id 重复。

分布式 ID 解决方案:

  • UUID
  • 每个库占用一个id号段
  • 基于数据库自增单独维护一张 ID表
  • Redis 缓存,通过读取缓存中的值,进行递增
  • 雪花算法(Snowflake)
  • 美团 Leaf
  • 滴滴 Tinyid
  • 百度 uid-generator

5.4 分布式事务

分库是无法避免分布式事务问题的

解决方案:

两阶段提交、三阶段提交、基于可靠消息(MQ)的解决方案、柔性事务等等。

5.5 多数据源

分库分表之后会面临从多个库或多个表中获取数据

解决方案:

客户端适配和代理层适配。

也就是说我们得借助分库分表的工具,帮助我们路由到对应的库表。

6 分库分表总结

  • 分库是解决数据库连接资源不足的问题,和磁盘IO性能息息相关
  • 分表是解决单表数据量过大问题,SQL查询时,即使通过索引也非常耗时,和CPU性能息息相关。
  • 分库解决的是写并发问题
  • 分表解决的是数据量大问题
  • 垂直分是从业务上分,水平分是从数据上分
  • 不要盲目进行分库分表,系统复杂度提升并不是什么好事
  • 要清楚系统出现了什么问题,评判一下是否需要分库分表
  • 根据系统出现的性能瓶颈,合理选取分库分表的方案
  • 选key很重要,既要考虑到均匀拆分,也要考虑到非Partition key(分区键)的查询
  • 在满足需求的情况下,分库分表的方案越简单越好

收好这份武林秘籍,让你分库分表再无烦恼相关推荐

  1. JAVAWEB增删改查武林秘籍

    增删改查武林秘籍 学之受用无穷,可在30分钟内写完增删改查所有后台代码 1.项目搭建 1:创建一个maven 带骨架webapp的项目 2:创建表:book表(你所要增删改查的表) 并且使用idea ...

  2. Qt武林秘籍学习笔记摘要

    1 原文链接 Qt开发经验: 自己总结的这十多年来做Qt开发以来的经验,以及Qt相关武林秘籍电子书,会一直持续更新增加,欢迎各位留言增加内容或者提出建议,谢谢! (gitee.com) 编程语录: 自 ...

  3. python武功秘籍解压密码_武林秘籍

    你好,我是一名极客!一个 75 后的老工程师! 我将花两分钟,表述清楚我喊你来这里的目的! 如果你看过武侠小说,你可以把这个经历理解为,你失足落入一个山洞遇到了一位垂暮 的老者!而这位老者打算传你一套 ...

  4. 基金套利的常见招数:高人套利手法像武林秘籍

    [题外话] 有人的地方就有江湖. 如今,在巨大的财富效应面前,投资基金已经成为一场"全民运动".虽然基金是强调价值投资的中长期投资品种,买入并长期持有就会有不错的收益,但江湖中人并 ...

  5. forms身份验证 不跳转_“东湖24小时”玩不够?收好这份指南,365天不重样

    5月19日 东湖风景区发起 "我在东湖等你"活动 网络达人畅游东湖 用镜头记录分享"东湖24小时" 镜头前,达人们在东湖帆船公园扬帆起航.在欢乐谷快乐尖叫.在楚 ...

  6. 普通平键的主要尺寸有_快来收下这份家装尺寸表,衣柜不再鸡肋

    有些人在装修的时候还把思想停留在户型决定一切的理念.户型的好坏对于我们的家装效果固然重要.但是好的装修效果离不开户型和格局布局设计的相辅相成.空间布局的设计其实和硬装没有太大的关系,几乎是由家具的摆放 ...

  7. Sphinx武林秘籍(上)

    为什么80%的码农都做不了架构师?>>>    Sphinx武林秘籍(上) ――使用现有的语言模型与声学模型 一.     使用平台 Windows XP.VMware workst ...

  8. 同软件多个线程设置不同ip_软件测试如何自学?收下这份《2020千锋性能测试入门视频教程》...

    萧亚轩在吐槽大会上说:"我只是天赋异禀,一个平平无奇的恋爱小天才."讲真,小千是百分百赞同,毕竟谁不是在平平无奇的生活里悄咪咪的给自己加持各种装备,偶尔一个大招适时放出,真的能够亮 ...

  9. 毕业季offer怎么拿?收下这份非典型求职面试指南

    摘要:求职面试莫慌,先自我评估一下 ,华为云专家手把手为你指导. 本文分享自华为云社区<毕业季offer怎么拿?收下这份非典型求职面试指南>,原文作者:技术火炬手 . 又是一年毕业季,对于 ...

最新文章

  1. 《皇帝:中国的崛起》从入门到精通
  2. iOS开发 贝塞尔曲线UIBezierPath
  3. Activity的launchMode和任务栈小结
  4. 【深度学习】——纠错error: Unable to find vcvarsall.bat:关于安装pycocotools
  5. Shell 快速指南
  6. (转)JDK 1.5中的ENUM用法
  7. 日产汽车宣布已关停日、英、美、南非、俄等地工厂
  8. python安装mysqldb模块_python MysqlDb模块安装及其使用详解
  9. nodejs虚拟服务器,NodeJs本地搭建服务器,模拟接口请求,获取json数据
  10. ❤️14万字的《微服务开发SpringBoot—从基础高级》(建议收藏)❤️
  11. 自己编写的C语言实时时钟代码
  12. Unity3D for iOS初级教程:Part 3/3
  13. macOS 常用字体下载安装
  14. php txt替换,文本替换专家批量替换TXT文本内容教程
  15. windows server2012搭建邮箱服务器+客户端界面(hmailserver+afterlogic)+批量创建邮箱
  16. win10 vs2008到期的解决办法:
  17. C++虚函数、多继承和虚基类学习心得 内存布局
  18. 给hotmail邮箱发信(status=deferred)解决办法
  19. The maximum number of tolerable server reconnection errors has been reached
  20. 2021年秋招面经分享·地平线【芯片设计研发工程师】

热门文章

  1. 分级渲染--百度地图路段红黄绿功能
  2. 被周董和郎朗拨弄,钢琴的“内心”如何掀起“波澜”?
  3. 本地windows首次远程登录阿里云ECS服务器
  4. threejs 绘制球体_Three.js基础探寻四——立方体、平面与球体
  5. tomcat基础简介与示例
  6. springboot中ElasticSearch入门与进阶:组合查询、聚合查询
  7. hbase应用场景 java_Hbase Java API 使用
  8. 两个数学家的问题,“你不知道我不知道你知道我知道”
  9. #10049. 「一本通 2.3 例 1」Phone List
  10. Fluent UDF中直接调用math.h中的误差函数erf、erfc