分布式ID生成器的剖析与设计

本文涉及的产品
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
简介: ID是身份标识,你所涉及的每类业务都会有一个,身份证, 手机号, QQ号。那么问题来了,如何设计一个算法,能快速生成ID又能有效地避免冲突。 往小了说,在存储领域每一行数据都会有一个ID,关系型数据库有 auto increment, 非关系型数据库,如mongodb有自己的objectID 算法。 对于各种ID我们可以简化为2类: 1.去中心化,统一长度,规则占坑类, mongodb属于这

ID是身份标识,你所涉及的每类业务都会有一个,身份证, 手机号, QQ号。那么问题来了,如何设计一个算法,能快速生成ID又能有效地避免冲突。
往小了说,在存储领域每一行数据都会有一个ID,关系型数据库有 auto increment, 非关系型数据库,如mongodb有自己的objectID 算法。
对于各种ID我们可以简化为2类:
1.去中心化,统一长度,规则占坑类, mongodb属于这一类, guid 属于这类类。
2.中心化,ID自增,auto increment属于这一类。

mongodb id

mongodb的ID规则

长度(byte) 含义
4 unix epoch到当前的秒数
3 机器标识
2 进程标识
3 扩展计数

机器标识可以防止不同机器出现相同ID, 进程标识则可以在同一台机器上防止出现冲突。
对于这种设计, 我想说 good! 无论是单机还是分布式都可以很有效地解决冲突的问题。
image.png

即使同一时刻有很多请求,不同机器也不会产生相同的ID,达到了去中心化的ID生成能力。

类似的设计还有twitter snowflake:

长度(bit) 含义
41 unix epoch到当前的毫秒数
10 节点ID
12 sequence

也做到了不同节点去重,不同时间有序,只是把节点ID的制定交给了使用者。

mysql auto seq
相信大部分研发同学都使用过mysql,auto increment可以有效地解决ID生成。

CREATE TABLE animals (
     id MEDIUMINT NOT NULL AUTO_INCREMENT,
     name CHAR(30) NOT NULL,
     PRIMARY KEY (id)
);

id会从1开始自增,所以单台Server的情况下是不会有冲突的可能的。
image.png

这样的设计一度非常常见于各种前端技术,用过LAMP技术栈的同学应该记得,目前仍被mysql, redis等广泛使用。

引申

第一种方案是于空间时间相关的,类似于身份证
image.png

把坑给你空好,生成机制设计好,生成ID就变成了按机制生成数字填坑, 只要时间不停摆,人口不会短时间内爆增,这个ID就很难重复。

等于把冲突保障交给了系统。

第二种方案与时空无关,只要生成器还在,就可以一直生产数据,冲突由生成器保障。

问题抛出

系统不可靠

以mongodb为例

长度(byte) 含义 依赖
4 unix epoch到当前的秒数 系统时间
3 机器标识
2 进程标识
3 扩展计数

如果因为操作不当,人为把机器的当前时间修改成过去某个时间,其新生成ID的时间部分就有可能冲突。

image.png

举例来说,某一年的公历出现了润8月(不可能出现),则8月出生的小朋友就要身份证重号了,现终于知道为什么身份证不使用农历了吧,哟呵呵。

自增的瓶颈

自增形式的生成器很可能成为瓶颈,争对单点的问题业界已经有一些方案,比如分片自增

有3个节点 p1, p2, p3, 生成的ID规则:

p1: 1,4,7,10,13,16,19,22

p2: 2,5,8,11,14,17,20,23

p3:3,6,9,12,15,18,21,24

image.png

既使节点A崩溃了,只要有一个节点还在,系统就能工作。

但是不太好扩展,如果一开始只分配了3台机器,后面系统达到瓶颈了添加机器,就比较麻烦了。

ID的意义

在分布式系统中,ID往往有很多意义,比如散列,如何精确地的把按ID的查询散列到某台机器上去,因此在一些场景下ID需要包含机器信息,第一类去中心化ID生成器很好的解决了此类问题。

另外,有时候需要从ID知道系统的数据规模,自增的ID能较好的解决此类问题。

从安全角度上看,不同场景下ID应该有不同的表述,避免被外部猜测。比如微信号在第三方服务中是不能被直接使用的,而是一个64字节不具备查询意义的open id。

