分库分表核心理念

文章目录

    • 分库,分表,分库分表
      • 什么时候分库?
      • 什么时候分表?
      • 什么时候既分库又分表?
      • 横向拆分 & 纵向拆分
    • 分表算法
      • Range 范围
      • Hash 取模
      • 一致性 Hash
      • 斐波那契散列
    • 严格雪崩标准(SAC)
    • 订单分库分表实战
    • 全局 ID 的生成
      • UUID
      • 基于某个单表做自增主键
      • 雪花算法
        • 时间回拨问题
    • 分库分表迁移
      • 停机迁移方案
      • 双写迁移方案
    • 分库分表带来的问题
    • 参考 & 推荐文章

分库,分表,分库分表

首先,我们需要知道所谓的"分库分表",根本就不是一件事,而是三件事,它们要解决的问题也都不一样。

这三件事分别是"只分库不分表"、“只分表不分库”、以及"既分库又分表"。

什么时候分库?

其实,分库主要解决的是并发量大的问题。因为并发量一旦上来了,那么数据库就可能会成为瓶颈,因为数据库的连接数是有限的,虽然可以调整,但也不是无限调整的。

所以,当你的数据库的读或者写的 QPS 过高,导致数据库连接数不足的时候,就需要考虑分库了,通过增加数据库实例的方式来提供更多的可用数据库连接,从而提升系统的并发度。

比较典型的分库场景就是在做微服务拆分的时候,会按照业务边界,把各个业务的数据从一个单一的数据库中拆分开,分别把订单、物流、商品、会员等单独放到对应的数据库中。

还有就是有的时候可能会把历史订单挪到历史库里面去。这也是分库的一种具体做法。

什么时候分表?

分库主要解决的是并发量大的问题,那分表其实主要解决的是数据量大的问题。

假如你的单表数据量非常大,因为并发不高,数据库连接可能还够,但是存储和查询的性能遇到了瓶颈,做了很多优化之后还是无法提升效率的时候,就需要考虑做分表了。

一般我们认为,单表行数超过 500 万行或者单表容量超过 2GB 时,才需要考虑做分库分表。

那我们是不是等到数据量到达 500 万后,才开始分库分表呢?

这个也不绝对,应该提前规划分库分表,如果估算 3 年后,表的数据量都不会到达 500 万,则不需要分库分表。

**分库分表的时候需要考虑数据未来 2~3 年的一个增量,**即使现在数据量不多,但是每天的数据增量很可观,几个月之后就可以突破 500 万上限,那么不是等到数据量到达 500 万的时候才分库分表,而是现在就应该考虑了。

什么时候既分库又分表?

那么什么时候分库又分表呢,那就是既需要解决并发量大的问题,又需要解决数据量大的问题的时候。通常情况下,高并发和大数据量的问题都是同时发生的,所以,我们会经常遇到分库分表需要同时进行的情况。

横向拆分 & 纵向拆分

谈及到分库分表,那就要涉及到该如何做拆分的问题。

通常在做拆分的时候有两种分法,分别是横向拆分(水平拆分)和纵向拆分(垂直拆分)。

假如我们有一张表,如果把这张表中某一条记录的多个字段,拆分到多张表中,这种就是纵向拆分。那如果把一张表中的不同的记录分别放到不同的表中,这种就是横向拆分。

横向拆分的结果是数据库表中的数据会分散到多张分表中,使得每一个单表中的数据的条数都有所下降。比如我们可以把不同的用户的订单,分表拆分放到不同的表中。

纵向拆分的结果是数据库表中的数据的字段数会变少,使得每一个单表中的数据的存储有所下降。比如可以把商品详情信息、价格信息、库存信息等等分别拆分到不同的表中。

纵向拆分比较适合做冷热分离,可以使得行数据变小,一个数据页就能存放更多的数据,在查询时就会减少I/O次数。

分表算法

选定了分表字段之后,如何基于这个分表字段来准确的把数据分表到某一张表中呢?

这就是分表算法要做的事情了,但是不管什么算法,我们都需要确保一个前提,那就是同一个分表字段,经过这个算法处理后,得到的结果一定是一致的,不可变的。

通常的分表算法有以下几种:

Range 范围

Range,即范围策略划分表。比如我们可以将表的主键 order_id,按照从 0~300万 的划分为一个表,300万 ~ 600万划分到另外一个表。

