万字长文之分库分表里如何设计一个主键生成算法?【后端面试题 | 中间件 | 数据库 | MySQL | 分库分表】

news2024/9/21 11:08:26

分库分表是在面试里一个非常热门而且偏难的话题,下面了解UUID、自增主键和雪花算法的特点,并且在面试的时候刷出亮点。

前置

所谓的分库分表严格来说是分数据源、分库和分表。例如每个公司订单表的分库分表策略就是用了8个主从集群,每个主从集群4个库,每个库有32张表,合起来有8432张表。
在这里插入图片描述
不过根据数据规模和读写压力,也可以共享一个主从集群,然后只分库或者只分表。如果面试面到了分库分表的内容,那么主键生成基本上就是一个绕不开的话题。在没有分库分表的时候,我们可以使用自增主键。
比如在MySQL里的建表语句,指定了AUTO_INCREMENT

CREATE TABLE order (
   id BIGINT PRIMARY KEY AUTO_INCREMENT,
   buyer_id BIGINT NOT NULL
)

在分库分表的场景下,这种自增主键就没法使用了,因为存在冲突的可能。举个最简单的例子,假如我们分库分表只分表,而且按照buyer_id % 2 的值来分成两张表,分别是order_1order_2。如果这两张表都依赖于自增生成主键,那么两张表会生成相同的ID,但是订单这一类的业务,需要一个全局唯一的ID
主键生成一般还伴随两个要点:

  • 全局唯一的ID依旧能够保持自增,因为自增与否会显著影响插入的性能;
  • 只有数据量大的才会考虑分库分表,而数据量大一般意味着并发高,所以还需要考虑怎么支持高并发

面试准备

首先需要把话题引到主键生成上,如果接触过使用分库分表的项目,那么在简历和项目介绍的时候一定要提及分库分表关键词,后面等面试官主动问你主键是如何生成的。
面试过程中被问到了数据库自增主键相关的问题,那么要主动提起自增主键不适用于分库分表场景,然后面试官自然就会追问分库分表场景下主键的生成问题。

准备工作:

  1. 深入理解市面上常见的主键生成策略
  2. 准备一个有亮点、微创新的主键生成方案
  3. 记住一些可行的优化方案

常见主键生成策略

UUID

最简单粗暴的方案,也是面试的时候必须要回答出来的一种策略。如果想要拉开差距,即使是最简单的UUID方案也需要下一番功夫,首先需要详细地解释UUID的两个弊端

  • 过长:这个弊端在面试里讨论的比较少,毕竟采用UUID的地方就不会在意它的长度
  • UUID不是递增的:要重点描述的,并且要尝试刷出亮点

亮点1:页分裂

UUID不是递增的这个弊端,要想讲清除,就要先描述为什么会希望在数据库里面使用自增主键。那么可以引用数据库为什么使用自增主键的知识点来回答这个问题,关键词就是页分裂
在这里插入图片描述如果你尝试往23之后插入一个25,这个叶子节点已经放不下了,不得已需要分裂成(20,21)和(22,23,25)两个节点。原本的(10,20,30)多了一个元素之后变成了(10,20,22,30),即页分裂这个东西是可能引起连锁反应的,从叶子节点沿着树结构一路分裂到根节点
可以这样回答

UUID最大的缺陷是它产生的ID不是递增的。一般来说,我们倾向于在数据库中使用自增主键,因为这样可以迫使数据库的数朝着一个方向增长,而不会造成中间叶节点分裂,这样插入性能最好。而整体上UUID生成的ID可以看作是随机,那么就会造成导致数据往页中间插入,引起更加频繁地页分裂,在糟糕的情况下,这种分裂可能引起连锁反应,整棵树的树形结构都会受到影响。所以我们普遍倾向于采用递增的主键。

亮点2:顺序读

此外,我们还可以从一个比较新奇的角度,解释为什么要使用自增主键,关键词就是顺序读
在这里插入图片描述