能不能有一个ID机器既能满足散列,又能反映数据规模呢,我想是可以的。

本来只想分享一下现行的生成规则,作为文章的附加,我简单设计了一个有效的ID生成器。

分布式ID的设计

设计方向

快速生成,不冲突。

既使系统时间被重置,也不会冲突。

能从最新的ID拿到当前的数据规模。

支持不同场景不同表述。

结构设计

长度(byte) 含义 意义
4 unix epoch到当前的秒数 去中心化
3 机器标识
2 进程标识
3 扩展计数
8 统一分配全局ID 中心化

设计思想

设计上将中心化与去中心化结合,对外展示去中心化的ID, 对内展示全部,对统计展示中心化ID。
image.png

分配架构

image.png

全局ID统一分配器设计

作为中心化的瓶颈,必须由机器保障其功能可靠。
image.png

各个节点从全局ID分配器批量获取ID作为预分配资源,当节点剩余预留ID低于阈值时再次申请。

节点分配设计

image.png

void Pot::onRequest(long& conID, long& reqID){
    
    uint64_t  id = cache.incr();     //预留ID自增
    if(id <= 0){                     //预留ID不够
        cache.distribute([this, &conID, &reqID](uint64_t id, uint32_t err){                            //请求后端申请全局分配预留片,并设置好回调
                        this->response(conID, reqID, id);
                });                //向全局请求分本书
        return;
    }

    if(cache.shouldDistribute()){                //检查预留池是否达到申请阈值 
        cache.distribute();                        //向后端申请全局分配预留片
    }

    response(conID, reqID, id);       //返回
}

全局分配器工作原理

image.png

工作原理也是一样简单

void Distribute::onRequest(long& conID, long& reqID){
    uint64_t  minID, maxID, err;
    uint32_t flag = db.pre(minID, maxID, err);     //预分配ID片
    if(flag <= 0){                       //是否出错
        response(conID, reqID, err);    //返回
        return;
    }
 
    response(conID, reqID, minID, maxID);       //返回ID片
}

崩溃处理机制

单节点崩溃,其他节点仍能崩溃,这个比较简单。

全局分配器的崩溃处理需要在设计上保障,可以同时并存几个全局生成器,且并不缓存当前最大分配ID,该值由数据库来存储。
image.png

全局分配的请求是个低频处理器。

假设工作节点数为N, 系统每秒处理并发为Concurrent, 全局分配器的请求频次F,每次分配ID片长度为P, 全局分配器个数为M,则有下列公式:

F=Concurrent/(NPM)

假设每秒并发数为100万 (1秒产生100万个ID,1秒新增100万个用户,实际不可能),有5个工作节点,全局分配器个数为4, 每次申请ID片为10000。

F=1000,000 / (5 100004) =5.

100万每秒的分配也只有每秒几次的请求,而且P的值可以灵活修改,假设P=100,000(每片10万个ID),则每秒1个请求都没有了。

image.png

这种情况下对全局分配的安全只需要对数据主从热备就行了。

理论退化
将上面的规则重新审视一下:

字段 长度(byte) 备注
time 4 unix epoch到当前的秒数 去中心化
machine 3 机器标识
process 2 进程标识
count 3 扩展计数
center id 8 统一分配全局ID 中心化

如果只有前4个,去掉center 只退化成了mongodb的去中心化ID生成器。

如果去掉前四项,保留center id,则退化成了具备分布式功能的中心化ID生成器。

在一些简单的业务里,可以只需要在这2者中选一个就可以满足分布式业务的处理。

