IT科技类资讯

雪花算法:分布式唯一ID生成利器

时间:2010-12-5 17:23:32  作者:系统运维   来源:人工智能  查看:  评论:0
内容摘要:前言以分布式ID为例,它的生成往往会在唯一性、递增性、高可用性、高性能等方面都有所要求。并且在业务处理时,还要防止爬虫根据ID的自增进行数据爬取。而雪花算法,在这些方面表现得都不错。常见分布式ID生成

前言

以分布式ID为例,雪花它的算法式唯生成生成往往会在唯一性、递增性、分布高可用性、利器高性能等方面都有所要求。雪花并且在业务处理时,算法式唯生成还要防止爬虫根据ID的分布自增进行数据爬取。而雪花算法,利器在这些方面表现得都不错。雪花

常见分布式ID生成

市面上比较常见的算法式唯生成分布式ID生成算法及类库:

UUID:Java自带API,生成一串唯一随机36位字符串(32个字符串+4个“-”)。分布可以保证唯一性,利器但可读性差,雪花无法有序递增。算法式唯生成

SnowFlake:雪花算法,分布Twitter开源的由64位整数组成分布式ID,性能较高,并且在单机上递增。GitHub上官方地址:https://github.com/twitter-archive/snowflake/tree/snowflake-2010 。

UidGenerator:百度开源的分布式ID生成器,基于雪花算法。GitHub参考链接:https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md 。该项目的说明文档及测试案例都值得深入学习一下。

Leaf:美团开源的分布式ID生成器,能保证全局唯一,趋势递增,但需要依赖关系数据库、b2b供应网Zookeeper等中间件。相关实现可参考该文:https://tech.meituan.com/2017/04/21/mt-leaf.html 。

雪花算法

雪花(snowflake),美丽、独特又变幻莫测。在大自然中几乎找不到两片完全一样的雪花。雪花的这些特性正好在雪花算法上有所展示。

SnowFlake算法是Twitter开源的分布式ID生成算法。核心思想就是:使用一个64 bit的 long 型的数字作为全局唯一ID。算法中还引入了时间戳,基本上保证了自增特性。

最初的版本的雪花算法是基于scala写的,当然,不同的编程语言都可以根据其算法逻辑进行实现。

雪花算法原理

SnowFlake算法生成ID的结果是一个64bit大小的整数,结构如下图:

算法解析:

第一个部分:1个bit,无意义,固定为0。亿华云二进制中最高位是符号位,1表示负数,0表示正数。ID都是正整数,所以固定为0。第二个部分:41个bit,表示时间戳,精确到毫秒,可以使用69年。时间戳带有自增属性。第三个部分:10个bit,表示10位的机器标识,最多支持1024个节点。此部分也可拆分成5位datacenterId和5位workerId,datacenterId表示机房ID,workerId表示机器ID。第四部分:12个bit,表示序列化,即一些列的自增ID,可以支持同一节点同一毫秒生成最多4095个ID序号。

由于在Java中64bit的整数是long类型,所以在Java中SnowFlake算法生成的id就是long来存储的。

雪花算法Java实现

雪花算法Java工具类实现:

public class SnowFlake {

/

**

* 起始的时间戳(可设置当前时间之前的高防服务器邻近时间)

*/

private final static long START_STAMP = 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_DATA_CENTER_NUM = ~(-1L << DATA_CENTER_BIT);

private final static long MAX_MACHINE_NUM = ~(-1L << MACHINE_BIT);

private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_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;

/

**

* 数据中心ID(0~31)

*/

private final long dataCenterId;

/

**

* 工作机器ID(0~31)

*/

private final long machineId;

/

**

* 毫秒内序列(0~4095)

*/

private long sequence = 0L;

/

**

* 上次生成ID的时间截

*/

private long lastStamp = -1L;

public SnowFlake(long dataCenterId, long machineId) {

if (dataCenterId > MAX_DATA_CENTER_NUM || dataCenterId < 0) {

throw new IllegalArgumentException("dataCenterId cant be greater than MAX_DATA_CENTER_NUM or less than " +

"0");

}

if (machineId > MAX_MACHINE_NUM || machineId < 0) {

throw new IllegalArgumentException("machineId cant be greater than MAX_MACHINE_NUM or less than 0");

}

this.dataCenterId = dataCenterId;

this.machineId = machineId;

}

/

**

* 产生下一个ID

*/

public synchronized long nextId() {

long currStamp = getNewStamp();

if (currStamp < lastStamp) {

throw new RuntimeException("Clock moved backwards. Refusing to generate id");

}

if (currStamp == lastStamp) {

//相同毫秒内,序列号自增

sequence = (sequence + 1) & MAX_SEQUENCE;

//同一毫秒的序列数已经达到最大

if (sequence == 0L) {

//阻塞到下一个毫秒,获得新的时间戳

currStamp = getNextMill();

}

} else {

//不同毫秒内,序列号置为0

sequence = 0L;

}

lastStamp = currStamp;

// 移位并通过或运算拼到一起组成64位的ID

return (currStamp - START_STAMP) << TIMESTAMP_LEFT //时间戳部分

| dataCenterId << DATA_CENTER_LEFT //数据中心部分

| machineId << MACHINE_LEFT //机器标识部分

| sequence; //序列号部分

}

private long getNextMill() {

long mill = getNewStamp();

while (mill <= lastStamp) {

mill = getNewStamp();

}

return mill;

}

private long getNewStamp() {

return System.currentTimeMillis();

}

public static void main(String[] args) {

SnowFlake snowFlake = new SnowFlake(11, 11);

long start = System.currentTimeMillis();

for (int i = 0; i < 10; i++) {

System.out.println(snowFlake.nextId());

}

System.out.println(System.currentTimeMillis() - start);

}

}

上述代码中,在算法的核心方法上,通过加synchronized锁来保证线程安全。这样,同一服务器线程是安全的,生成的ID不会出现重复,而不同服务器由于机器码不同,就算同一时刻两台服务器都产生了雪花ID,结果也是不一样的。

其他问题

41位时间戳最长只能有69年

下面来用程序推算一下,41位时间戳为什么只能支持69年。

41的二进制,最大值也就41位都是1,也就是说41位可以表示2^{41}-1个毫秒的值,转化成单位年则是(2^{41}-1) / (1000 * 60 * 60 * 24 *365) = 69年。

通过代码验证一下:

public static void main(String[] args) {

//41位二进制最小值

String minTimeStampStr = "00000000000000000000000000000000000000000";

//41位二进制最大值

String maxTimeStampStr = "11111111111111111111111111111111111111111";

//转10进制

long minTimeStamp = new BigInteger(minTimeStampStr, 2).longValue();

long maxTimeStamp = new BigInteger(maxTimeStampStr, 2).longValue();

//一年总共多少毫秒

long oneYearMills = 1L * 1000 * 60 * 60 * 24 * 365;

//算出最大可以多少年

System.out.println((maxTimeStamp - minTimeStamp) / oneYearMills);

}

所以,雪花算法生成的ID只能保证69年内不会重复,如果超过69年的话,那就考虑换个服务器(服务器ID)部署,并且要保证该服务器的ID和之前都没有重复过。

前后端数值类型

在使用雪花算法时,由于生成的ID是64位,在传递给前端时,需要考虑以字符串的类型进行传递,否则可能会导致前端类型溢出,再回传到服务器时已经变成另外一个值。

这是因为Number类型的ID在JS中最大只支持53位,直接将雪花算法的生成的ID传递给JS,会导致溢出。

小结

生成唯一性ID(其他数据)是几乎在每个系统中都会有的场景,对其生成算法不仅要保证全局唯一性、趋势递增性,还要保证信息安全(比如被爬取数据),同时还要保证算法的高可用性(QPS、可行5个9、平均延时、TP999等指标)。这就对ID生成的算法有一定的要求,而雪花算法算是一个不错的选择。

但它也是有一定的缺点的,比如强依赖机器时钟,如果机器上的时钟回拨,会导致重复或服务不可用的问题,这也是我们在使用时需要注意的事项。

小天苹果皮的惊人功效(探秘小天苹果皮的保健奇迹)
就在几天之前,Canonical公司刚刚发布了Ubuntu 15.10 wily werewolf的稳定版本,而且目前大家已经可以将其下载并安装在自己的计算机当中。在今天的文章中,我们将一同了解Ubuntu 15.10新版本当中值得关注的新特性,同时探讨此次大版更新了哪些重要内容。1.Linux内核4.2Ubuntu 15.10的发布标配为Linux内核4.2。新的内核版本带来了大量变化,包括支持新的AMD GPU驱动程序、NCQ TRIM处理、排队自旋锁、F2FS单一文件加密以及其它多种全新或者经过更新的驱动程序。2.Unity 7.3.2作为Ubuntu 15.10版本的主桌面方案,Unity目前已经更新至7.3.2版本。新版本不仅修复了大量bug,同时也提升了精致水平并带来不少小规模可用性改进。下面一起来看Unity 7.3.2当中的各类新特性:◆允许用户直接从Dash当中将应用拖拽至桌面并为其创建快捷方式。◆在Dash当中按下键盘上的Page up/Page down键可实现滚动效果。◆Dash标题与BFB工具提示将根据用户的隐私设定进行更新。◆会话退出按钮现在拥有了点击特效。◆修正了屏幕锁定可能造成计算机“关闭”的问题。◆活跃应用程序图标现在会在启动器展开时以显示其详情。◆修正全屏菜单栏。◆修正了“显示桌面”可能导致同一应用的两个窗口中某一窗口可能消失的问题。◆Dash:非可扩展类型标题可通过键盘快捷键进行导航。◆Dash:非可扩展类型标题不再在鼠标悬念于其上时以高亮方式显示。◆Dash:修正屏幕阅读器与KeyNav功能。◆新增设定以控制帮助显示延时(在按下Alt键时)。◆进行逻辑调整,避免鼠标从指示图形移动到其菜单时打开相邻菜单。3.GNOME 3.16堆栈此次新版本中的另一大重要调整在于GNOME堆栈中的大部分软件包被更新至3.16.x版本。这不啻为一条好消息,因为这些软件包在更新之后普遍获得了显著改进。不过遗憾的是,Ubuntu 15.10当中的nautilus文件管理器仍然为3.14版本,而Gedit文件编辑器文件仍然为3.10版本。4.引入GNOME Overlay滚动条在Ubuntu 15.10当中,Ubuntu开发人员已经决定采用GNOME Overlay滚动条,并利用其替代面向GTK3应用程序的Unity堆叠滚动条。其本质功能与后者并无区别,不过GNOME Overlay滚动条在设计上更加鲜明,其会始终显示以提醒用户当前浏览位置,而不再像过去那样需要将鼠标移动至对应位置才能查看到其存在。5.Ubuntu Make作为一款命令行工具,Ubuntu Make允许用户以更为轻松的方式在Ubuntu之上下载各大人气开发者工具的最新版本。其现在甚至能够支持更多平台、框架与服务,其中包括一套完整的Android开发环境。6.持久网络接口名称Ubuntu开发人员还在Ubuntu 15.10版本当中引入了无状态持久网络接口名称机制,这意味着我们无需再将网络接口命名为eth0或者eth1,而能够采用更为全面的描述名称。另外,即使我们进行设备重启或者硬件移除,上述名称仍将继续生效。7.Steam手柄支持能力在此次最新版本当中,Ubuntu开发人员还在Ubuntu 15.10当中添加了对Steam手柄的支持能力。现在,经过更新的Steam软件包似乎只能运行在Ubuntu 15.10版本之上,不过未来我们也许能够利用更新补丁使其它早期版本与之相兼容。这意味着Ubuntu 15.10用户将能够接入新型手柄,开启Steam,而后在无需使用任何咨询教程的前提下顺畅进行游戏。8.新的默认壁纸Ubuntu 15.10提供一张新的默认壁纸,据称该壁纸的设计概念源自折纸工艺品。另外,新版本还包含有一组来自社区的全新壁纸。9.核心应用程序更新Ubuntu 15.10还对核心应用程序进行了更新,具体包括:◆火狐41◆Chromium 45◆LibreOffice 5.0.2◆Totem (即原本的‘视频’) 3.16◆Nautilus (即原本的‘文件’) 3.14.2◆Rhythmbox 3.2.1◆GNOME Terminal 3.16◆Eye of GNOME 3.16◆Empathy 3.12.10◆Shotwell 0.22下载Ubuntu 15.10最终版本Ubuntu 15.10的完整镜像目前已经可供大家下载与安装。其提供64位与32位两种版本,二者皆可通过以下链接通过官方ISO下载页面进行获取:下载Ubuntu 15.10
copyright © 2025 powered by 编程之道  滇ICP备2023006006号-34sitemap