自增主键还有一个好处,就是数据会有更大的概率按照主键的大小排序,两条主键相近的记录,在磁盘上的位置也是相近的。那么可以预计,在范围查询的时候,我们能够更加充分地利用到磁盘地顺序读特性

亮点3:InnoDB的数据组织

如果希望在面试官面前树立在数据库方面积累比较多的形象,就要进一步解释数据库的页分裂究竟是怎么回事,这时候可以用MySQL InnoDB引擎来举例子。

MySQL的InnoDB引擎,每一页上按照主键的大小放着数据。假如说我现在有一个页放着主键1、2、3、5、6、7这六行数据,并且这一页已经放慢了。现在我要插入一个ID为4的行,那么InnoDB引擎就会发现,这一页已经放不下4这行数据了,于是逼不得已,就要把原本的页分成两页,比如1、2、3放到一页,5、6、7放到另外一页,然后把4放到第一页。
这种页分裂会造成一个问题,就是虽然从逻辑上来说 1、2、3 这一页和 5、6、7 这一页是邻近的两个页,但是在真实存储的磁盘上,它们可能离得很远。

到这一步,你的回答就已经算是可以了。面试官如果想要进一步探讨,那么可能会继续追问 InnoDB 引擎上的页这种数据结构有什么字段,各自有什么用处。如果你现在是临时抱佛脚准备面试,那么就没必要去背这些八股文。但是如果你只是平常在学习技术知识,想要夯实基础,那么我建议你深入去看看这部分内容。

数据库自增

还有一种常见的方案也叫自增,不过这种自增有点特殊,它是设置了步长的自增。
在这里插入图片描述
经过分库分表后我有10个表,可以让每个表按照步长生成自增ID,比如第一个表生成的是1,11,21,31这种ID,第二个表生成的是2,12,22,32这种ID,这种方案的关键是表内部自增。这种方案非常简单,而且本身我们在应用层面并不需要做任何事情,只是需要在创建表的时候指定好步长就可以了。ID虽然并不一定是全局递增的,但是在一个表内部,它肯定是递增的,这种方案的性能基本取决于数据库性能,应用层面上也不需要关注。

雪花算法

还可以从雪花类算法上找找亮点,雪花算法的原理不难,它的关键点在于分段
在这里插入图片描述
雪花算法保证ID唯一性的理由:

  • 时间戳是递增的,不同时刻产生的ID肯定是不同的
  • 机器ID是不同的,同一时刻不同机器产生的ID肯定也是不同的
  • 同一时刻同一机器上,可以轻易控制序列号
    面试中要先回答这几个理由,然后解释:

雪花算法采用64位来表示一个ID,其中1比特保留,41比特表示时间戳,10比特作为机器ID,12比特作为序列号

刷亮点有以下方法

亮点1:调整分段

第一个方法是深入讨论每个字段,关键点就是根据需求自定义各个字段含义、长度
大多数情况下,如果自己设计一个类似的算法,那么每个字段的含义、长度都可以灵活控制的,比如时间戳41比特可以改的更短或更长。

机器ID虽然明面上是机器ID,但是实际上并不是指物理机器,准确说是算法实例。例如,一台机器部署两个进行,每个进行的ID是不同的;又或者进一步切割,机器ID前半部分表示机器,后半部分可以表示这个机器上用于产生ID的进程、协程或线程。甚至机器ID也并不一定非得表示机器,也可以引入一些特定的业务含义。而序列号也是可以考虑加长或缩短的。

最后一句总结,升华主题

雪花算法可以算是一种思想,借助时间戳和分段,我们可以自由切割ID的不同比特位,赋予其不同的含义,灵活设计自己的ID算法。

在这里插入图片描述

亮点2:序列号耗尽

有一个问题,不管你怎么设计雪花算法,你的序列号长度都有可能不够。比如前面标准的是12比特,那么有没有可能并发非常高,以至于12比特在某一个特定的时刻机器上的比特全部用完了吗?