相关实践学习
MongoDB数据库入门
MongoDB数据库入门实验。
快速掌握 MongoDB 数据库
本课程主要讲解MongoDB数据库的基本知识,包括MongoDB数据库的安装、配置、服务的启动、数据的CRUD操作函数使用、MongoDB索引的使用(唯一索引、地理索引、过期索引、全文索引等)、MapReduce操作实现、用户管理、Java对MongoDB的操作支持(基于2.x驱动与3.x驱动的完全讲解)。 通过学习此课程,读者将具备MongoDB数据库的开发能力,并且能够使用MongoDB进行项目开发。 &nbsp; 相关的阿里云产品:云数据库 MongoDB版 云数据库MongoDB版支持ReplicaSet和Sharding两种部署架构,具备安全审计,时间点备份等多项企业能力。在互联网、物联网、游戏、金融等领域被广泛采用。 云数据库MongoDB版(ApsaraDB for MongoDB)完全兼容MongoDB协议,基于飞天分布式系统和高可靠存储引擎,提供多节点高可用架构、弹性扩容、容灾、备份回滚、性能优化等解决方案。 产品详情: https://www.aliyun.com/product/mongodb
目录
相关文章
|
4月前
|
算法 Go
[go 面试] 雪花算法与分布式ID生成
[go 面试] 雪花算法与分布式ID生成
|
1月前
|
算法 关系型数据库 MySQL
分布式唯一ID生成:深入理解Snowflake算法在Go中的实现
在分布式系统中,确保每个节点生成的 ID 唯一且高效至关重要。Snowflake 算法由 Twitter 开发,通过 64 位 long 型数字生成全局唯一 ID,包括 1 位标识位、41 位时间戳、10 位机器 ID 和 12 位序列号。该算法具备全局唯一性、递增性、高可用性和高性能,适用于高并发场景,如电商促销时的大量订单生成。本文介绍了使用 Go 语言的 `bwmarrin/snowflake` 和 `sony/sonyflake` 库实现 Snowflake 算法的方法。
39 1
分布式唯一ID生成:深入理解Snowflake算法在Go中的实现
|
17天前
|
NoSQL Java 数据处理
基于Redis海量数据场景分布式ID架构实践
【11月更文挑战第30天】在现代分布式系统中,生成全局唯一的ID是一个常见且重要的需求。在微服务架构中,各个服务可能需要生成唯一标识符,如用户ID、订单ID等。传统的自增ID已经无法满足在集群环境下保持唯一性的要求,而分布式ID解决方案能够确保即使在多个实例间也能生成全局唯一的标识符。本文将深入探讨如何利用Redis实现分布式ID生成,并通过Java语言展示多个示例,同时分析每个实践方案的优缺点。
34 8
|
1月前
|
NoSQL 算法 关系型数据库
分布式 ID 详解 ( 5大分布式 ID 生成方案 )
本文详解分布式全局唯一ID及其5种实现方案,关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
分布式 ID 详解 ( 5大分布式 ID 生成方案 )
|
5月前
|
存储 NoSQL Java
通用快照方案问题之通过Sleuth进行耗时分析和链路优化如何解决
通用快照方案问题之通过Sleuth进行耗时分析和链路优化如何解决
49 0
|
5月前
|
消息中间件 Java Nacos
通用快照方案问题之通过Spring Cloud实现配置的自动更新如何解决
通用快照方案问题之通过Spring Cloud实现配置的自动更新如何解决
79 0
|
5月前
|
存储 算法 Java
分布式自增ID算法---雪花算法(SnowFlake)Java实现
分布式自增ID算法---雪花算法(SnowFlake)Java实现
301 0
|
6月前
|
存储 算法 Java
分布式唯一ID解决方案-雪花算法
分布式唯一ID解决方案-雪花算法
59 0
|
7月前
|
SQL 算法
基于若依的ruoyi-nbcio流程管理系统修改代码生成的sql菜单id修改成递增id(谨慎修改,大并发分布式有弊端)
基于若依的ruoyi-nbcio流程管理系统修改代码生成的sql菜单id修改成递增id(谨慎修改,大并发分布式有弊端)
111 1
|
7月前
|
算法 关系型数据库 MySQL
Go语言中的分布式ID生成器设计与实现
【5月更文挑战第6天】本文探讨了Go语言在分布式系统中生成全局唯一ID的策略,包括Twitter的Snowflake算法、UUID和MySQL自增ID。Snowflake算法通过时间戳、节点ID和序列号生成ID,Go实现中需处理时间回拨问题。UUID保证全局唯一,但长度较长。MySQL自增ID依赖数据库,可能造成性能瓶颈。选择策略时需考虑业务需求和并发、时间同步等挑战,以确保系统稳定可靠。
220 0