前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面试官:“只会用自增主键?回去等通知吧”

面试官:“只会用自增主键?回去等通知吧”

原创
作者头像
Kokomo
发布2023-11-11 22:36:35
3660
发布2023-11-11 22:36:35
举报

各位小伙伴大家好呀,今天我们来讨论一下分布式id,在讨论分布式id前我们先来考虑一个问题,为什么我们需要分布式id?在业务发展的初期,业务量小,通常利用DB默认的自增主键策略即可满足需求,效率高且使用便捷,但随着业务的发展,当数据量增大,分库分表后,如果还采用自增策略,就会出现问题。那么各位小伙伴思考一下,在生成分布式id时我们需要满足哪些特性呢?

全局唯一性: ID 是作为唯一的标识,不能出现重复

有序性:准确的来说是有序性 有序的主键既可以保证写入的效率,也方便查找及排序

那接下来我们来看下各种主流的分布式id的生成方案及其特点

分布式 ID 生成方案

1、UUID

UUID是Universally Unique Identifier的缩写,它是在一定的范围内(从特定的名字空间到全球)唯一的机器生成的标

UUID 的标准型式包含 32 个 16 进制数字,以连字号分为五段,形式为 8-4-4-4-12 的 36 个字符。UUID的优点显而易见,唯一,且易生成,性能十分高,但是UUID杂乱无序,字符与数字混合,不易存储,由于UUID的生成基于MAC地址,也会造成安全问题。

2、雪花算法

雪花算法算是最有名的分布式id生成方案了,雪花算法(SnowFlake)是Twitter公布的分布式主键生成算法,也是ShardingSphere默认提供的配置分布式主键生成策略方式。

图片
图片

上图是一个标准的雪花id的结构,第一位为标志位,后面41位为时间戳,可以表示到毫秒,41-bit 位可表示 241 个数,大约可表示69年的时间,后面十位是节点id,最多可容纳1024个节点,最后12位是生成的自增id序列,这样雪花算法可以在一个最大容纳1024个节点的系统中,每毫秒可以为单节点生成4094个id,虽然官方表示并发数可以达到409w/s,但这个数据其实是有问题的,在每毫秒中必须不能超过4096。在使用中,各位小伙伴可以根据业务的实际场景对位数进行调整。

雪花算法的优点十分明显,雪花算法生成的 ID 是趋势递增,不依赖数据库等第三方系统。生成 ID 的效率非常高,稳定性好,可以根据自身业务特性分配 bit 位,比较灵活;缺点就是强依赖于机器时钟,一旦某一台机器发生时钟回拨,那么该机器的发号将可能产生重复,如果单位时间内并发数超过序列的最大值,发号也将处于不可用状态,这里介绍的只是最初的雪花算法,经过一段时间的实践,一些问题已经得到了解决办法,这一部分,我们将在下文详细描述。

3、MYSQL自增主键

步长模式

在分布式环境下其实也可以利用mysql生成id,如果是在分库分表的情况下,需要在表中设置auto_increment_offset ,依据节点数对生成id的步长进行配置。

图片
图片

这种方案看起来是可行的,但一旦扩容,步长就需重新设置,如果涉及的机器很多就需要花很长时间进行设置,此外,由于此方案依赖MYSQL,生成的瓶颈也在MYSQL,频繁操作数据库,对DB的压力也很大。另外这种方式生成的ID是趋势递增,并非单调递增,在使用时需要考虑业务场景是否允许

号段模式

这种方式是针对步长模式的一种扩展,所有的ID出自一张表,每次获取ID,获取一个范围的ID 比如[1,1000],第二次取就是[1001,2000],每次取完后,将获取到的值放在内存中,等获取的ID使用完毕,再去DB中取值,避免频繁操作数据库。

这种表的结构大致如下

代码语言:javascript
复制
CREATE TABLE id_generator(id int (10) NOT NULL,max_id bigint (20) NOTNULL COMMENT'当前最大id',stepint (20) NOTNULL COMMENT'号段的步长',biz_type int(20) NOTNULL COMMENT'业务类型',PRIMARYKEY(`id`))

biz_type用来区分不同的业务模块 max_id记录当前获取到的最大ID,步长记录单次获取ID的数量

4、redis

利用redis生成分布式id主要是基于redis是单线程,利用incr命令实现ID的原子性自增。利用redis单线程的特点保证了id的唯一与递增,并且不依赖数据库,从性能角度看也比数据库要好

