本文主要描述分库分表的算法方案、按什么规则划分。循序渐进比较目前出现的几种规则方式,最后第五种增量迁移方案是我设想和推荐的方式。后续章再讲述技术选型和分库分表后带来的问题。。

背景

随着业务量递增,数据量递增,一个表将会存下大量数据,在一个表有一千万行数据时,通过sql优化、提升机器性能还能承受。为了未来长远角度应在一定程度时进行分库分表,如出现数据库性能瓶颈、增加字段时需要耗时比较长的时间的情况下。解决独立节点承受所有数据的压力,分布多个节点,提供容错性,不必一个挂整个系统不能访问。

目的

本文讲述的分库分表的方案,是基于水平分割的情况下,选择不同的规则,比较规则的优缺点。 一般网上就前三种,正常一点的会说第四种,但不是很完美,前面几种迁移数据都会很大影响,推荐我认为比较好的方案五。

  • 方案一:对Key取模,除数逐步递增
  • 方案二:按时间划分
  • 方案三:按数值范围
  • 方案四:一致性Hash理念——平均分布方案(大众点评用这种,200G并且一步到位)
  • 方案五:一致性Hash理念——按迭代增加节点(为了方便增量迁移)
  • 方案六:一致性Hash理念——按范围分库(迭代迁移)

Version:1.0 StartHTML:000000208 EndHTML:000021485 StartFragment:000004002 EndFragment:000021399 StartSelection:000004002 EndSelection:000021395 SourceURL:https://my.oschina.net/u/4200053/blog/4255903 window.__STATIC_DOMAIN = "https://static.oschina.net";

方案选择


方案一:对Key取模,除数逐步递增

公式:key mod x (x为自然数)
Key可以为主键,也可以为订单号,也可以为用户id,这个需要根据场景决定,哪个作为查询条件概率多用哪个。优点:

  • 按需增加库、表,逐步增加
  • 分布均匀,每一片差异不多

缺点:

  • 很多时候会先从2开始分两个库逐级递增,然后分3个、4个、5个。如在mod 3 变 mod 5的情况下,取模后的大部分的数据的取模结果会变化,如key=3时,mod 3=0,突然改变为mod 5=3,则将会从第0表迁移到第3表,将会造成很多数据多重复移动位置。
  • 会重复迁移数据,当分2个时,有数据A在第0号表,分3个时数据A去了第1号表、到分4个时数据A会回到第0号表

方案二:按时间划分
可以按日、按月、按季度。
tb_20190101 tb_20190102 tb_20190103 ……
这个算法要求在订单号、userId上添加年月日或者时间戳,或者查询接口带上年月日,才能定位在哪个分片。优点:

  • 数据按时间连续
  • 看数据增长比较直观

缺点:

  • 因为考虑到历史数据一开始没分库分表后续进行分库分表时,历史数据的订单号不一定有时间戳,历史数据可能为自增或者自定义算法得出的分布式主键,导致查询时必须要上游系统传订单号、创建时间两个字段。
  • 若上游系统没有传时间,或者上游系统的创建时间与当前系统对应订单的创建时间不在同一天的情况下,则当前数据库表的数据记录需要有时间字段。因为上游系统只传订单号,这个时候需要获取创建时间,当前系统就必须要有一个主表维护订单号和创建时间的关系,并且每次查询时都需要先查当前系统主表,再查具体表,这样就会消耗性能。
  • 分布不一定均匀:每月增长数据不一样,可能会有些月份多有些月份少

推荐使用场景:日志记录


方案三:按数值范围

表0 [0,10000000) 表1 [10000000,20000000) 表2 [20000000,30000000) 表3 [30000000,40000000) ……优点:

  • 分布均匀

缺点:

  • 因为未知最大值,所以无法用时间戳作为key,这个方法不能用表的自增主键,因为每个表都自增数量不是统一维护。所以需要有一个发号器或发号系统做统一维护key自增的地方。

说后续推荐的方案中先简单说说一致性hash
先说一下一致性hash,有些文章说一致性Hash是一种算法,我认为它并不是具体的计算公式,而是一个设定的思路。
1.先假定一个环形Hash空间,环上有固定最大值和最小值,头尾相连,形成一个闭环,如int,long的最大值和最小值。 很多文章会假定2^32个位置,最大值为2^32-1最小值为0,即0~(2^32)-1的数字空间,他们只是按照常用的hash算法举例,真实分库分表的情况下不是用这个数字,所以我才会认为一致性hash算法其实是一个理念,并不是真正的计算公式。 如下图