解决方案:

  • 如果12比特不够,你就给更多比特,这部分比特可以从时间戳里面拿出来
  • 如果还不够,那么就让业务方等待一下,到下一个时刻,自然又可以生成新的ID了,也就是时间戳变了,这也算是一种变相的限流

在这里插入图片描述在这里插入图片描述
所以可以这样回答:

一般来说可以考虑加长序列号的长度,比如缩减时间戳,然后挪给序列号ID。当然也可以更加粗暴地将64位地ID改成96位地ID,那么序列号ID就可以有三四十位。不过彻底地兜底方案还是要有地,我们可以考虑引入类似限流地做法,在当前时刻地ID已经耗尽以后,可以让业务方等一下。我们的时间戳一般都是毫秒数,那么业务方最多就会等一毫秒。

这里也有个问题就是:如果业务方等一下会有什么问题?

业务方等待算是一个比较危险的方案,因为这可能导致大量业务方阻塞住,导致线程耗尽或协程耗尽之类的问题,不过如果是偶发性的序列号不够,那么问题不大,因为阻塞的业务方很快就能拿到ID。
那么如果序列号耗尽不是偶发性一个问题,是长期的问题,还是要考虑从业务角度切割,不同业务使用不同的ID生成,就不要共享了。又或者,逼不得已还是用96或128位的。

亮点3:数据堆积

设想一个场景:分库分表是按照ID除以32的余数来进行的,那么如果你的业务非常低频,以至于每个时刻都只生成了尾号为1的ID,那么是不是所有的数据都分到了一张表里呢?
在这里插入图片描述
解决方案也很简单:

  • 某一个时刻使用随机数作为起点,而不是每次从0开始计数
  • 使用上一个序列号作为起点,比如上一个序列只分到了3,那么下一个时刻的序列号就从4开始

随机数算是比较正统的方案,第二个方案看起来有点奇怪。

在低频场景下,很容易出现序列号几乎没有增长,从而导致数据在经过分库分表之后只落到某一张表里的情况。为了解决这种问题,可以考虑这么做,序列号部分不再是从0开始增长,而是从一个随机数开始增长。还有一个策略就是序列号从上一时刻的序列号开始增长,但是如果上一时刻序列号已经很大了,那么就可以退化为从0开始增长,这样比随机数更可控一点,而且性能也更好一点。
一般来说,这个问题只在利用ID进行哈希的分库分表里面有解决的意义。在利用ID进行分库分表的情况下,很显然某一段时间内产生的ID都会落到同一张表里。不过这也是我们的使用范围分库分表预期的行为,不需要解决

主键内嵌分库分表键

大多数时候,我们会面临一个问题,就是分库分表的键和主键并不是同一个。比如在C端的订单分库分表,我们可以采用买家ID来进行分库分表。但是一些业务场景,比如说查看订单详情,可能是根据主键或是订单SN来查找的。
那么可以考虑借鉴雪花算法的设计,将主键生成策略和分库分表键结合在一起,也就是在主键内部嵌入分库分表键。例如,我们可以这样设计订单ID的生成策略,假设分库分表使用的是买家ID的后四位。第一段依旧是采用时间戳,第二段换成了买家后四位,第三段采用随机数

普遍情况下,我们都是用买家ID来查询对应的订单信息,在别的场景下,比如我们只有一个订单ID,这时候我们可以取出订单ID里嵌入进去的买家ID后四位,来判断数据存储哪个库、哪个表。类似的设计还有答题记录按照答题者ID来分库分表,但是答题记录ID本身可以嵌入这个答题者ID里用于分库分表的部分。

在这里插入图片描述

最后要记得升华一下这种设计思想

这一类解决方案,核心就是不拘泥于雪花算法每一段的含义。比如第二段可以使用具备业务含义的ID,第三段可以自增,也可以随机。只要我们能够保证ID生成是全局递增且独一无二的就可以