有时候我们也可以按时间范围来划分,如不同年月的订单放到不同的表。

  • 优点:范围分表,有利于扩容。
  • 缺点:最近一段时间的数据都是汇聚在一张表里面,可能会有热点问题。比如最近一个月的订单都在 0~300万之间,平时用户一般都查最近一个月的订单比较多,那么请求就都打到 order_01 了。

Hash 取模

Hash 取模策略:

指定的路由key(一般是 user_id、order_id 等作为key)对分表总数进行取模,把数据分散到各个表中。

比如原始订单表信息,我们把它分成4张分表:

比如 id=1,对 4 取模,就会得到1,就把它放到 t_order_1 ;

一般,我们会取哈希值,再做取余:

Math.abs(orderId.hashCode()) % table_number

  • 优点:Hash取模的方式,不会存在明显的热点问题。
  • 缺点:如果未来某个时候,表数据量又到瓶颈了,需要扩容,就比较麻烦。所以一般建议提前规划好,一次性分够(可以考虑一致性哈希)。

一致性 Hash

为了解决 Hash 扩容的问题,我们可以采用一致性哈希的方式来做分表。

一致性哈希可以按照常用的 Hash 算法来将对应的 key 哈希到一个具有 2^32 次方个节点的空间中,形成一个顺时针首尾相接的闭合环形,这个环称为哈希环

当添加一台新的数据库服务器时,只有增加服务器的位置和逆时针方向第一台服务器之间的键会受影响。

简单来说,一致性哈希算法能够使机器节点的变动对整个集群的影响达到最小。

一致性哈希也存在一些问题,如:节点漂移、数据倾斜。这些都有对应的解决方案,大家可以参考我发的文章链接,这里不再赘述。

参考:一致性哈希问题及其解决方案。

斐波那契散列

前面几种分表算法,大家会接触多一点,斐波那契散列实际在分表算法中几乎不被使用。

JDK 的 ThreadLocal 源码中有一段有意思的代码,如下所示:

定义了一个魔法值 HASH_INCREMENT = 0x61c88647,这个值被称之为 “魔数”。

0x61c88647 与一个神奇的数字产生了联系,它就是 (Math.sqrt(5) - 1)/2。也就是传说中的黄金比例 0.618(0.618 只是一个粗略值),即0x61c88647 = 2^32 * 黄金分割比,同时也对应了上文提到的斐波那契散列。

它常用于在散列中增加哈希值。上面的代码注释中也解释到是为了让哈希码能均匀的分布在 2 的 N 次方的数组里。

至于为什么使用斐波那契数列后散列更均匀,就涉及到相关数学问题了,此处不做更多解释。

详细解释可参考:从ThreadLocal的实现看散列算法

严格雪崩标准(SAC)

上面介绍了一些分表算法,那么一个好的分表算法有没有参考标准呢?

在密码学中,雪崩效应avalanche effect)指加密算法的一种理想属性。雪崩效应是指当输入发生最微小的改变(例如,反转一个二进制位)时,也会导致输出的不可区分性改变(输出中每个二进制位有50%的概率发生反转)。

严格雪崩标准(SAC),建立于密码学的完全性概念上,是雪崩效应的形式化。它指出,当任何一个输入位被反转时,输出中的每一位均有 50% 的概率发生变化。

简单来说,当我们对数据库从 8库32表 扩容到 16库32表 的时候,每一个表中的数据总量都应该以 50% 的数量进行减少。这样才是合理的。

引入严格雪崩标准(SAC) 之后,斐波那契散列是不满足这个标准的,也就是说使用斐波那契散列,在分库分表扩容情况下,可能导致数据分布不均匀,这也是为什么斐波那契散列几乎不用于分表算法的原因。

订单分库分表实战

背景:订单表的读写场景复杂,⼀般有买家维度、卖家维度、订单号维度 3 个主要维度。多读写维度情况下⽆论采取哪种维度做分库分表,对另外两种维度的查询性能来说,基本都是灾难。

解决方案:双拆分列哈希(RANGE_HASH)。

选取两个拆分键,两个拆分键的后 N 位需确保一致,根据任一拆分键后 N 位计算哈希值,然后再按分库数取模,完成路由计算。

先采用 RANGE_HASH 拆分算法按买家 id 后 N 位、订单号后 N 位维度做分库分表,作为买家表逻辑表。再用 HASH 拆分函数按商家 id 冗余一份数据,作为卖家表逻辑表。