2. 设计一个公式函数 value = hash(key),这个公式将会有最大值和最小值,如 key mod 64 = value; 这个公式最大为64,最小为0。然后把数据都落在环上。

3. 设定节点node。设定节点的方式如对ip进行hash,或者自定义固定值(后续方案是使用固定值)。然后node逆时针走,直到前一个节点为止,途经value=hash(key)的所有数据的都归这个节点管。 如 hash(node1)=10,则hash(key)=0~10的数据都归node1管。

概括
这里不详细说明这个理论,它主要表达的意思是固定好最大值,就不再修改最大值到最小值范围,后续只修改节点node的位置和增加node来达到减少每个node要管的数据,以达到减少压力。
备注: * 不推荐对ip进行hash,因为可能会导致hash(ip)得出的结果很大,例如得出60,若这个节点的前面没有节点,则60号位置的这个节点需要管大部分的数据了。 * 最好生成key的方式用雪花算法snowFlake来做,至少要是不重复的数字,也不要用自增的形式。 * 推荐阅读铜板街的方案 订单号末尾添加user%64


方案四:一致性Hash理念——平均分布方案
利用一致性hash理论,分库选择hash(key)的公式基准为 value= key mod 64,分表公式value= key / 64 mod 64,key为订单号或者userId等这类经常查询的主要字段。(后续会对这个公式有变化) 我们假定上述公式,则可以分64个库,每个库64个表,假设一个表1千万行记录。则最大64 * 64 * 1000万数据,我相信不会有这一天的到来,所以我们以这个作为最大值比较合理,甚至选择32 * 32都可以。
因为前期用不上这么多个表,一开始建立这么多表每个表都insert数据,会造成浪费机器,所以在我们已知最大值的情况下,我们从小的数字开始使用,所以我们将对上述计算得出的value进行分组。
分组公式:64 = 每组多少个count * group需要分组的个数 数据所在环的位置(也就是在哪个库中):value = key mode 64 / count * count
以下举例为16组,也就是16个库,group=16 这个时候库的公式为value = key mode 64 / 4 * 4,除以4后,会截取小数位得出一个整数,然后 * 4倍,就是数据所在位置。
// 按4个为一组,分两个表 count = 4: Integer dbValue = userId % 64 / count * count ;

hash(key)在0~3之间在第0号库 hash(key)在4~7之间在第4号库 hash(key)在8~11之间在第8号库 ……
备注:其实一开始可以64个为一组就是一个库,后续变化32个为一组就是两个库,从一个库到两个库,再到4个库,逐步递进。
从分1库开始扩容的迭代:

下图中举例分16组后,变为分到32组,需要每个库都拿出一半的数据迁移到新数据,扩容直到分64个组。

可以看到当需要进行扩容一倍时需要迁移一半的数据量,以2^n递增,所以进行影响范围会比较大。优点:

  • 如果直接拆分32组,那么就比较一劳永逸
  • 如果数据量比较大,未做过分表可以用一劳永逸方式。
  • 分布均匀
  • 迁移数据时不需要像方案一那样大部分的数据都需要进行迁移并有重复迁移,只需要迁移一半

缺点:

  • 可以扩展,但是影响范围大。
  • 迁移的数据量比较大,虽然不像方案一那样大部分数据迁移,当前方案每个表或库都需要一半数据的迁移。
  • 若要一劳永逸,则需要整体停机来迁移数据

方案五:一致性Hash理念——按迭代增加节点
(我认为比较好的方案)
一致性hash方案结合比较范围方案,也就是方案三和方案四的结合。
解析方案四问题所在
方案四是设定最大范围64,按2^n指数形式从1增加库或者表数量,这样带来的是每次拆分进行迁移时会影响当总体数据量的1/2的数据,影响范围比较大,所以要么就直接拆分到32组、64组一劳永逸,要么每次1/2迁移。
方案四对应迁移方案:

  1. 第一种是停机迁移数据,成功后,再重新启动服务器。影响范围为所有用户,时间长。
  2. 第二种是把数据源切到从库,让用户只读,主库迁移数据,成功后再切到主库,虽然用户能适用,影响业务增量
  3. 第三种是设定数据源根据规则让一半的用户能只读,另一半的用户能读能写,因为方案四迁移都是影响一般的数据的,所以最多能做到这个方式。