为什么要这样升华呢?因为这段话里,我们很明显的埋下了两颗雷,一个是全局递增,一个是独一无二。也就是说这个亮点方案保证不了这两点,可能会被面试官追问这两个点。

全局递增

问题:这个方案能够保证主键递增吗?

这个保证不了,但是能够做到大体上是递增的。可以设想,同一时刻如果有两个用户来创建订单,其中用户ID为2345的先创建,用户ID为1234的后创建,很显然用户1234会产生一个比用户2345更小的订单ID;又或者同一时刻一个买家创建了两个订单,但是第三段是随机数,第一次100,第二次99,显然第一次产生的ID更大。
但是这并不妨碍我们认为,随着时间推移,后一时刻产生的ID肯定比前一时刻产生的ID要大。这样一来,虽然性能比不上完全严格递增的主键好,但是比完全随机的主键好。

独一无二

这个方案不能保证ID唯一,但是这并不是一个很大的问题。
可以想到,不能保证独一无二是因为在第三段里使用了随机数,既然是随机数,就可能随机到同样的数字。但是,产生冲突ID的可能性是很低的。它要求同一时刻同一个用户创建了两个订单,然后订单ID的随机数部分随机到了同一个数字

产生一样ID的概率不是没有,而是极低,他还要求同一个用户在同一时刻创建了两个订单,然后订单ID的随机数部分一模一样,这是一个很低的概率。
在下单场景下,正常的用户都是一个个订单慢慢下,如果同一时刻同时创建两个订单,对于用户来说,是一件不可能的事情。而如果有攻击者下单,那就更加无所谓,失败就失败了。而即使真的由用户因为共享账号之类的问题同一时刻下两个订单,那么随机到同一个数的概率也是十万分之一。(这里的十万分之一,是假设产生的随机数的范围是0到十万)
解决方案也很简单,就是在插入数据的时候,如果返回了主键冲突,就重新产生一个,再次尝试就可以了

还想继续刷亮点的话,可以假装不经意地说:

实际上,还有一种非常偶发性的因素也可能会引起 ID 冲突,也就是时钟回拨,不过相比正统雪花算法,时钟回拨问题在这个方案里面不太严重,毕竟还有一个随机数的部分。

时钟回拨现象指的是系统时钟被调整到之前的时间,这通常发生在服务器或系统时间同步时。在分布式系统中,如果使用基于时间戳生成的主键策略(如雪花算法),时钟回拨可能会导致ID冲突。

优化思路

这些思路说只是想过,但是并没有落地

优化的点就是:批量取、提前取、singleflight 取、局部分发。注意,批量取和提前取对应的还有批量生成和提前生成,思路是一样的。万一面试官问到了如何设计一个高性能的发号器,你也可以回答这些点。

发号器是一种在分布式系统中用于生成唯一ID的工具或服务。它可以确保在多个并发请求或业务流程中生成的ID是全局唯一的,通常用于数据库的主键生成、事务标识符、分布式锁等场景。

批量取

业务方每次跟发号器打交道,不是只拿走一个ID,而是拿走一批,比如拿100个,拿到之后业务方内部自己慢慢消耗,消耗完了再去取下一批。
优点:极大地减轻发号器地并发压力,比如一批是100个,那么并发数就降低为原来的1%了。
缺点:可能破坏递增的趋势。比如说一个业务方 A 先取走了 100 个 ID,然后业务方 B 又取走 100 个,结果业务方 B 先用完了自己取的 ID,插到了数据库里面;然后业务方 A 才用完自己的 100 个

提前取

业务方提前取到ID,这样就不需要真的等待需要ID的时候再临时取。
提前取可以和批量取结合在一起,即提前取一批,然后内部慢慢使用。在快用完的时候再取一批。
同时也要设计一个兜底措施,如果要是用完了,新的一批还没取过来,要让业务方停下来等待。
优点: 提高业务方性能
缺点: 可能破坏递增的趋势

singleflight取

类似于在缓存中应用singleflight模式。假如说业务方A有几十个线程或协程需要ID,那么没有必要让这些线程都去取ID,而是派一个代表去取。这个代表取到之后再分发给需要的线程,这也能降低发号器的并发负载

思路可以进一步优化,每次取得时候多取一点,后续还有别的线程需要,也不用自己去取了。

局部分发

假如说现在整个实例上有 1000 个 ID,这些 ID 是批量获取的。那么一个线程需要 ID 的时候,它就不再是只拿一个,而是拿 20 个,然后存在自己的 TLB(thread-local-buffer) 里面,以后这个线程需要 ID 的时候,就先从自己的 TLB 里面拿,避开了全局竞争,减轻了并发压力

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Layout View

GoTo 数据网格和视图入门 本文档仅概述特定于LayoutView的功能。有关常用卡和选择功能的信息,请参阅根卡和布局视图一文。 Template Card 布局视图使用布局控件来排列卡片内的内容。对于每个网格列,都会生成一个LayoutViewField对象。切换到数据网格设…

django超市管理系统-计算机毕业设计源码53507

摘 要 随着社会经济的不断发展,超市作为零售行业的一部分,扮演着重要的角色。在信息技术的快速发展的背景下,计算机软件和硬件技术的普及应用在商业管理中起到了至关重要的作用,因此基于Django的超市管理系统应运而生,…

昇思25天学习打卡营第15天|基于MobileNetv2的垃圾分类

一、关于MobileNetv2 MobileNet网络专注于移动端、嵌入式或IoT设备的轻量级CNN网络。MobileNet网络使用深度可分离卷积(Depthwise Separable Convolution)的思想在准确率小幅度降低的前提下,大大减小了模型参数与运算量。并引入宽度系数 α和…

MySQL集群、Redis集群、RabbitMQ集群

一、MySQL集群 1、集群原理 MySQL-MMM 是 Master-Master Replication Manager for MySQL(mysql 主主复制管理器)的简称。脚本)。MMM 基于 MySQL Replication 做的扩展架构,主要用来监控 mysql 主主复制并做失败转移。其原理是将真…

解决vscode项目中无法识别宏定义的问题

在c_cpp_properties.json中的"defines":[]中定义的宏无法被识别。 从而导致代码中的宏开关无法生效,造成代码的阅读不便利。 排查路线是: 关闭所有插件,删除当前工程目录下的.vscode文件夹。 经过一系列排查发现是C/C插件与clangd插…

能把进程和线程讲的这么透彻的,没有20年功夫还真不行【0基础也能看懂】

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. 🤭🤭🤭可能说的不是那么严谨.但小编初心是能让更多人…

数据库基本查询(表的增删查改)

