6位顺序号生成_浅谈 ID 生成器

“叮咚,叮咚……”,微信提示音一声接一声,声音是那么的频繁,有妖气,待俺去看一看。

打开微信,发现我们的技术讨论组里有100 多条未读消息,心想,是不是系统出问题了,怎么消息那么频繁?于是迅速的爬楼,历时 1 秒 23,爬到楼顶,了解事情的来龙去脉,大体意思:下午五点,研发一组在第二会议室开会,会议主题是:开发一个适合多个业务场景的分布式ID 生成器。不一会儿,我们都来到第二会议室,开始了激烈讨论。

可能你们都知道,ID 是某个体系中唯一的编码,用来标识事务,比如:身份标识号、账号、唯一编码、专属号码。

ID生成器又是什么呢?说白了就是生成ID 工具,而在这里我们说的 ID 生成器,却是一个服务(下同)。

为什么要开发分布式ID 生成器?

原因大体有两点:

1.许多业务系统需要对大量的订单、消息进行进行唯一标识,如:金融、电商、支付等。

2.每个部门都开发一套 ID 生成器,总体上增加了工作量,增加公司的成本,不利于维护、管理。

分布式ID生成器有哪些要求?

1. 全局唯一性:不能出现重复的ID 号,既然是唯一标识,这是最基本的要求。

2. 递增:比较低要求的条件为趋势递增,即保证下一个ID一定大于上一个ID,而比较苛刻的要求是连续递增,如1001 ,1002,1003 等等。根据我们自己的业务,我们选择的是趋势递增(连续递增,会暴露出系统实际订单量)。

3. 高可用:ID生成事关重大,一旦挂掉会导致整个系统崩溃,给公司带来巨大的损失,需要保证ID的正常、稳定生成。

4. 高性能:必须要在压测下表现良好,如果达不到要求则在高并发环境下依然会导致系统瘫痪。

5. 灵活多变:每个业务场景对ID 的要求也各不相同,ID 生成要做到灵活多变可配置,尽可能多的满足需求。

针对以上那么多要求,我们到底要怎么做?看看前辈们都是怎么做的吧,目前业内有几种常见的解决方案。

一、 UUID(用的最多)方案

UUID:通用唯一识别码(Universally Unique Identifier)的标准型式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的36个字符,示例:550e8400-e29b-41d4-a716-446655440000。

优点:本地生成,全局唯一,没有网络消耗;

缺点:UUID太长,通常以36长度的字符串表示,对MySQL索引不利,如果作为数据库主键,在InnoDB引擎下,UUID的无序性可能会引起数据位置频繁变动,严重影响性能;UUID不能标识业务含义,可读性差;不满足递增要求;不够灵活。

二、 Twitter的雪花算法SnowFlake方案

这种方案大致来说是一种以划分命名空间(UUID也算,由于比较常见,所以单独分析)来生成ID的一种算法,这种方案把64-bit分别划分成多段,分开来标示机器、时间等,比如在snowflake中的64-bit分别表示如下图所示:

6b533833bab18c1e47fcd7c840c01de0.png

第一位为未使用,接下来的41 位为毫秒级时间(41位的长度可以使用 69 年),然后是 5 位 datacenterId和 5 位 workerId (10 位的长度最多支持部署 1024 个节点) ,最后 12 位是毫秒内的计数(12 位的计数顺序号支持每个节点每毫秒产生 4096 个 ID 序号)一共加起来刚好 64 位,为一个 Long 型 (转换成字符串长度为 18) 。示例:323893460451070160,

323893460455264256。

优点:整体上按照时间自增排序;全局唯一;效率高。

缺点:有序,但不连续;不能同时满足多个系统对ID的需求,不够灵活。

三、 数据库自增序列生成方案

以MySQL 举例,利用给字段设置 auto_increment_increment和 auto_increment_offset 来保证 ID 自增,每次业务使用下列SQL 读写 MySQL 得到 ID 号。

优点:简单,利用现有数据库系统的功能实现;成本小,有 DBA 专业维护。ID 号自增,可以实现一些对 ID 有特殊要求的业务。

缺点:强依赖 DB ,当DB 异常时整个系统不可用,属于致命问题;配置主从复制可以尽可能的增加可用性,但是数据一致性在特殊情况下难以保证;主从切换时的不一致可能会导致重复发号。

ID发号性能瓶颈限制在单台MySQL的读写性能。

四、 基于 ZooKeeper和本地缓存的方案

使用ZooKeeper 作为分段节点协调工具,每台服务器首先从Zookeeper缓存一段,如1-2000的ID,此时Zookeeper 上保存最大值2000,每次获取的时候都会进行判断,如果ID<=2000 ,则更新本地的当前值,如果为2001,则会将Zookeeper 上的最大值更新至 4000,本地缓存段更新为2001-4000,更新的时候使用分布式锁来实现。

优点:全局唯一,效率高。

缺点:维护成本较高,不能同时满足多个系统对ID的需求,不够灵活。

看了许多业内解决方案,有些方案已经基本上可以满足我们的需求,但是不够灵活。我们需要一种灵活、多变、可配置的方案。

经过一番讨论,我们选择了自主研发,核心思想:

使用数据库双 buffer 优化方案,每次从数据库拿取一个号段,当该号段下发50% 时,如果下一个号段未更新,则启动另一个线程去提前更新新号段,当该号段已全部下发完成,且下一个号段准备完成,则切换到下一个号段,就这样一次一次的循环。

举个栗子: 第一次取一个号段100,000 ——110,000 1,当该号段下发到105,000 时,去检查120,000 号段是否更新,如果未更新,启动一个线程去更新号段120,000 ——130,000 2,当1号段已经下发完成,切换到2号段。

这样做的好处是不用频繁的访问数据库,保证了效率,在数据库宕机的一段时间内,服务仍然可生成ID 。

这样就灵活了吗?

NO,这提高性能、效率的方式,要想做到灵活,我们需要设计一张数据库表,表中要区别不同的业务类型,可以设置ID 的前缀规则、后缀规则、长度、步长、最大 ID,保证 ID 灵活、多变、可配置。

数据库表结构设计:

a9682ed4fae2dc4d7c6de3ca7f53bf50.png

key_name:区分业务

key_length:id长度

key_cache:缓存数量

key_prefix:前缀规则

key_subffix:后缀规则

key_digit:key 生成规则,支持 10 进制、36进制或者 62 进制

数据库表测试数据:

c8c78d50de2e1f835f7d5782ed141f7c.png

测试服务器配置:Linux 2 核 4G 内存X 2

步长设置为1000,缓冲池设置为 1000,每秒大约可生成16,675,231 次。

分布式 ID 生成器的方案还有很多,各有各的优点,需要你们根据自己的业务场景去选择,“不选贵的,只选对的”。如过觉得这篇文章对你有点用的话,请动一下你发财的小手,帮忙转发一下,感激涕零。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值