方案五详解
现在我想方法时,保持一致性hash理念,1个1个节点来增加,而不是方案四的每次增加2^n-n个节点。但是代码上就需要进行对新节点内的数据hash值判断。
我们基于已经发生过1次迭代分了两个库的情况来做后续迭代演示,首先看看已经拆分两个库的情况:
数据落在第64号库名为db64和第32号库名为db32


迭代二: 区别与方案四直接增加两个节点,我们只增加一个节点,这样迁移数据时由原本影响1/2的用户,将会只影响1/4的用户。

在代码中,我们先把分组从32个一组改为16个一组,再给代码特殊处理 0~16的去到新的节点 16~32走回原来的32号节点 32~63走回原来64号节点 所以下面就要对节点特殊if else
// 按32改为16个为一组,分两变为4个库 count = 16; Integer dbValue = userId % 64 / count * count ; if(dbValue<16){ // 上一个迭代这些数据落在db32中,现在走新增节点名为db16号的那个库 dbValue = 16; return dbValue; } else { // 按原来规则走 return dbValue; }


迭代三:

这样就可以分迭代完成方案四种的一轮的迁移
迁移前可以先上线,增加一段开关代码,请求接口特殊处理hash值小于16的订单号或者用户号,这样就只会影响1/4的人
// 在请求接口中增加逻辑 public void doSomeService(Integer userId){ if(迁移是否完成的开关){ // 如果未完成 Integer dbValue = userId % 64 / count * count ; if(dbValue<16){ //这部分用户暂时不能走下面的逻辑 return ; } } return dbValue; } }
// 在分片时按32个为一组,分两个库 count = 16; Integer dbValue = userId % 64 / count * count ; if(dbValue<16){ // 上一个迭代这些数据落在db32中,有一半需要走新增节点名为db16号的那个库 if(迁移是否完成的开关){ // 如果已经完成,就去db16的库 dbValue = 16; } return dbValue; } else { // 按原来规则走 return dbValue; }
如此类推,下一轮总共8个节点时,每次迁移只需要迁移1/8。其实也可以在第一个迭代时,不选择dbValue小于16号的来做。直接8个分一组,只选择dbValue<8的来做,这样第一个迭代的影响范围也会比较案例中小。上述案例用16只是比较好演示优点:

  • 易于扩展
  • 数据逐渐增大过程中,慢慢增加节点
  • 影响用户数量少
  • 按迭代进行,减少风险
  • 迁移时间短,如敏捷迭代思想

缺点:

  • 一段时间下不均匀

方案六:一致性Hash理念——按范围分库(迭代迁移)
如同上述方案五是方案四+方案一,可以达到逐步迁移数据,还有一种方案。就是方案四+方案三,只是不用取模后分组。
userId % 64 / count * count
因为上述公式,得出结果中,不一定每一片数据都是平均分布的。其实我们可以取模后,按范围划分分片,如下公式。
第一片 0<userId % 64<15 第二片 16<userId % 64<31 第三片 32<userId % 64<47 第四片 48<userId % 64<63
当然范围可以自定义,看取模后落入哪个值的数量比较多,就切某一片数据就好了,具体就不画图了,跟方案四类似。
因为迁移数据的原因,方案四中,如果数据量大,达到1000万行记录,每次迁移都需要迁移很多的数据,所以很多公司会尽早分库分表。
但是在业务优先情况下,一直迭代业务,数据一进达到很多的情况下16分支一也是很多的数据时,我们就可以用一致性Hash理念--按范围分库

