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

举报
Kokomo 发表于 2023/12/27 09:54:15 2023/12/27
【摘要】 面试官:“只会用自增主键?回去等通知吧”

各位小伙伴大家好呀,今天我们来讨论一下分布式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中取值,避免频繁操作数据库。

这种表的结构大致如下

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的唯一与递增,并且不依赖数据库,从性能角度看也比数据库要好

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

/**     * 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的生成有了更深一层的了解,各位看到现在应该有一种感觉,没有能解决一切的银弹,每一种方式都有其优缺点,每一种方式都要考虑到业务实际的使用场景。受限于篇幅,还有一些细节问题没有展开,各位小伙伴如果有自己的想法也欢迎留言,一起交流。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。