百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

ID生成器 看这一篇就够了(常见的id生成器有哪些)

cac55 2024-10-20 04:21 12 浏览 0 评论

一、前言

上一篇文章《面试必备:如何将一个长URL转换为一个短URL?》中谈到如何将长地址URL转换为短地址URL,其中谈到了一个比较理想的解决方案就是使用发号器生成一个唯一的整数ID,然后转换为62进制,作为短地址URL。

其中使用到了ID发号器,可能很多小伙伴还不懂什么是ID发号器以及如何去实现,今天我们就一起探讨一下什么是ID发号器?ID发号器的原理是什么?如何实现一个ID发号器等。

二、从数据库主键ID说起

1、单机数据库

当我们的业务访问量不是很大的时候,我们可以使用一台数据库服务器满足我们的业务需求,我们一般设计数据库的时候主键ID用bigint类型,并且设置为自增、无符号,如下所示:

这种方式完全可以满足我们的业务需求,生成全局唯一递增ID是数据库可以提供给我们的功能,具有如下优点:

(1)能够保证唯一性;

(2)能够保证递增性;

(3)步长固定;

但是当我们的业务逐渐扩大,我们需要对数据库进行分库分表等操作的时候,这种方式是就变得没有办法了!

试想一下,如果我们有一个业务,每一个省份维护自己的一台数据库,User表用于记录当前省份的用户信息,假如有一天我们需要把每一个省份的User表用户信息全部合并到一台中央数据库User表中进行统计的时候,结果是不是会崩掉,因为每一个省份User表中的ID都是从1主键递增的!

2、数据库集群、分库分表

当我们的数据库达到一定规模的时候,就需要对其进行分库分表,分库分表的时候我们就很难保证主键ID的唯一性,这一点很好理解。这是因为,我们的一张表被分割到不同机器上的数据库中,如果还依靠与数据库自带的自增功能的话就很那保证ID唯一性!如下图所示:

可以看出,User表中的100W数据被分到两个数据库中,在每一个数据库内部主键ID是自增的,但是却没法保证全局主键ID自增的,这显然是错误的!如何解决这种问题哪?

(1)使用UUID

最简单、最容易想到的就应该是使用UUID了,根据UUID的特性,可以产生一个唯一的字符串,这一点大家都知道。UUID是在本地生成的,所以相对性能较高、时延低、扩展性高,完全不受分库分表的影响!

但是使用UUID是有点小问题的,主要体现在:

UUID无法保证趋势递增;

UUID过长,往往用32位字符串表示,占用数据库空间较大,做主键的时候索引中主键ID占据的空间较大;

UUID作为主键建立索引查询效率低,常见优化方案为转化为两个uint64整数存储;

由于使用实现版本的不一样,在高并发情况下可能会出现UUID重复的情况;

UUID虽然能够保证全局主键ID的唯一性,但是UUID并不具有有序性,会导致B+树索引在写的时候有过多的随机写操作(连续的ID会产生部分的顺序写);另外,由于在写的时候不能产生有顺序的append操作,而需要进行insert操作,将会读取整个B+树节点加到内存中,在插入这条记录后将整个节点写回磁盘,这种操作在记录占用空间比较大的情况下,性能下降明显。

(2)ID分组

虽然,UUID很方便,但由于他的一些弊端我们无法接受,所以在很多对一些性能要求较高的业务场景中,我们是很少使用UUID的,那我们还有没有什么其他方法哪?接下来让我们看一下ID分组的使用:

如上图所述,由1个数据库变成4个库,每个数据库设置不同的auto_increment初始值init,以及相同的增长步长step,以保证每个数据库生成的ID是不同的,改进后的架构保证了可用性,但缺点是:

丧失了ID生成的“绝对递增性”,但这个问题不大,我们的目标是趋势递增,不是绝对递增;

数据库的写压力依然很大,每次生成ID都要访问数据库;

可扩展性差;

我们可以想象的是,目前虽然我们的机器只有4台,然后由不同的init和不同的step,但是如果我们需要在其中再加一台机器的话,可想而知我们需要手动更新init和step,这是一件比较繁琐的事情!但有人可能会说了,我们可以直接把 step设置大一些,假如,我们预期数据最大规模的时候用100台数据库服务器就可以了,那我们就可以设置step为100。尽管如此,扩展性还不是很高!

