当前位置: 首页 > news >正文

第4章唯一ID生成器——4.2 单调递增的唯一ID

唯一ID生成器本身也是一个服务,为了生成单调递增的唯一ID,这个服务需要使用某种存储系统记录可分配的唯一ID。Redis和其他数据库都可以达到这个目的。

4.2.1 Redis INCRBY 命令

Redis提供的INCRBY命令可以为键(Key)的数字值加上指定的增量(increment)。如果键不存在,则其数字值被初始化为0,然后执行增量操作。使用INCRBY命令限制的值类型为64位有符号整数,此命令的特性与单调递增的唯一ID的诉求非常契合。基于Redis INCRBY命令实现的唯一ID生成器的Go语言代码非常简单:

func GenID() (int64, error) {// 执行 Redis 命令:INCRBY seq_id 1cmd := rdb.IncrBy(context.TODO(), "seq_id", 1)if cmd.Err() != nil {return 0, cmd.Err()}return cmd.Val(), nil
}

每当生成唯一ID的请求到来时,唯一ID生成器都对同一个键seq_id执行一次加1的增量操作,并将键的值作为唯一ID返回,这样就可以保证ID生成器生成的ID是唯一且单调递增的。

由于Redis具备高性能,且INCRBY命令执行的时间复杂度是O(1),所以基于Redis INCRBY命令实现的唯一ID生成器性能表现很好,不过还有优化的空间。

  • 唯一ID生成器服务可以每次从Redis中批量获取ID并存储到本地内存中,当业务服务请求到来时,直接从本地内存返回最小可用的ID。

  • 如果本地内存中没有可用的ID,则再次从Redis中批量获取。

这个优化方案的整体架构和流程如图4-5所示。

image-20250321191504620

唯一 ID生成器的Go语言代码实现如下:

// 唯一工D生成器服务结构

唯一ID生成器服务从Redis中批量获取ID的方式,不仅可以进一步提高服务的性能,而且可以降低由于网络抖动而导致Redis访问超时所带来的影响。不过,Redis主要用作缓存,它并不具有严格的数据持久化能力。如果最新的唯一ID数据丢失,则很容易生成重复的ID,给业务服务带来难以估计的风险。我们先来看一个Redis实例宕机的例子。

  1. T1时刻seq_id键的值为1000, Redis已将数据持久化到RDB文件和AOF文件中。
  2. T2时刻唯一ID生成器服务从Redis中获取10个ID,得到1001~1010,seq_id键的最新值变为1010。
  3. T3时刻Redis实例宕机,seq_id键的最新值还未来得及被持久化。
  4. T4时刻Redis重启,Redis使用RDB文件和AOF文件重建内存数据,seq_id键的值依然是1000。
  5. T5时刻唯一ID生成器服务从Redis中获取10个ID,再次得到1001~1010。

如果采用Redis主从架构呢?依然无法彻底解决ID重复的问题,因为Redis主从复制是异步的,即Redis主节点无法确定从节点是否已经复制了某数据。假设唯一ID生成器服务从Redis主节点获取了 1001-1010的ID后主节点发生宕机,由于seq_id键的最新值尚未被复制到从节点,从节点上seq_id键的值依然是1000;如果此时从节点被提升为主节点,那么下一次唯一ID生成器服务从其获取的10个ID依然是1001-1010,即生成了重复的ID。

4.2.2 基于数据库的自增主键

生成单调递增的唯一ID的另一种方式是基于数据库的自增主键。首先在数据库中创建数据表seq_id:

create table seq_id (
id bigint(20) unsigned NOT NULL auto_increment,
col tinyint NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB;

其中,id字段使用auto_increment声明为自增主键。首次向seq_id数据表中插入数据后,该数据主键被设置为1,之后每次插入新数据时,数据主键都会以1的增量自增;col字段只是为了方便插入数据,没有特殊含义。唯一ID生成器服务先在seq_id数据表中插入一条数据,然后读取此数据的主键并将其作为唯一ID返回。使用Go语言代码实现如下:

func GenID() (int64, error) {// 执行 SQL 语句:INSERT INTO seq_id (col) VALUES 0result, err := db.Exec("INSERT INTO seq_id(col) VALUES (?)", 0)if err != nil {return 0, err}lastInsertID, err := result.LastInsertId()	// 获取所插入数据的主键return lastInsertID, err
}

基于数据库的自增主键插入数据生成唯一ID的性能不高,我们同样可以将其改进为采用4.2.1节介绍的批量生成ID的方式。唯一ID生成器服务的代码实现与Redis方案类似,只是批量生成ID的函数不一样:

// 从数据库中批量获取工D
func (svr *IdGeneratorService) multiGenlDFromDB(count int64) error {}

为了保证高可用性,数据库采用主从架构,同时主从数据复制采用半同步复制或MGR(MySQL组复制)的机制,这样每次将新数据插入数据库主节点时,主节点都会将新数据同步复制到从节点。如果数据库主节点宕机,那么从节点可以立刻代替主节点对外提供服务,唯一 ID生成器服务不会因为访问从节点而生成重复的ID。

4.2.3 高可用架构

需要注意的是,如果批量生成唯一ID的生成器服务有多个实例对外提供服务,则无法生成单调递增的唯一ID。如图4-6所示,假设唯一ID生成器服务初次启动并有两个实例接收业务请求。

image-20250321192643130

  1. T1时刻实例A收到请求1,于是从数据库中批量获取10个唯一 ID,即1-10,将其缓存到本地,然后为请求1返回ID=1。
  2. T2时刻实例B收到请求2,也从数据库中批量获取10个唯一ID,即11-20,将其缓存到本地,然后为请求2返回ID=11。
  3. T3时刻实例A收到请求3,于是返回本地可用的ID=2。
  4. T4时刻实例B收到请求4,于是返回本地可用的ID=12。

唯一ID生成器服务依次生成的ID是不满足单调递增的1、11、2、12,而是趋势递增的,所以此服务在任何时刻只能有一个实例对外提供服务。而服务只有一个实例,则意味着这个服务的可用性较差。为了提高唯一ID生成器服务的可用性,可以增加一个备用实例:此实例日常不对外提供服务,仅当工作实例宕机后,它才接替工作实例处理业务请求。最终的唯一ID生成器服务的架构如图4-7所示。

image-20250321192844738

无论是否采用批量获取ID的思路,单调递增的唯一ID生成器都始终无法支持高并发访问,这是单调递增的唯一ID不被广泛使用的最主要原因。

  • 如果不批量获取ID,则意味着每个业务请求都会写数据库,数据库难以承受高并发写入操作,性能表现不佳,甚至可能会被击垮;
  • 如果批量获取ID,虽然数据库访问量级降低了,但是ID生成器服务只能有一个工作实例,单实例所能承载的并发量级非常有限,服务失去了可扩展性。

相比之下,唯一ID生成器能被广泛接受的实现方式是生成趋势递增的唯一ID。

总结

生成单调递增的唯一ID有哪些方法?

  1. Redis INCRBY命令
  2. 基于数据库的自增主键

为什么单调递增的唯一ID生成器不被广泛使用呢?

  • 核心原因:单调递增的唯一ID生成器无法支持高并发访问。
  • 如果不批量获取ID,则意味着每个业务请求都会写数据库,数据库难以承受高并发写入操作,性能表现不佳,甚至可能会被击垮;
  • 如果批量获取ID,虽然数据库访问量级降低了,但是ID生成器服务只能有一个工作实例,单实例所能承载的并发量级非常有限,服务失去了可扩展性。
http://www.dtcms.com/a/301893.html

相关文章:

  • 【Android】卡片式布局 滚动容器ScrollView
  • Go语法入门:变量、函数与基础数据类型
  • 飞算科技重磅出品:飞算 JavaAI 重构 Java 开发效率新标杆
  • JAVA后端开发——用 Spring Boot 实现定时任务
  • 【Spring】Spring Boot启动过程源码解析
  • 鸿蒙打包签名
  • HarmonyOS 6 云开发-用户头像上传云存储
  • 前端工程化常见问题总结
  • Windows|CUDA和cuDNN下载和安装,默认安装在C盘和不安装在C盘的两种方法
  • AI技术革命:产业重塑与未来工作范式转型。
  • 深入解析MIPI C-PHY (四)C-PHY物理层对应的上层协议的深度解析
  • 齐护Ebook科技与艺术Steam教育套件 可图形化micropython Arduino编程ESP32纸电路手工
  • 湖南(源点咨询)市场调研 如何在行业研究中快速有效介入 起头篇
  • Triton编译
  • 【n8n教程笔记——工作流Workflow】文本课程(第一阶段)——5.5 计算预订订单数量和总金额 (Calculating booked orders)
  • Rouge:面向摘要自动评估的召回导向型指标——原理、演进与应用全景
  • 分表分库与分区表
  • Android启动时间优化大全
  • 蛋白质反向折叠模型-ProteinMPNN安装教程
  • 学习日志20 python
  • 【unitrix】 6.18 二进制小数特质(t_decimal.rs)
  • EPOLLET 边缘触发模式深度解析
  • 抗辐照芯片在低轨卫星星座CAN总线通讯及供电系统的应用探讨
  • vue3的一些浅显用法
  • Day06–哈希表–242. 有效的字母异位词,349. 两个数组的交集,202. 快乐数,1. 两数之和
  • 浙大公开课—基于深度学习的特征匹配与姿态估计
  • (补题)拼图游戏
  • EPOLLIN事件的详细解析
  • 【时时三省】(C语言基础)指针数组和多重指针
  • MySQL 8.4 Windows 版安装记录与步骤参考