订单号生成规则需要根据买家表分表特性订单号后 N 位等于买家 id 后 N 位做设计。

比如用户id为 12345678,则用户在下单时生成的单号为:xxxxxxxxx345678,单号前几位可以根据公司自己规则设定,但是要注意不能重复。

全局 ID 的生成

涉及到分库分表,就会引申出分布式系统中唯一主键 ID 的生成问题,有以下几种方式:

UUID

UUID 是可以做到全局唯一的,而且生成方式也简单,但是我们通常不推荐使用它做唯一ID,首先 UUID 太长了,其次字符串的查询效率也比较慢,而且没有业务含义,根本看不懂。

基于某个单表做自增主键

多张单表生成的自增主键会冲突,但是如果所有表的主键都从同一张表生成是不是就可以了。

所有的表在需要主键的时候,都到这张表中获取一个自增的 ID。

这样做是可以做到唯一,也能实现自增,但是问题是这个单表就变成整个系统的瓶颈,而且也存在单点问题,一旦他挂了,那整个数据库就都无法写入了。

雪花算法

雪花算法也是比较常用的一种分布式 ID 的生成方式,它具有全局唯一、递增、高可用的特点。

雪花算法生成的主键主要由 4 部分组成,1bit 符号位、41bit 时间戳位、10bit 工作进程位以及 12bit 序列号位。

时间戳占用 41bit,精确到毫秒,总共可以容纳约 69 年的时间。

工作进程位占用 10bit,其中高位 5bit 是数据中心 ID,低位 5bit 是工作节点 ID,最多可以容纳 1024 个节点。

序列号占用 12bit,每个节点每毫秒从0开始不断累加,最多可以累加到 4095,一共可以产生 4096 个 ID。

所以,雪花算法在同一毫秒内最多可以生成 1024 X 4096 = 4194304 个唯一的 ID。

时间回拨问题

熟悉雪花算法的可能了解到雪花算法存在名为“时间回拨” 的问题。

时间回拨:由于机器的时间是动态调整的,有可能会出现时间跑到之前几毫秒,如果这个时候获取到了这种时间,则会出现数据重复。

时间回拨问题解决思路可以参考美团开源的 Leaf。

文章链接:Leaf——美团点评分布式ID生成系统

美团 Leaf 引入了 Zookeeper 来解决时钟回拨问题,其大致思路为:每个 Leaf 运行时定时向 zk 上报时间戳。每次 Leaf 服务启动时,先校验本机时间与上次发 ID 的时间,再校验与 zk 上所有节点的平均时间戳。如果任何一个阶段有异常,那么就启动失败报警。

这个解决方案还是比较好理解的,就是对比上次发 ID 的时间,还有其他机器的平均时间,通过本地存储时间戳 + 定时上报时间戳的方式,解决了时间回拨的问题。

分库分表迁移

有一个未分库分表的系统,现在要分库分表,如何才可以让系统从未分库分表切换到分库分表上?

停机迁移方案

先说一个最 low 的方案,就是很简单,大伙凌晨 12点 开始运维,网站或者 app 挂个公告,说 0 点到早上 6 点进行服务器维护,无法访问…

接着到 0 点,停机,系统停掉,没有流量写入了,此时老的单库单表数据库静止了。然后提前写好一个导数的一次性工具,此时直接跑起来,然后将单库单表的数据读出来,写到分库分表里面去。

导数完了之后,就 ok 了,修改系统的数据库连接配置啥的,包括可能代码和 SQL 也许有修改,那你就用最新的代码,然后直接启动连到新的分库分表上去。

但是这个方案比较 low,有个致命的问题就是业务要中断,来看看高大上一点的方案。

双写迁移方案

这个是常用的一种迁移方案,比较靠谱一些,不用停机。

大致步骤如下:

  1. 先改造我们的数据写入端, 使数据同时写入旧数据库和新数据库。
  2. 对存量数据进行不停机的迁移。
  3. 等到双写服务运行一段时间,再次进行旧数据和新数据的校验同步。
  4. 完全切换读取的数据源为新数据库,关闭旧数据库的写入和读取,下线旧数据库。

这种方式的好处是:迁移的过程可以随时回滚,将迁移的风险降到了最低。劣势是:时间周期比较长,应用有改造的成本。

分库分表带来的问题

分库分表之后,会带来很多问题。