3、还有什么操作哪?

上述我们讨论了一个一个的优缺点,当然,还有很多其他的主键ID生成方案。但总的来说,我们讨论问题的关键浮出水面:如何高效生成趋势有序的全局唯一ID,兼顾有序性、高性能、可扩展等因素!

这就需要我们今天的主角登场了,他就是:ID发号器!ID发号器的主要思想大致相同,但不同平台的实现方式可能会有所不同,本文主要介绍一下:Twitter公司的SnowFlake、如何自己实现一个ID发号器、Vesta框架。

三、SnowFlake简介

Twitter公司的SnowFlake算法就是著名的《雪花算法》,SnowFlake是通过Scala语言实现的,目前GitHub上已经看不到源代码了,只有一个2010年的版本,地址为:https://github.com/twitter/snowflake/releases/tag/snowflake-2010,因此很难在我们实际的项目中真正的使用到 ,我们更多的是采用雪花算法的思想,去构建自己属于自己的ID发号器。

1、SnowFlake原理

SnowFlake产生的ID是一个64位的整型,结构如下(每一部分用“-”符号分隔):

(1)1位:标识部分,在java中由于long的最高位是符号位,正数是0,负数是1,一般生成的ID为正数,所以为0;

(2)41位:时间戳部分,这个是毫秒级的时间,一般实现上不会存储当前的时间戳,而是时间戳的差值(当前时间-固定的开始时间),这样可以使产生的ID从更小值开始;41位的时间戳可以使用69年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年;

(3)10位:节点部分,Twitter实现中使用前5位作为数据中心标识,后5位作为机器标识,可以部署1024个节点;

(4)12位:序列号部分,支持同一毫秒内同一个节点可以生成4096个ID;

SnowFlake算法生成的ID大致上是按照时间递增的,用在分布式系统中时,需要注意数据中心标识和机器标识必须唯一,这样就能保证每个节点生成的ID都是唯一的!

2、SnowFlake算法如何实现

SnowFlake算法的实现在GitHub或者码云上有各种实现版本!SnowFlake算法为我们提供了一个可行的思路,但是我们不一定都需要像上面那样使用5位作为数据中心标识,5位作为机器标识,可以根据我们业务的需要,灵活分配节点部分,如:若不需要数据中心,完全可以使用全部10位作为机器标识;若数据中心不多,也可以只使用3位作为数据中心,7位作为机器标识。所以,我们可以看出SnowFlake算法只是一种指导思想,我们下边自己简单的实现一个一下!

四、如何自己实现一个ID发号器

注意这里只有生成ID的部分,没有Client也没有Server,如果想要详细的,请看第五节《Vesta框架简介》!