long 雪花算法_海量数据分库分表方案(一)算法方案相关推荐

  1. 以下哪个不是迭代算法的缺点_海量数据分库分表方案(一)算法方案

    本文主要描述分库分表的算法方案.按什么规则划分.循序渐进比较目前出现的几种规则方式,最后第五种增量迁移方案是我设想和推荐的方式.后续章再讲述技术选型和分库分表后带来的问题. 背景 随着业务量递增,数据 ...

  2. 这篇讲MySQL海量数据分库分表的,实在太过瘾了!

    孙玄,前58同城技术委员会主席,前转转首席架构师,绝对业界大佬,资深架构师. 最近和玄姐聊技术,对架构的认知更上一层楼,下面是玄姐的一些谈话纪要. 01.怎么谈"架构"不耍流氓 前 ...

  3. 架构组件:基于Shard-Jdbc分库分表,数据库扩容方案

    架构组件:基于Shard-Jdbc分库分表,数据库扩容方案 一.数据库扩容 1.业务场景 互联网项目中有很多"数据量大,业务复杂度高,需要分库分表"的业务场景. 这样分层的架构 ( ...

  4. 高并发大流量情况下带来的海量数据分库分表的正确姿势

    注意!!! 不要为了分库分表而分库分表!!! 引入SOA架构中的一句话:架构不是一蹶而起的,而是慢慢演进的 一.为什么需要分库分表? 请求数太高: 在高并发情况下,大量请求落入数据库,最终会导致数据库 ...

  5. mysql分库分表风险_数据库分库分表存在的问题及解决方案

    读写分离分散了数据库读写操作的压力,但是没有分散存储压力,当数据库的数据量达到千万甚至上亿条的时候,单台数据库服务器的存储能力就会达到瓶颈,主要体现在以下几个方面: 数据量太大,读写性能会下降,即使有 ...

  6. CRC32算法理论和分库分表业务实现

    算法原理 CRC检验原理实际上就是在一个p位二进制数据序列之后附加一个r位二进制检验码(序列),从而构成一个总长为n=p+r位的二进制序列:附加在数据序列之后的这个检验码与数据序列的内容之间存在着某种 ...

  7. mysql分库分表按时间_数据库分库分表思路

    一. 数据切分 关系型数据库本身比较容易成为系统瓶颈,单机存储容量.连接数.处理能力都有限.当单表的数据量达到1000W或100G以后,由于查询维度较多,即使添加从库.优化索引,做很多操作时性能仍下降 ...

  8. nodejs mysql 分表_数据库分库分表学习

    大型网站数据库分库分表 分库分表方案: 垂直&水平 1.什么是垂直拆分? 指的是将一个包含了很多表的数据库,根据表的功能的不同,拆分为多个小的数据库,每个库中包含部分表. .垂直拆分的另外2种 ...

  9. mysql 分库分表 建表_【分库分表】sharding-jdbc实践—分库分表入门

    一.准备工作 1.准备三个数据库:db0.db1.db2 2.每个数据库新建两个订单表:t_order_0.t_order_1 DROP TABLE IF EXISTS`t_order_x`;CREA ...

最新文章

  1. 以安装PyTorch为例说明Anaconda在Windows/Linux上的使用
  2. 转:mysql的日期/时间函数
  3. 自定义Android时钟(支持秒针)
  4. 如何设置eclipse下查看java源码
  5. 【Android】Activity的task相关
  6. 读《构建之法》第11,12章有感
  7. linux 一切都是文件_一切都是文件
  8. 缓存设计方案 你了解吗 SpringBoot 快速集成实现一级缓存Redis和二级缓存Caffeine 可自定义扩展
  9. 工程选择LibGdx--开发环境搭建Strut2教程-java教程
  10. 苹果Mac Spotify 音乐格式转换器推荐:Sidify Music Converter
  11. modbus地址扫描_西门子PLC通信编程MODBUS通信举例
  12. cecore.cls.php 08cms,08cms小说系统 v1.0PHP CMS源码下载-华软网
  13. 【深入浅出向】从自信息到熵、从相对熵到交叉熵,nn.CrossEntropyLoss, 交叉熵损失函数与softmax,多标签分类
  14. postgresSQL的FDE加密
  15. 沙箱环境下实现支付宝网站支付
  16. 蓝牙协议栈HFP SCO连接流程
  17. 全球及中国板材制造行业销售前景与产销规模分析报告2022-2028年
  18. 简单的汇率转换工具---初试AJAX
  19. 新零售mysql设计(采购表 入库信息表 入库商品表)
  20. 计算机工程与科学是sci,系统科学与系统工程有哪些sci期刊

热门文章

  1. Java垃圾回收(GC)、找垃圾的方式、GC Root、GC停顿、引用、垃圾收集算法、收集器、GC日志、安全点、安全区域
  2. 取一列_excel工作案例:如何快速实现一列变两列?
  3. C/Cpp / 模板类中可以使用虚函数吗?模板成员函数可以是虚函数吗?
  4. 启明云端WT32-CAM操作视频,让你快速上手ESP32camera应用
  5. python关键字匹配_python通过BF算法实现关键词匹配的方法
  6. java头像选择系统_Android+超高仿微信图片选择器(头像选择)
  7. python中for循环的用法_浅谈Python的for循环
  8. IE浏览器error:Promise未定义
  9. 自动化测试 div sendkeys无效_【自动化测试】【JestSelenium】(04)—— Selenium WebDriver...
  10. Groovy 设计模式 -- 保镖模式