架构设计-分布式ID

news2024/11/25 14:36:32

一、 分布式ID基础

1.背景

1.为什么要引用分布式主键ID?

比如单机 MySQL 数据库,前期因为业务量不大,只是使用单个数据库存数据,后期发现业务量一下子就增长,单机 MySQL 已经不能满足于现在的数据量,单机 MySQL 已经没办法支撑了,这时候就需要进行分库分表。

在分库分表之后会有一个问题, 数据发布在不同服务器上的数据库,数据库的自增主键已经没办法满足生成的主键唯一了,那样就无法作为业务的唯一标识了。如下图主键 ID 重复:

在这里插入图片描述

2.引用分布式主键ID能解决什么问题?

分表之后,不同表生成全局唯一的Id是非常棘手的问题。因为同一个逻辑表内的不同,实际表之间的自增键是无法互相感知的, 这样会造成重复Id的生成。

比如如果涉及到查询多张表进行排序等,分布式主键ID性能将更高。

二、 分布式主键ID生成的几种策略

下面是常用的策略

在这里插入图片描述

1.UUID生成分布式主键

UUID是通用唯一识别码 (Universally Unique Identifier),在其他语言中也叫GUID,可以生成一个长度32位的全局唯一识别码。

String uuid = UUID.randomUUID().toString()

结果示例:

046b6c7f-0b8a-43b9-b35d-6489e6daee91

1.UUID的缺点

由于InnoDB采用的B+Tree索引特性,UUID生成的主键插入性能较差
在这里插入图片描述

为什么无序的UUID会导致入库性能变差呢?

这就涉及到 B+树索引的分裂:

在这里插入图片描述
众所周知,关系型数据库的索引大都是B+树的结构,拿ID字段来举例,索引树的每一个节点都存储着若干个ID。

如果我们的ID按递增的顺序来插入,比如陆续插入8,9,10,新的ID都只会插入到最后一个节点当中。当最后一个节点满了,会裂变出新的节点。这样的插入是性能比较高的插入,因为这样节点的分裂次数最少,而且充分利用了每一个节点的空间。

在这里插入图片描述
但是,如果我们的插入完全无序,不但会导致一些中间节点产生分裂,也会白白创造出很多不饱和的节点,这样大大降低了数据库插入的性能。

2.数据库自增主键

假设名为table的表有如下结构:

CREATE TABLE `sequence_id` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `stub` char(10) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  UNIQUE KEY `stub` (`stub`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

stub 字段无意义,只是为了占位,便于我们插入或者修改数据。并且,给 stub 字段创建了唯一索引,保证其唯一性。

每一次生成ID的时候,访问数据库,执行下面的语句:

BEGIN;
REPLACE INTO sequence_id (stub) VALUES ('zhangsan');
SELECT LAST_INSERT_ID();
COMMIT;

REPLACE INTO 的含义是插入一条记录,如果表中唯一索引的值遇到冲突,则替换老数据。

这样一来,每次都可以得到一个递增的ID。

为了提高性能,在分布式系统中可以用DB proxy请求不同的分库,每个分库设置不同的初始值,步长和分库数量相等:

在这里插入图片描述

这样一来,DB1生成的ID是1,4,7,10,13…,DB2生成的ID是2,5,8,11,14…

1.缺点

在这里插入图片描述

3.号段模式

号段模式一般也是基于数据库自增实现分布式 ID 的一种方式,是当下分布式 ID 生成方式中比较流行的一种,其使用可以简单理解为每次从数据库中获取生成的 ID 号段范围,将范围数据获取到应用本地后,在范围内循递增生成一批 ID,然后将这批数据存入缓存。

每次应用需要获取 ID 时,这时就候就可以从缓存中读取 ID 数据,当缓存中的 ID 消耗到一定数目时候,这时再去从数据库中读取一个号段范围,再执行生成一批 ID 操作存入缓存,这是一个重复循环的过程,这样重复操作每次都只是从数据库中获取待生成的 ID 号段范围,而不是一次次获取数据库中生成的递增 ID,这样减少对数据库的访问次数,大大提高了 ID 的生成效率。

相比于数据库主键自增的方式,数据库的号段模式对于数据库的访问次数更少,数据库压力更小。

在使用号段模式时,我们通常会先建立一张表用于记录上述的 ID 号段范围,如下:

CREATE TABLE `sequence_id_generator` (
  `id` int(10) NOT NULL,
  `current_max_id` bigint(20) NOT NULL COMMENT '当前最大id',
  `step` int(10) NOT NULL COMMENT '号段的长度',
  `version` int(20) NOT NULL COMMENT '版本号记录更新的版本号,主要作用是乐观锁,每次更新时都会更新该值,以保证并发时数据的正确性',
  `biz_type`    int(20) NOT NULL COMMENT '业务类型',
   PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

每次从数据库中获取号段 ID 的范围时,都会执行更新语句,其中计算新号段范围最大值 max_id 的公式是 current_max_id+ step 组成,所以 SQL 中设置 current_max_id= current_max_id + step 来执行更新语句,更新数据库中这个范围最大值 current_max_id,然后再通过查询语句查询更新后 ID 最大值,再根据最大值 current_max_id与步长 step 计算出待生成的 ID 的范围,SQL 如下:

update `sequence_id_generator` set`current_max_id` = current_max_id+ step, `version` = version + 1 where `version` = #{执行更新的版本号} and `biz_type` = #{业务类型}

select`current_max_id`, `step`, `version` from`sequence_id_generator` where`biz_type` = #{业务类型}

实战

下面实现数据库号段模式生成 ID 过程描述:

例如,某个业务需要批量获取 ID,首先它往数据库 sequence_id_generator 中插入一条初始化值,设置 current_max_id = 0 和步长 step=100 及使用该 ID 的业务标识 biz_type=test 与版本 version=0,如下:

INSERT INTO `sequence_id_generator` (`id`, `current_max_id`, `step`, `version`, `biz_type`)
VALUES (1, 0, 100, 0, 101);

这时数据库中多了一条数据:

在这里插入图片描述
然后以 biz_type 作为筛选条件,从数据库 sequence_id_generator 中读取 current_max_id 与 step 的值:

max_id:0- step:100
通过这两个值可以知道号段范围为 (0,100),生成该批量 ID 存入到缓存中,那么这时候缓存大小为100。

每次都从缓存中取值,创建一个监听器用于监听缓存中 ID 消耗比例,设置阈值,判断如果取值超过的阈值后就进行数据库号段更新操作,比如,设置阈值为 50%,当缓存中存在 100 个 ID,监听器监听到业务应用已经消耗到 50 个,已经超过阈值,创建一个新的线程去执行更新 SQL 语句,让数据库中号段范围按照设置的 step 扩大,然后获取新的号段最大值,应用中再生成一批范围为 (101,200) 范围的 ID 存入缓存供应用使用。

整个过程是一个循环的过程,每到消耗到一定数据后就会生成新的一批。相比于数据库主键自增的方式,数据库的号段模式对于数据库的访问次数更少,数据库压力更小。

数据库号段模式生成 ID 的缺点:

存在数据库单点问题,可以使用数据库集群解决,不过增加了复杂度数据库宕机会造成整个系统不可用。ID 号码不够随机,可能够泄露发号数量的信息,不太安全

4.snowflake雪花算法

JDBC接口来实现对于生成Id的访问,而将底层具体的Id生成实现分离出来。如Sharding-JDBC使用分布式ID。

5.Redis 实现分布式 ID

Redis由于是单线程模型,所以对redis的读写操作都是线程安全的,所以可以用它来保证分布式场景下的分布式ID唯一。

Redis 中存在原子操作指令 INCR 或 INCRBY,执行后可用于创建初始化值或者在原有数字基础上增加指定数字,并返回执行 INCR 命令之后 key 的值,这样就可以很方便的创建有序递增的 ID。

1、INCR: 将 key 中储存的数字 +1,如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。

2、INCRBY: 将 key 中储存的数字加上指定的增量值,如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。

Redis 生成 ID 示例:

127.0.0.1:6379> set sequence_id_biz_type 1
OK
127.0.0.1:6379> incr sequence_id_biz_type
(integer) 2
127.0.0.1:6379> get sequence_id_biz_type
"2"


使用 Redis 单机生成 ID 存在性能瓶颈,无法满足高并发的业务需求,且一旦 Redis 崩溃或者服务器宕机,那么将导致整个基于它的服务不可用,这是业务中难以忍受的。

为了提高可用性和并发,我们可以使用 Redis Cluser。Redis Cluser 是 Redis 官方提供的 Redis 集群解决方案。

除了 Redis Cluser 之外,你也可以使用开源的 Redis 集群方案Codis (大规模集群比如上百个节点的时候比较推荐)。

除了高可用和并发之外,我们知道 Redis 基于内存,我们需要持久化数据,避免重启机器或者机器故障后数据丢失。Redis 支持两种不同的持久化方式:快照(snapshotting,RDB)、只追加文件(append-only file, AOF)。并且,Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 aof-use-rdb-preamble 开启)。关于 Redis 持久化就不多说了,这个不是重点。

1.Redis 生成分布式 ID 的缺点:

1、增加了程序的复杂度,和硬件资源

2、如果 Rdeis 宕机,则服务不可用

3、可能达到Rdeis的性能瓶颈,则需要部署多台,使用步长方式

4、持久化问题,若使用RDB持久化策略则可能在最后一次持久化之前发生宕机则恢复后可能发生

5、ID重复问题,若使用AOF持久化策略则在恢复Redis时需要较长时间
 

2.针对redis 不可用或者key失效的解决方案

当key如果失效了,则从DB数据库中取最大值,然后再放进到数据库中。

当redis不可用,则从DB中取最大值,然后基于服务内存运算,但这样会引进新的缓存击穿和分布式并发问题而导致数据不唯一

参考资料

1.漫画:什么是SnowFlake算法?https://blog.csdn.net/bjweimengshu/article/details/80162731
2.为什么需要分布式 ID,在项目中该怎么做?https://blog.csdn.net/wuhuayangs/article/details/125203180

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

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

相关文章

【实战项目开发技术分享】常用的ROS工具与命令

常用的ROS工具介绍 作为机器人领域最为流行的开源框架之一,ROS(Robot Operating System)提供了丰富的工具来支持机器人的开发和部署。下面是一些常用的ROS工具及其功能的介绍: roscore:roscore是ROS的主要进程,它启动ROS Master节点并协调ROS系统中的所有进程。所有ROS节…

NEFU数据库基本操作实验总结

前言 SQL语言集数据定义、数据操纵、数据查询、数据控制功能于一体。 数据定义:create,drop,alter 数据操纵:增(insert into … ),删(delete from …),改(update … set …) 数据查询:select 数…

如何在 Linux 环境下安装使用 pycharm?介绍一种快捷打开方式

之所以要出这个教程,是想介绍一种更快捷的打开方式。官网上的教程,每次打开都要切换到下载地址,然后输入 ./pycharm.sh 才能运行。 如图,在任意位置打开终端,敲快捷键pc就能打开 pycharm 怎么样,非常方便…

MYSQL进阶01

MYSQL进阶 存储引擎存储引擎的特性MyISAMInnoDBMEMORY 如何选择合适的引擎 存储引擎 MYSQL默认支持多种存储引擎,可以根据用户不同需求选择合适的储存引擎。MYSQL支持的存储引擎包括但不局限于以下几种(MyISAM、InnoDB、MEMORY、MERGE…,创建…

【Leetcode -142.环形链表Ⅱ -143.重排链表】

Leetcode Leetcode -142.环形链表ⅡLeetcode - 143.重排链表 Leetcode -142.环形链表Ⅱ 题目:给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 如果链表中有某个节点,可以通过连续跟踪 …

CodeRush Ultimate Crack终极版

CodeRush Ultimate Crack终极版 CodeRush Ultimate通过提高生产力,同时消除侵蚀创造力的重复,帮助开发人员在更短的时间内提供更多高质量的功能。它可以帮助您在几秒钟内创建复杂的代码块,并立即扩展代码模板,并在您按逻辑键入、扩…

[架构之路-179]-《软考-系统分析师》-19- 系统可靠性分析与设计 - 故障模型、可靠性模型、可靠性分析

目录 前言: 1 9 . 1 系统可靠性概述 19.1.1 系统故障模型 1. 在信息系统中,故障或错误有如下儿种表现形式: 2. 故障的缘由 3. 故障模型 (1)逻辑电路级的故障 (2) 数据结构级的故障 &a…

spass modeler

课时1:SPSS Modeler 简介 本课时一共分为五个模块,分别是Modeler概述、工具安装、窗口说明以及功能介绍和应用案例。相信通过本课时内容的学习,大家将会对SPSS Modeler有个基础的了解. 在学习本节课内容之前,先来看看本节课我们究…

Wi-Fi (-6) 知识点整理

Wi-Fi - 6 知识点整理 2019年IEEE推出IEEE 802.11ax,WiFi联盟(WFA)称其为WiFi-6 Wi-Fi (Wireless Fidelity) 无线保真,无线兼容性认证 通信技术 商标 商业认证 技术联盟 Wi-Fi 网络基本要素 接入点(Access Point&a…

前端存储二:indexedDB

indexedDB 特点:以域名纬度,浏览器大量结构化数据存储方案,运行在浏览器的非关系型数据库。 大小:不会小于 250MB,支持二进制存储。 接口:异步接口,支持事物机制 这里使用网页脚本生成&#x…

【五一创作】【Simulink】采用延时补偿的三相并网逆变器FCS-MPC

👉 【Simulink】基于FCS-MPC的三相并网逆变器控制 上一篇博客介绍了FCS-MPC的基本操作,并且以三相并网逆变器为控制对象进行了Simulink仿真。 但实际仿真中没有考虑补偿延时。本篇博客将讨论为什么要考虑延时并进行补偿,最后对此仿真验证。 …

参会记录|全国多媒体取证暨第二届多媒体智能安全学术研讨会(MAS‘2023)

前言:2023年4月8日上午,我与实验室的诸位伙伴们共聚浙江杭州西子湖畔的六通宾馆,参加了为期一天半的全国多媒体取证暨第二届多媒体智能安全学术研讨会(MAS’2023)。本届学术研讨会由浙江省自然科学基金委员会资助&…

3.2 静态随机存取存储器

学习目标: 学习静态随机存取存储器(SRAM)的基本原理、结构和工作方式,理解其与动态随机存取存储器(DRAM)的区别和优缺点,掌握SRAM的性能参数和应用领域,了解SRAM的发展历程和未来趋…

三元操作 三元操作符 if-else / ? :

Python 三元操作符 if-else , 其他语言三元操操作符 ? : 。 【学习的细节是欢悦的历程】 Python 官网:https://www.python.org/ Free:大咖免费“圣经”教程《 python 完全自学教程》,不仅仅是基础那么简单……地址:h…

【Java】类和对象,封装

目录 1.类和对象的定义 2.关键字new 3.this引用 4.对象的构造及初始化 5.封装 //包的概念 //如何访问 6.static成员 7.代码块 8.对象的打印 1.类和对象的定义 对象:Java中一切皆对象。 类:一般情况下一个Java文件一个类,每一个类…

深度学习的定义和未来发展趋势

深度学习的定义和未来发展趋势 什么是深度学习数学和编程的基础知识深度学习的应用领域深度学习的常见算法和模型训练深度学习模型深度学习的未来 🏘️🏘️个人简介:以山河作礼。 🎖️🎖️:Python领域新星创作者&#…

Linux [常见指令 (1)]

Linux常见指令 ⑴ 1. 操作系统1.1什么事操作系统1.2选择指令的原因 2.使用工具3.Linux的指令操作3.1mkdir指令描述:用法:例子 mkdir 目录名例子 mkdir -p 目录1/ 目录2/ 目录3 3.2 touch指令描述:用法:例子 touch 文件 3.2pwd指令描述:用法:例子 pwd 3.4cd指令描述:用法:例子 c…

SQL语句截取字段某指定字符的前半段/后半段内容

最近项目中遇到一个小问题: 需要从数据库中取出对应数据,并根据某个字段中的前半段内容进行排序,搜索资料后得以解决,现将解决方法记录如下: 最初的查询SQL: SELECT file_name,sort FROM base_annexesfil…

Linux守护进程(Daemon Process)

1. 守护进程概念: 独立于终端控制并周期性地执行处理某些任务的后台进程。 2. 守护进程创建步骤: 核心:让进程脱离控制终端→创建新会话。 (1)创建子进程,父进程退出(必须)&#x…

LVS +Keepalived 高可用群集部署

一、LVSKeepalived 高可用群集 在这个高度信息化的 IT 时代,企业的生产系统、业务运营、销售和支持,以及日常管理等环节越来越依赖于计算机信息和服务,对高可用(HA)技术的应用需求不断提高,以便提供持续的…