/**
* Twitter的SnowFlake算法,使用SnowFlake算法生成一个整数,然后转化为62进制变成一个短地址URL
* @author beyond https://github.com/beyondfengyu/SnowFlake
* @author xuliugen
* @date 2018/04/23
*/
public class SnowFlake {
/**
* 起始的时间戳
*/
private final static long START_TIMESTAMP = 1480166465631L;
/**
* 每一部分占用的位数
*/
private final static long SEQUENCE_BIT = 12; //序列号占用的位数
private final static long MACHINE_BIT = 5; //机器标识占用的位数
private final static long DATA_CENTER_BIT = 5; //数据中心占用的位数
/**
* 每一部分的最大值
*/
private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
private final static long MAX_DATA_CENTER_NUM = -1L ^ (-1L << DATA_CENTER_BIT);
/**
* 每一部分向左的位移
*/
private final static long MACHINE_LEFT = SEQUENCE_BIT;
private final static long DATA_CENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
private final static long TIMESTAMP_LEFT = DATA_CENTER_LEFT + DATA_CENTER_BIT;
private long dataCenterId; //数据中心
private long machineId; //机器标识
private long sequence = 0L; //序列号
private long lastTimeStamp = -1L; //上一次时间戳
/**
* 根据指定的数据中心ID和机器标志ID生成指定的序列号
* @param dataCenterId 数据中心ID
* @param machineId 机器标志ID
*/
public SnowFlakeShortUrl(long dataCenterId, long machineId) {
if (dataCenterId > MAX_DATA_CENTER_NUM || dataCenterId < 0) {
throw new IllegalArgumentException("DtaCenterId can't be greater than MAX_DATA_CENTER_NUM or less than 0!");
}
if (machineId > MAX_MACHINE_NUM || machineId < 0) {
throw new IllegalArgumentException("MachineId can't be greater than MAX_MACHINE_NUM or less than 0!");
}
this.dataCenterId = dataCenterId;
this.machineId = machineId;
}
/**
* 产生下一个ID
* @return
*/
public synchronized long nextId() {
long currTimeStamp = getNewTimeStamp();
if (currTimeStamp < lastTimeStamp) {
throw new RuntimeException("Clock moved backwards. Refusing to generate id");
}
if (currTimeStamp == lastTimeStamp) {
//相同毫秒内,序列号自增
sequence = (sequence + 1) & MAX_SEQUENCE;
//同一毫秒的序列数已经达到最大
if (sequence == 0L) {
currTimeStamp = getNextMill();
}
} else {
//不同毫秒内,序列号置为0
sequence = 0L;
}
lastTimeStamp = currTimeStamp;
return (currTimeStamp - START_TIMESTAMP) << TIMESTAMP_LEFT //时间戳部分
| dataCenterId << DATA_CENTER_LEFT //数据中心部分
| machineId << MACHINE_LEFT //机器标识部分
| sequence; //序列号部分
}
private long getNextMill() {
long mill = getNewTimeStamp();
while (mill <= lastTimeStamp) {
mill = getNewTimeStamp();
}
return mill;
}
private long getNewTimeStamp() {
return System.currentTimeMillis();
}
public static void main(String[] args) {
SnowFlakeShortUrl snowFlake = new SnowFlakeShortUrl(2, 3);
for (int i = 0; i < (1 << 4); i++) {
//10进制
System.out.println(snowFlake.nextId());
}
}
}

//输出结果:

185892988017455104

185892988021649408

185892988021649409

185892988021649410

185892988021649411

185892988021649412

185892988021649413

185892988021649414

185892988021649415

185892988021649416

185892988021649417

185892988021649418

185892988021649419

185892988021649420

185892988021649421

185892988021649422

还记得上一篇《面试必备:如何将一个长URL转换为一个短URL?》文中提到的短地址吗?上文中已经生成了唯一不重复的ID,我们只需要增加一个进制转换的工具就可以了,进制转换的工具如下:

/**
* 进制转换工具,最大支持十进制和62进制的转换
* 1、将十进制的数字转换为指定进制的字符串;
* 2、将其它进制的数字(字符串形式)转换为十进制的数字
* @author xuliugen
* @date 2018/04/23
*/
public class NumericConvertUtils {
/**
* 在进制表示中的字符集合,0-Z分别用于表示最大为62进制的符号表示
*/
private static final char[] digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};
/**
* 将十进制的数字转换为指定进制的字符串
* @param number 十进制的数字
* @param seed 指定的进制
* @return 指定进制的字符串
*/
public static String toOtherNumberSystem(long number, int seed) {
if (number < 0) {
number = ((long) 2 * 0x7fffffff) + number + 2;
}
char[] buf = new char[32];
int charPos = 32;
while ((number / seed) > 0) {
buf[--charPos] = digits[(int) (number % seed)];
number /= seed;
}
buf[--charPos] = digits[(int) (number % seed)];
return new String(buf, charPos, (32 - charPos));
}
/**
* 将其它进制的数字(字符串形式)转换为十进制的数字
* @param number 其它进制的数字(字符串形式)
* @param seed 指定的进制,也就是参数str的原始进制
* @return 十进制的数字
*/
public static long toDecimalNumber(String number, int seed) {
char[] charBuf = number.toCharArray();
if (seed == 10) {
return Long.parseLong(number);
}
long result = 0, base = 1;
for (int i = charBuf.length - 1; i >= 0; i--) {
int index = 0;
for (int j = 0, length = digits.length; j < length; j++) {
//找到对应字符的下标,对应的下标才是具体的数值
if (digits[j] == charBuf[i]) {
index = j;
}
}
result += index * base;
base *= seed;
}
return result;
}
}
写个测试用例如下:
public static void main(String[] args) {
SnowFlakeShortUrl snowFlake = new SnowFlakeShortUrl(2, 3);
for (int i = 0; i < (1 << 4); i++) {
//10进制
Long id = snowFlake.nextId();
//62进制
String convertedNumStr = NumericConvertUtils.toOtherNumberSystem(id, 62);
//10进制转化为62进制
System.out.println("10进制:" + id + " 62进制短地址:" + convertedNumStr);
//TODO 执行具体的存储操作,可以存放在Redis等中
//62进制转化为10进制
System.out.println("62进制短地址:" + convertedNumStr + " 10进制:" + NumericConvertUtils.toDecimalNumber(convertedNumStr, 62));
System.out.println();
}
}

