存储拆分后,如何解决唯一主键问题?

之前我们讲到了分库分表,现在考虑这样一个问题:在单库单表时,业务 ID 可以依赖数据库的自增主键实现,现在我们把存储拆分到了多处,如果还是用数据库的自增主键,势必会导致主键重复。

那么我们应该如何解决主键问题呢?本文就来看下生成唯一主键相关的知识。

生成主键有哪些方案

如果用最简单的方式来生成唯一主键,可以怎么做呢?一个最直接的方案是使用单独的自增数据表,存储拆分以后,创建一张单点的数据表,比如现在需要生成订单 ID,我们创建下面一张数据表:

CREATE TABLE IF NOT EXISTS `order_sequence`(
   `order_id` INT UNSIGNED AUTO_INCREMENT,
   PRIMARY KEY ( `order_id` )
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

当每次需要生成唯一 ID 时,就去对应的这张数据表里新增一条记录,使用返回的自增主键 ID 作为业务 ID。

这个方案实现起来简单,但问题也很明显。首先,性能无法保证,在并发比较高的情况下,如果通过这样的数据表来创建自增 ID,生成主键很容易成为性能瓶颈。第二,存在单点故障,如果生成自增 ID 的数据库挂掉,那么会直接影响创建功能。

在实际开发中,实现唯一主键有多种方案可选,下面介绍几种常见的实现思路,分别是使用 UUID、使用 Snowflake 算法,以及配置自增区间在内存中分配的方式。

使用 UUID 能否实现

UUID 大家都很熟悉,在 Java 语言中,就内置了 UUID 的工具类实现,可以很容易地生成一个 UUID:

public String getUUID(){
   UUID uuid=UUID.randomUUID();
   return uuid.toString();
}

那么是否可以应用 UUID 生成唯一主键呢?

UUID 虽然很好地满足了全局唯一这个要求,但是并不适合作为数据库存储的唯一主键。我们输出一个 UUID 看一下,比如:135c8321-bf10-46d3-9980-19ba588554e8,这是一个 36 位的字符串。

首先 UUID 作为数据库主键太长了,会导致比较大的存储开销,另外一个,UUID 是无序的,如果使用 UUID 作为主键,会降低数据库的写入性能。

以 MySQL 为例,MySQL 建议使用自增 ID 作为主键,我们知道 MySQL InnoDB 引擎支持索引,底层数据结构是 B+ 树,如果主键为自增 ID 的话,那么 MySQL 可以按照磁盘的顺序去写入;如果主键是非自增 ID,在写入时需要增加很多额外的数据移动,将每次插入的数据放到合适的位置上,导致出现页分裂,降低数据写入的性能。

基于 Snowflake 算法

Snowflake 是 Twitter 开源的分布式 ID 生成算法,由 64 位的二进制数字组成,一共分为 4 部分,下面是示意图:

image (6).png

其中:

  • 第 1 位默认不使用,作为符号位,总是 0,保证数值是正数;

  • 41 位时间戳,表示毫秒数,我们计算一下,41 位数字可以表示 241 毫秒,换算成年,结果是 69 年多一点,一般来说,这个数字足够在业务中使用了;

  • 10 位工作机器 ID,支持 210 也就是 1024 个节点;

  • 12 位序列号,作为当前时间戳和机器下的流水号,每个节点每毫秒内支持 212 的区间,也就是 4096 个 ID,换算成秒,相当于可以允许 409 万的 QPS,如果在这个区间内超出了 4096,则等待至下一毫秒计算。

Twitter 给出了 Snowflake 算法的示例,具体实现应用了大量的位运算,可以点击具体的代码库查看。

Snowflake 算法可以作为一个单独的服务,部署在多台机器上,产生的 ID 是趋势递增的,不需要依赖数据库等第三方系统,并且性能非常高,理论上 409 万的 QPS 是一个非常可观的数字,可以满足大部分业务场景,其中的机器 ID 部分,可以根据业务特点来分配,比较灵活。

Snowflake 算法优点很多,但有一个不足,那就是存在时钟回拨问题,时钟回拨是什么呢?

因为服务器的本地时钟并不是绝对准确的,在一些业务场景中,比如在电商的整点抢购中,为了防止不同用户访问的服务器时间不同,则需要保持服务器时间的同步。为了确保时间准确,会通过 NTP 的机制来进行校对,NTP(Network Time Protocol)指的是网络时间协议,用来同步网络中各个计算机的时间。

如果服务器在同步 NTP 时出现不一致,出现时钟回拨,那么 SnowFlake 在计算中可能出现重复 ID。除了 NTP 同步,闰秒也会导致服务器出现时钟回拨,不过时钟回拨是小概率事件,在并发比较低的情况下一般可以忽略。关于如何解决时钟回拨问题,可以进行延迟等待,直到服务器时间追上来为止,感兴趣的同学可以查阅相关资料了解下。

数据库维护区间分配

下面我们介绍一种基于数据库维护自增ID区间,结合内存分配的策略,这也是淘宝的 TDDL 等数据库中间件使用的主键生成策略。

使用这种方式的步骤如下。

  • 首先在数据库中创建 sequence 表,其中的每一行,用于记录某个业务主键当前已经被占用的 ID 区间的最大值。

sequence 表的主要字段是 name 和 value,其中 name 是当前业务序列的名称,value 存储已经分配出去的 ID 最大值。

CREATE TABLE `sequence` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'Id',
  `name` varchar(64) NOT NULL COMMENT 'sequence name',
  `value` bigint(32) NOT NULL COMMENT 'sequence current value',
   PRIMARY KEY (`id`),
  UNIQUE KEY `unique_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 
  • 接下来插入一条行记录,当需要获取主键时,每台服务器主机从数据表中取对应的 ID 区间缓存在本地,同时更新 sequence 表中的 value 最大值记录。

现在我们新建一条记录,比如设置一条 order 更新的规则,插入一行记录如下:

INSERT INTO sequence (name,value) values('order_sequence',1000);i

当服务器在获取主键增长区段时,首先访问对应数据库的 sequence 表,更新对应的记录,占用一个对应的区间。比如我们这里设置步长为 200,原先的 value 值为 1000,更新后的 value 就变为了 1200。

  • 取到对应的 ID 区间后,在服务器内部进行分配,涉及的并发问题可以依赖乐观锁等机制解决。

有了对应的 ID 增长区间,在本地就可以使用 AtomicInteger 等方式进行 ID 分配。

不同的机器在相同时间内分配出去的 ID 可能不同,这种方式生成的唯一 ID,不保证严格的时间序递增,但是可以保证整体的趋势递增,在实际生产中有比较多的应用。

为了防止单点故障,sequence 表所在的数据库,通常会配置多个从库,实现高可用。

除了上面的几种方案,实际开发中还可以应用 Redis 作为解决方案,即通过 Redis Incr 命令来实现,感兴趣的同学可以去了解一下。

总结

本文主要分享了实现唯一主键的几种思路,也就是我们通常说的分布式发号器,主要有使用 UUID、使用 Snowflake 算法,以及数据库存储区间结合内存分配的方式。

现在再来总结一下,一个生产环境中可用的主键生成器,应该具备哪些特性呢?

首先是生成的主键必须全局唯一,不能出现重复 ID,这对于主键来说是最基础的需求。

第二,需要满足有序性,也就是单调递增,或者也可以满足一段时间内的递增,这是出于业务上的考虑。一方面在写入数据库时,有序的主键可以保证写入性能,另一方面,很多时候都会使用主键来进行一些业务处理,比如通过主键排序等。如果生成的主键是乱序的,就无法体现一段时间内的创建顺序。

再一个是性能要求,要求尽可能快的生成主键,同时满足高可用。因为存储拆分后,业务写入强依赖主键生成服务,假设生成主键的服务不可用,订单新增、商品创建等都会阻塞,这在实际项目中是绝对不可以接受的。

你可以联系实际工作,在你负责的项目中涉及唯一主键的模块,是否也考虑了这些特性,以及具体是如何实现的,欢迎留言分享。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

普通二叉树和右倾斜二叉树--LeetCode 111题《Minimum Depth of Binary Tree》

本文将以解释计算二叉树的最小深度的思路为例,致力于用简洁易懂的语言详细描述普通二叉树和右倾斜二叉树在计算最小深度时的区别。通过跟随作者了解右倾斜二叉树的概念以及其最小深度计算过程,读者也将对左倾斜二叉树有更深入的了解。这将为解决LeetCode…

Leaflet.Graticule源码分析以及经纬度汉化展示

目录 前言 一、源码分析 1、类图设计 2、时序调用 3、调用说明 二、经纬度汉化 1、改造前 2、汉化 3、改造效果 总结 前言 在之前的博客基于Leaflet的Webgis经纬网格生成实践中,已经深入介绍了Leaflet.Graticule的实际使用方法和进行了简单的源码分析。认…

Python【Matplotlib】图例可拖动改变位置

代码: import matplotlib.pyplot as plt from matplotlib.widgets import Button# 创建一个示例图形 fig, ax plt.subplots() line, ax.plot([1, 2, 3], labelLine 1)# 添加图例 legend ax.legend(locupper right, draggableTrue)# 添加一个按钮,用于…

媒体直播平台有哪些,活动直播如何扩大曝光?

传媒如春雨,润物细无声,大家好,我是51媒体网胡老师。 媒体直播平台包括人民视频、新华社现场云、中国网、新浪新闻直播、搜狐视频直播、凤凰新闻直播、腾讯新闻直播等。活动直播想要扩大曝光,可以考虑以下方式: 1.选择…

【深度学习】TensorFlow深度模型构建:训练一元线性回归模型

文章目录 1. 生成拟合数据集2. 构建线性回归模型数据流图3. 在Session中运行已构建的数据流图4. 输出拟合的线性回归模型5. TensorBoard神经网络数据流图可视化6. 完整代码 本文讲解: 以一元线性回归模型为例, 介绍如何使用TensorFlow 搭建模型 并通过会…

数据泄露警报:不同行业危机解析与迅软DSE的拯救之道

在如今全球信息数字化不断加速的时代里,数据资料的价值更为突出,根据IBM数据显示,数据泄露的平均成本接近440万美元。一旦泄露可能意味着丢失信息、声誉受损,并可能导致延误和生产力损失。那么不同行业一旦发生了数据泄露将会面临…

Linux部署MySQL5.7和8.0版本 | CentOS和Ubuntu系统详细步骤安装

一、MySQL数据库管理系统安装部署【简单】 简介 MySQL数据库管理系统(后续简称MySQL),是一款知名的数据库系统,其特点是:轻量、简单、功能丰富。 MySQL数据库可谓是软件行业的明星产品,无论是后端开发、…

Redis——02,redis-benchmark 性能测试

redis-benchmark 性能测试 一、benchmark 性能测试。二、参数详解: 一、benchmark 性能测试。 在bin目录下,有一个redis-benchmark 工具,是用来测试性能的。 二、参数详解: http://doc.yaojieyun.com/www.runoob.com/redis/re…

VMP泄露编译的一些注意事项

VMP编译教程 鉴于VMP已经在GitHub上被大佬强制开源,特此出一期编译教程。各位熟悉的可以略过,不熟悉的可以参考一下。 环境(软件) Visual Studio 2015 - 2022 (建议使用VS2019,Qt插件只有这个版本及以上…

Python等比例缩放图片并修改对应的Labelme标注文件(v2.0)

Python等比例缩放图片并修改对应的Labelme标注文件(v2.0) 前言前提条件相关介绍实验环境Python等比例缩放图片并修改对应的Labelme标注文件Json文件代码实现输出结果 前言 此版代码,相较于Python等比例缩放图片并修改对应的Labelme标注文件&a…

原子学习笔记1——阻塞和非阻塞IO

阻塞式 I/O 顾名思义就是对文件的 I/O 操作(读写操作)是阻塞式的,非阻塞式 I/O 同理就是对文件的I/O 操作是非阻塞的。 当对文件进行读操作时,如果数据未准备好、文件当前无数据可读,那么读操作可能会使调用者阻塞&…

编程实际应用实例:洗车店会员管理系统操作教程

一、前言 洗车店在会员管理有时候需要一卡多用,基本也不需要做卡,直接报手机号或车牌号即可完成电子会员卡录入。 下面以 佳易王洗车店会员管理系统软件为例说明, 软件试用版下载或技术支持可以点击下方的官网卡片 如图:这个卡…

[HCTF 2018]WarmUp (代码审计)

打开题目: 好好好。 看看源码: ? source.php 让我看看! 发现还有个文件叫hint,php 看看: 得到目的文件是ffffllllaaaagggg 分析代码: $_REQUEST 变量 $_REQUEST用于收集HTML表单提交的数据&#x…

迅为RK3568开发板使用OpenCV处理图像-ROI区域-位置提取ROI

在图像处理过程中,我们可能会对图像的某一个特定区域感兴趣,该区域被称为感兴趣区域(Region of Interest, ROI)。在设定感兴趣区域 ROI 后,就可以对该区域进行整体操作。 位置提取 ROI 本小节代码在配套资料“iTOP-3…

RocketMQ系统性学习-RocketMQ领域模型及Linux下单机安装

MQ 之间的对比 三种常用的 MQ 对比,ActiveMQ、Kafka、RocketMQ 性能方面: 三种 MQ 吞吐量级别为:万,百万,十万消息发送时延:毫秒,毫秒,微秒可用性:主从,分…

【深度学习】机器学习概述(一)机器学习三要素——模型、学习准则、优化算法

​ 文章目录 一、基本概念二、机器学习的三要素1. 模型a. 线性模型b. 非线性模型 2. 学习准则a. 损失函数1. 0-1损失函数2. 平方损失函数(回归问题)3. 交叉熵损失函数(Cross-Entropy Loss)4. Hinge 损失函数 b. 风险最小化准则1.…

MQTT 介绍与学习 —— 筑梦之路

之前写过的相关文章: MQTT协议(转载)——筑梦之路_mqtt url-CSDN博客 k8s 部署mqtt —— 筑梦之路-CSDN博客 CentOS 7 搭建mqtt服务——筑梦之路_腾讯云宝塔搭 centos 7.9.2009 x86_64 建标准mqtt服务器-CSDN博客 mqtt简介 MQTT&#xff…

tcp/ip协议2实现的插图,数据结构5 (22 - 章)

(103) 103 二二1 协议控制块 结构 file, socket , rawcb , inpcb , tcpcb 之间的联系 (104) (105)

Python:如何将MCD12Q1\MOD11A2\MOD13A2原始数据集批量输出为TIFF文件(镶嵌/重投影/)?

博客已同步微信公众号:GIS茄子;若博客出现纰漏或有更多问题交流欢迎关注GIS茄子,或者邮箱联系(推荐-见主页). 00 前言 之前一段时间一直使用ENVI IDL处理遥感数据,但是确实对于一些比较新鲜的东西IDL并没有python那么好的及时性&…

【Linux】使用官方脚本自动安装 Docker(Ubuntu 22.04)

前言 Docker是一种开源平台,用于开发、交付和运行应用程序。它利用了容器化技术,使开发人员能够将应用程序及其依赖项打包到一个称为Docker容器的可移植容器中。这些容器可以在任何运行Docker的机器上快速、一致地运行,无论是开发环境、测试…