首先,做了分库分表之后,所有的读和写操作,都需要带着分表字段,这样才能知道具体去哪个库、哪张表中去查询数据。如果不带的话,就得支持全表扫描。

还有,一旦我们要从多个数据库中查询或者写入数据,就有很多事情都不能做了,比如跨库事务就是不支持的。

所以,分库分表之后就会带来因为不支持事务而导致的数据一致性的问题。

其次,做了分库分表之后,以前单表中很方便的分页查询、排序等等操作就都失效了。因为我们不能跨多表进行分页、排序。

总之,分库分表虽然能解决一些大数据量、高并发的问题,但是同时也会带来一些新的问题。所以,在做数据库优化的时候,还是建议大家优先选择其他的优化方式,最后再考虑分库分表。

参考 & 推荐文章

分库分表经典15连问

再有人问你什么是分库分表,直接把这篇文章发给他

Leaf——美团点评分布式ID生成系统

大众点评订单系统分库分表实践

从ThreadLocal的实现看散列算法

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/419928.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【880高数】高等数学一刷错题整理

第一章 函数、极限、连续 2024.8.11日 1. 2. 3. 4. 5. 2024.8.12日 1. 2. 3. 4. 5. 6. 7. 8. 2024.8.13日 1. 2. 3. 4. 2024.8.14日 1. 2. 3. 4. 5. 第二章 一元函数微分学及其应用 2024.8.15日 1. 2. 3. 4. 5. 6. 2024.8.16日 1. 2. 3. 4. 5. 2024.8.17日 1. 2. 3. 4…

个人简历 (自己设计的)

欢迎大家来观看。 代码如下&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" co…

相亲交友系统背后的科技力量:智能匹配的秘密

随着互联网技术的发展&#xff0c;相亲交友系统已经成为许多人寻找另一半的重要工具。这些相亲交友系统不仅仅是一个简单的社交平台&#xff0c;它们背后隐藏着强大的科技力量&#xff0c;尤其是智能匹配技术&#xff0c;使得用户能够更加高效地找到适合自己的伴侣。 相亲交友…

信息学奥赛初赛天天练-87-NOIP2014普及组-完善程序-矩阵、子矩阵、最大子矩阵和、前缀和、打擂台求最大值

1 完善程序 最大子矩阵和 给出 m行 n列的整数矩阵&#xff0c;求最大的子矩阵和(子矩阵不能为空)。 输入第一行包含两个整数 m和 n&#xff0c;即矩阵的行数和列数。之后 m行&#xff0c;每行 n个整数&#xff0c;描述整个矩阵。程序最终输出最大的子矩阵和。 &#xff08;最…

C语言俄罗斯方块(VS2022版)

C语言俄罗斯方块 演示视频一、前置知识1.Win32 API 的使用2.宽字符的使用 二、封装核心数据与框架介绍三、核心操作介绍旋转操作检测操作水平检测竖直检测代码化简 四、源码展示在 tetris.h 中&#xff1a;在 tetris.c 中&#xff1a;在 test.c 中&#xff1a; 以下代码环境为 …

码上进阶_刷题模块测试_用例设计

码上进阶_刷题模块测试_用例设计 系统概述&#xff1a; 码上进阶是为程序员专门打造的交流平台&#xff0c;采用主流的微服务框架和C端技术栈作为技术基础。在这个平台上&#xff0c;程序员 可以通过刷题、练习和模拟面试来提升自己的面试能力。 功能测试&#xff1a; 登录…

SpringBoot OAuth2自定义登陆/授权页

背景 5 月份的时候&#xff0c;我实践并整理了一篇博客&#xff1a;SpringBoot搭建OAuth2&#xff0c;该博客完成之后&#xff0c;很长一段时间里我都有种意犹未尽的感觉。诚然&#xff0c;我把OAuth2搭起来了&#xff0c;各种场景的用例也跑通了&#xff0c;甚至源码也看了&am…

99.WEB渗透测试-信息收集-网络空间搜索引擎shodan(1)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;98.WEB渗透测试-信息收集-Google语法&#xff08;12&#xff09; 信息收集方向-网络空间…

【IDEA配置一个maven项目(详细操作流程)】

目录 一、安装Maven 1、官网下载maven链接地址&#xff1a;Maven – Download Apache Maven 2、下载完成后&#xff0c;解压到某一路径下。E:\JavaTools\apache-maven-3.9.8为例&#xff0c;实际配置环境变量时以自己安装的路径为准。 二、配置环境变量 1、右键此电脑–&g…