一、增加 1、添加信息 insert 语法 insert into table_name (列名) values (列数据1,列数据2,列数据3...) 若插入时主键或唯一键冲突就无法插入。 但如果我们就是要修改一列信息也可以用insert insert into table_name (列名) values (列数据1&am…

nginx的正向与反向代理

正向代理与反向代理的区别 虽然正向代理和反向代理都涉及代理服务器接收客户端请求并向服务端转发请求,但它们之间存在一些关键的区别: 正向代理: 在正向代理中,代理服务器代表客户端向服务器发送请求,并将服务…

【Linux】安装PHP扩展-igbinary

说明 本文档是在centos7.6的环境下,安装PHP7.4之后,安装对应的PHP扩展igbinary。 一、igbinary简述 igbinary 是一个 PHP 扩展,主要用于序列化和反序列化数据,其设计目的是为了提高序列化过程中的性能和内存效率。 优点&#…

wifi信号处理的CRC8、CRC32

🧑🏻个人简介:具有3年工作经验,擅长通信算法的MATLAB仿真和FPGA实现。代码事宜,私信博主,程序定制、设计指导。 🚀wifi信号处理的CRC8、CRC32 目录 🚀1.CRC概述 🚀1.C…

LeNet入门和Pytorch实现

1. LeNet简介 LeNet是一系列网络的合称,包括LeNet1-LeNet5,是卷积神经网络的开山之作。 文献:LeCun Y, Boser B, Denker J, et al. Handwritten digit recognition with a back-propagation network[J]. Advances in neural information pro…

鸿蒙开发:Universal Keystore Kit(密钥管理服务)【查询密钥是否存在(C/C++)】

查询密钥是否存在(C/C) HUKS提供了接口供应用查询指定密钥是否存在。 在CMake脚本中链接相关动态库 target_link_libraries(entry PUBLIC libhuks_ndk.z.so)开发步骤 构造对应参数。 指定密钥别名keyAlias,密钥别名最大长度为64字节。查询密钥需要的属性TAG&#…

DZS-12CE/S延时中间继电器 导轨安装 约瑟JOSEF

中间继电器型号: DZS-254 DZS-145 DZS-233 DZS-121 DZS-112 DZS-121 DZS-12BG DZS-12B DZS-213 DZS-234 DZS-11B/Q DZS-226 DZS-652 DZS-17E/302 DZS-12CE/S DZS-821 DZS-226 DZS-249 DZS-254G DZS-12E DZS-895 DZS-234 DZS-655G DZS-651 DZS-115 DZS-…

使用自制Qt工具配合mitmproxy进行网络调试

在软件开发和网络调试过程中,抓包工具是不可或缺的。传统的抓包工具如Fiddler或Charles Proxy通常需要设置系统代理,这会抓到其他应用程序的网络连接,需要设置繁琐的过滤,导致不必要的干扰。为了解决这个问题,我们可以…

一个引发openssl崩溃问题案例

1 背景 最近用libevent写了一个https代理功能,在调研的时候,遇到了一个项目用到了本地多个openssl库引发的ssl握手崩溃问题。 2 开发环境 项目库版本号依赖项libeventlibevent-2.1.8-stableopenssl 1.1openssl1.0u / 1.1.1w / 3.3.1...... 3 问题现象…

FlinkErr:org/apache/hadoop/hive/ql/parse/SemanticException

在flink项目中跑 上面这段代码出现如下这个异常&#xff0c; java.lang.NoClassDefFoundError: org/apache/thrift/TException 加上下面这个依赖后不报错 <dependency> <groupId>org.apache.thrift</groupId> <artifactId>libthrift</artifactId…

springmvc1

以前的servlet程序&#xff1a; springmvc 不同的处理器&#xff1a;不同的方法或者处理类 所有的请求都会经过dispathcherservlet的doservice方法&#xff1a; mvc原理&#xff1a; 前端控制器&#xff1a;jsp或者什么东西

axios以post方式提交表单形式数据

某些后端框架请求接口必须走form表单提交的那种形式&#xff0c;但前端很少有<form action"接口地址" method"post"></form>这种写法去提交表单数据&#xff0c;所以前端需要用axios模拟一个表单提交接口。 Content-Type 代表发送端&#xff0…

【Unity学习笔记】第二十 · 物理引擎脉络梳理(数值积分、碰撞检测、约束解决)

转载请注明出处: https://blog.csdn.net/weixin_44013533/article/details/139808452 作者&#xff1a;CSDN|Ringleader| 物理引擎综述 物理引擎是利用物理规则模拟物体运动和碰撞的模块&#xff0c;以在重力、弹力、摩擦力等各种力作用下做出真实运动表现&#xff0c;并对碰…

实现将Nginx的每个网站配置单独的nginx配置文件——每个网站单独管理

一、问题描述 Nginx默认地配置文件【nginx.conf】是包含了所有网站的配置内容,如果我们需要配置很多网站的话,就需要在默认的配置文件中给每个网站都添加一条server记录,这样下去nginx默认配置文件会变得很大,很难管理(比如有些网站不使用了,需要注销掉,也需要到该文件操…