//执行结果如下:

10进制:185894506410029056 62进制短地址:dJoJ1Xyo3C

62进制短地址:dJoJ1Xyo3C 10进制:185894506410029056

10进制:185894506414223360 62进制短地址:dJoJ1XPZbG

62进制短地址:dJoJ1XPZbG 10进制:185894506414223360

10进制:185894506414223361 62进制短地址:dJoJ1XPZbH

62进制短地址:dJoJ1XPZbH 10进制:185894506414223361

10进制:185894506414223362 62进制短地址:dJoJ1XPZbI

62进制短地址:dJoJ1XPZbI 10进制:185894506414223362

10进制:185894506414223363 62进制短地址:dJoJ1XPZbJ

62进制短地址:dJoJ1XPZbJ 10进制:185894506414223363

10进制:185894506414223364 62进制短地址:dJoJ1XPZbK

62进制短地址:dJoJ1XPZbK 10进制:185894506414223364

......

所有代码地址在:https://gitee.com/xuliugen/codes/9upvmzyk6c2i78eb3lgnj63

五、Vesta框架简介

Vesta是一款通用的ID产生器,互联网俗称统一发号器,它具有全局唯一、粗略有序、可反解和可制造等特性,它支持三种发布模式:嵌入发布模式、中心服务器发布模式、REST发布模式,根据业务的性能需求,它可以产生最大峰值型和最小粒度型两种类型的ID,它的实现架构使其具有高性能,高可用和可伸缩等互联网产品需要的质量属性,是一款通用的高性能的发号器产品。

码云:https://gitee.com/robertleepeak/vesta-id-generator

GitHub:https://github.com/cloudatee/vesta-id-generator

由于Vesta的设计与实现较为复杂,一小节不足以说明清楚,后续我们再单独写一篇文章讨论,这里不再详细的介绍,有兴趣的参考上述仓库地址文档!

原文链接:https://blog.csdn.net/bntX2jSQfEHy7/article/details/80059147

资料:美团leaf :https://tech.meituan.com/2017/04/21/mt-leaf.html

相关推荐

MIRIX重塑AI记忆:超Gemini 410%,节省99.9%内存,APP同步上线

MIRIX,一个由UCSD和NYU团队主导的新系统,正在重新定义AI的记忆格局。在过去的十年里,我们见证了大型语言模型席卷全球,从写作助手到代码生成器,无所不能。然而,即使最强大的模型依...

硬盘坏了怎么把数据弄出来对比10种硬盘数据恢复软件

机械硬盘或固态硬盘损坏导致数据丢失时,应立即停止对硬盘的读写操作,并根据损坏类型选择逻辑层恢复工具或专业物理恢复服务。紧急处置措施立即停止通电使用:发现硬盘异响、无法识别或数据异常时,需立即断开连接,...

蓝宝石B850A WIFI主板新玩法:内存小参调节体验

蓝宝石前段时间发布了一款性价比极高的主板:NITRO氮动B850AWIFI主板。这款主板的售价只要1349元,相比普遍1500元以上的B850主板,确实极具竞争力。虽然价格实惠,蓝宝石NITR...