springboot、flowable 生成图片发布到Docker乱码问题

flowable自带的方法生成图片时&#xff0c;如设置字体为宋体&#xff0c;则本地测试没有问题&#xff0c;因为windows自带宋体字体库&#xff0c;但是如果发布到Docker&#xff0c;则会出现乱码问题&#xff0c;因为大部分Docker并不包含宋体字体库&#xff1b; 通过Java代码&a…

基于springboot+vue实现的在线商城系统

系统主要功能&#xff1a; &#xff08;1&#xff09;商品管理模块&#xff1a;实现了商品的基本信息录入、图片上传、状态管理等相关功能。 &#xff08;2&#xff09;商品分类模块&#xff1a;实现了分类的增删改查、分类层级管理、商品分类的关联等功能。 &#xff08;3&…

一个穷稳且病多的中年案例

调整 理性消费&#xff0c;量入为出 重视健康&#xff0c;提前规划 多元收入&#xff0c;提升自我 心态平和&#xff0c;知足常乐 提示&#xff1a;最后悔买“方”。 “方”和“車”对现金流的影响非常大。 全都是大额消耗性支出。 保持健康也需要物质基础。 为何收入或…

深度学习应用 - 自然语言处理(NLP)篇

序言 在信息技术的浩瀚星空中&#xff0c;深度学习犹如一颗璀璨的新星&#xff0c;正引领着人工智能领域的深刻变革。作为这一领域的核心分支&#xff0c;自然语言处理&#xff08; NLP \text{NLP} NLP&#xff09;更是借助深度学习的力量&#xff0c;实现了前所未有的飞跃。自…

BookStack在线文档管理系统本地Docker部署与远程访问详细教程

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

电池的电-热-寿命模型是什么?

一、背景 电池的电-热-寿命模型在工程领域具有重要意义&#xff0c;它是一种描述电池性能、温度与使用寿命之间相互关系的复杂模型。具体工程意义体现在以下几个方面&#xff1a; 性能预测&#xff1a; 通过电-热-寿命模型&#xff0c;工程师可以预测在不同负载条件下电池的…

基于YOLOv8的PCB缺陷检测算法,加入一种基于内容引导注意力(CGA)的混合融合方案(一)

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文内容&#xff1a;针对基于YOLOv8的PCB缺陷检测算法进行性能提升&#xff0c;加入各个创新点做验证性试验。 1&#xff09;提出了一种基于内容引导注意力(CGA)的混合融合方案&#xff0c;mAP0.5由原始的0.966提升至0.975 1.PCB缺陷…

【数据结构】排序算法篇二

【数据结构】排序算法篇二 1. 快速排序&#xff08;hoare版本&#xff09;&#xff08;1&#xff09;基本思想&#xff1a;&#xff08;2&#xff09;动态图解&#xff1a;&#xff08;3&#xff09;代码实现&#xff1a;&#xff08;4&#xff09;特性总结&#xff1a; 2. 快速…

Spring Boot属性注入的多种方式!

Spring Boot的一个问题&#xff0c;证明你是不是真正的 "会用" Spring boot ?Spring Boot的一个问题&#xff0c;直接暴露你是不是真正使用Spring Boothttps://mp.weixin.qq.com/s?__bizMzkzMTY0Mjc0Ng&mid2247484040&idx1&sn64ad15d95e44c874cc890973…

uboot源码分析uboot启动流程,uboot-CMD命令调用关系

uboot的最终目的是引导启动内核加载系统&#xff0c;根据这个线索我们可以首先找到uboot引导内核的main函数&#xff0c;查看系统引导的执行跳转的函数 main_loop。 下面对uboot函数的调用关系和主要调用函数进行分析。 一、uboot函数调用关系梳理 函数调用如下&#xff1a; …

Oracle Linux 8.10安装Oracle19c(19.3.0)完整教程

安装前请仔细将文档通读一遍&#xff0c;安装过程中根据安装命令仔细核对&#xff0c;特别留意一些字体加粗或标红的字样&#xff0c;遇到问题请及时咨询公司 1、基础环境 1.1、操作系统 cat /etc/redhat-release 1.2、主机名 医院默认分配的主机名可能跟其他主机会有重复&a…