代码语言:javascript
复制
127.0.0.1:6379> set seq_id 1     // 初始化自增ID为1127.0.0.1:6379> incr seq_id      // 增加1,并返回递增后的数值(integer) 2

缺点也十分明显 系统中需要引入redis相关组件,且强依赖于redis,一旦redis宕机,将会影响所有业务。

主流实现

百度-Uidgenerator

百度 UidGenerator 是百度开源基于 Java 语言实现的唯一 ID 生成器,是在雪花算法 Snowflake 的基础上做了一些改进。https://github.com/baidu/uid-generator

https://github.com/baidu/uid-generator

引用官网的解释:

UidGenerator是Java实现的, 基于Snowflake算法的唯一ID生成器。UidGenerator以组件形式工作在应用项目中, 支持自定义workerId位数和初始化策略, 从而适用于docker等虚拟化环境下实例自动重启、漂移等场景。在实现上, UidGenerator通过借用未来时间来解决sequence天然存在的并发限制; 采用RingBuffer来缓存已生成的UID, 并行化UID的生产和消费, 同时对CacheLine补齐,避免了由RingBuffer带来的硬件级「伪共享」问题. 最终单机QPS可达600万。

图片
图片
  • sign(1bit) 固定1bit符号标识,即生成的UID为正数。
  • delta seconds (28 bits) 当前时间,相对于时间基点"2016-05-20"的增量值,单位:秒,最多可支持约8.7年
  • worker id (22 bits) 机器id,最多可支持约420w次机器启动。内置实现为在启动时由数据库分配,默认分配策略为用后即弃,后续可提供复用策略。
  • sequence (13 bits) 每秒下的并发序列,13 bits可支持每秒8192个并发。

借助未来时间和ringbuffer的作用是什么

在上文我们讲过,雪花id单位时间内是一个节点有最大并发数限制的,在UID中,一个节点的最大并发就是8192/s, 如果超过了这个时间,在填充RingBuffer时,生成的id的delta seconds 部分会使用未来的时间。这样就最大程度的避免了服务的不可用。

每个ringbuffer的容量就是序列的长度,在填充时如果在同一秒填充了两次,那么就会在当前时间戳上加一 使用未来的时间生成UID,

而设计了两个RingBuffer也最大可能的保证了永远都有buffer对外提供服务,避免了极端情况下由于生成id导致的延迟

美团-LEAF

美团的LEAF就是上文讲的号段模式的体现,使用微服务,对外提供接口,底层操作数据库生成UID,此外还提供了雪花模式,下图就是LEAF服务的示意图。

LEAF采用双buffer了的方式,Leaf服务内部有两个号段缓存区segment。当前号段已下发10%时,如果下一个号段未更新,则另启一个更新线程去更新下一个号段。当前号段全部下发完后,如果下个号段准备好了则切换到下个号段为当前segment接着下发,循环往复。这样即使DB宕机,LEAF服务依然能够对外提供服务。

seata中的分布式ID实现

在seata中也提供了一种分布式的ID生成方式

io.seata.common.util.IdWorker

这个类改变了原有雪花算法定义的机构

将原有的时间戳与节点更换了位置,Idworker在面对高并发的情况下同样采用了使用未来时间的方式,在实现层面 只需几行代码便可以获得UID

代码语言:javascript
复制
/**     * get next UUID(base on snowflake algorithm), which look like:     * highest 1 bit: always 0     * next   10 bit: workerId     * next   41 bit: timestamp     * lowest 12 bit: sequence     * @return UUID     */    public long nextId() {        waitIfNecessary();        long next = timestampAndSequence.incrementAndGet();        long timestampWithSequence = next & timestampAndSequenceMask;        return workerId | timestampWithSequence;    }

但这种实现其实有一点问题,就是获得的id趋势递增 非单调递增,有些小伙伴可能不懂了,这两种有什么区别吗,如果一个表只有一个节点操作那没问题,如果多个节点操作,那id便不是有序的。

尾声

相信能看到这里的各位同学都对分布式ID的生成有了更深一层的了解,各位看到现在应该有一种感觉,没有能解决一切的银弹,每一种方式都有其优缺点,每一种方式都要考虑到业务实际的使用场景。受限于篇幅,还有一些细节问题没有展开,各位小伙伴如果有自己的想法也欢迎留言,一起交流。

我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

“邀请人:“努力的小雨”

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 分布式 ID 生成方案
  • 主流实现
  • 尾声
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档