内存卡损坏读不出怎么修复?这5个数据恢复工具汇总,3秒挽回!

在数字化生活的浪潮中,内存卡凭借小巧便携与大容量存储的特性,成为相机、手机、行车记录仪等设备存储数据的得力助手,承载着无数珍贵回忆与重要文件。然而,当内存卡突然损坏无法读取,无论是误删、格式化、病毒入...

内存卡修复不再难,2025年必学的6款软件工具

内存卡出现问题时,通常是因为文件系统损坏、物理损坏或病毒感染。通过专业的修复工具,我们可以尝试恢复数据并修复内存卡。内存卡修复利器:万兴恢复专家万兴恢复专家是一款功能强大的数据恢复软件,支持多种设备和...

有5款内存卡修复工具汇总,内存卡数据轻松找回!

在如今的数字时代,内存卡作为不可或缺的存储介质,广泛应用于相机、手机、行车记录仪等各类设备中,承载着我们珍贵的照片、视频以及重要文件。然而,数据丢失的风险却如影随形,误删、格式化、病毒入侵、硬件故障等...

揭秘:如何通过多种方式精准查询内存条型号及规避风险?

以下是内存条型号查询的常用方法及注意事项,综合了物理查看、软件检测、编码解析等多种方式:一、物理标签查看法1.拆机查看标签打开电脑主机/笔记本后盖找到内存条,观察标签上的型号标识。例如内存标签通常标...

内存卡数据恢复5个工具汇总推荐,轻松找回珍贵记忆!

在这个数字化时代,内存卡作为我们存储珍贵照片、重要文件的常用载体,广泛应用于手机、相机、平板电脑等设备。但数据丢失的意外却常常不期而至,误删除、格式化、病毒攻击,甚至内存卡的物理损坏,都可能让辛苦保存...

电脑内存智能监控清理,优化性能的实用软件

软件介绍Memorycleaner是一款内存清理软件。功能很强,效果很不错。Memorycleaner会在内存用量超出80%时,自动执行“裁剪进程工作集”“清理系统缓存”以及“用全部可能的方法清理...

TechPowerUp MemTest64:内存稳定性测试利器

TechPowerUpMemTest64:内存稳定性测试利器一、软件简介TechPowerUpMemTest64,由知名硬件信息工具GPU-Z的出品公司TechPowerUp发布,是一款专为64位...

微软推出AI恶意软件检测智能体Project Ire,精确度高达98%

IT之家8月6日消息,当地时间周二,微软宣布推出可自主分析恶意软件的AI检测系统原型——ProjectIre。该项目由微软研究院、Defender研究团队及Discovery&a...

农村老木匠常用的20种老工具,手艺人靠它养活一家人,你认识几种

生活中的手艺老匠人是非常受到尊敬和崇拜的,特别是在农村曾经的老匠人都是家里的“座上宾”。对于民间传统的手艺人,有一种说法就是传统的八大匠:木匠、泥匠、篾匠、铁匠、船匠、石匠、油匠和剃头匠。木匠的祖始爷...

恶意木马新变种伪装成聊天工具诱人点击

国家计算机病毒应急处理中心通过对互联网监测发现,近期出现一种恶意木马程序变种Trojan_FakeQQ.CTU。该变种通过伪装成即时聊天工具,诱使计算机用户点击运行。该变种运行后,将其自身复制到受感染...

学习网络安全 这些工具你知道吗?

工欲善其事必先利其器,在新入门网络安全的小伙伴而言。这些工具你必须要有所了解。本文我们简单说说这些网络安全工具吧!Web安全类web类工具主要是通过各种扫描工具,发现web站点存在的各种漏洞...

5分钟盗走你的隐私照片,这个全球性漏洞到底有多可怕?

这个时代,大家对电脑出现漏洞,可能已经习以为常。但如果机哥告诉大家,这个漏洞能够在5分钟内,破解并盗取你所有加密文件,而且还无法通过软件和补丁修复...这可就有点吓人啦。事情是酱婶的。来自荷兰埃因...

取消回复欢迎 发表评论: