雪花算法:分布式唯一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生成器,能保证全局唯一,趋势递增,但需要依赖关系数据库、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 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*/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 {//不同毫秒内,序列号置为0sequence = 0L;}lastStamp = currStamp;// 移位并通过或运算拼到一起组成64位的IDreturn (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生成的算法有一定的要求,而雪花算法算是一个不错的选择。
但它也是有一定的缺点的,比如强依赖机器时钟,如果机器上的时钟回拨,会导致重复或服务不可用的问题,这也是我们在使用时需要注意的事项。
博主简介:《SpringBoot技术内幕》技术图书作者,酷爱钻研技术,写技术干货文章。
公众号:「程序新视界」,博主的公众号,欢迎关注~
技术交流:请联系博主微信号:zhuan2quan
雪花算法:分布式唯一ID生成利器相关推荐
- 雪花算法:分布式唯一 ID 生成利器
无论是在分布式系统中的 ID 生成,还是在业务系统中请求流水号这一类唯一编号的生成,都是软件开发人员经常会面临的一场景.而雪花算法便是这些场景的一个解决方案. 以分布式 ID 为例,它的生成往往会在唯 ...
- asp按时间自动递增编号_Java秒杀系统实战系列-分布式唯一ID生成订单编号
本文是"Java秒杀系统实战系列文章"的第七篇,在本文中我们将重点介绍 "在高并发,如秒杀的业务场景下如何生成全局唯一.趋势递增的订单编号",我们将介绍两种方法 ...
- java 唯一id生成算法_唯一ID生成算法剖析
在业务开发中,大量场景需要唯一ID来进行标识:用户需要唯一身份标识:商品需要唯一标识:消息需要唯一标识:事件需要唯一标识-等等,都需要全局唯一ID,尤其是分布式场景下. 唯一ID有哪些特性或者说要求呢 ...
- Java秒杀系统实战系列~分布式唯一ID生成订单编号
摘要: 本篇博文是"Java秒杀系统实战系列文章"的第七篇,在本博文中我们将重点介绍 "在高并发,如秒杀的业务场景下如何生成全局唯一.趋势递增的订单编号",我们 ...
- java 唯一编号_Java秒杀系统实战系列~分布式唯一ID生成订单编号
摘要: 本篇博文是"Java秒杀系统实战系列文章"的第七篇,在本博文中我们将重点介绍 "在高并发,如秒杀的业务场景下如何生成全局唯一.趋势递增的订单编号",我们 ...
- 存数据返回他的序列号id_雪花般的分布式唯一ID雪花算法
点击上方 Java老铁,并选择 设为星标 优质文章和资料会及时送达 导读:唯一ID可以标识数据的唯一性,在分布式系统中生成唯一ID的方案有很多,常见的方式大概有以下三种 依赖数据库,使用如MySQL自 ...
- 雪花算法之唯一ID生成器理解
雪花算法基本情况 雪花算法是一个分布式的唯一ID生成器. 它应该具有高并发,以及高性能优点. 基于时间戳,ID具有有序性,同时分布式下机器间时间差异过大(类似同一台机器时间回拨,一定会重复),会导致重 ...
- Java架构直通车——分布式唯一 ID生成方案
文章目录 分布式ID的几种生成方案 UUID MySQL主键自增 数据库自增ID改进方案 雪花算法(SnowFlake) 雪花算法的优化 Redis自增id Zookeeper有序节点 最近要做区块链 ...
- 分布式唯一ID生成企业级方案(含时钟回拨生产级解决)
目录 分布式唯一ID要求 常见的几种方案 一. 数据库自增主键 二. UUID 三. SnowFlow算法 四. Redis自增机制 五. flickr 雅虎公司方案 六. flickr方案的高并发优 ...
最新文章
- 统治世界的 10 大算法,你知道几个?
- mysql中关于count(*) count(id)的误区
- 01两数之和(哈希表)
- 三十二、R语言基本语法(下篇)
- 利用子网掩码划分子网
- 举例说明语言接触会造成哪些结果_语言学概论全真模拟演练(二)
- mysql sys exec_python - 使用MySQL UDF执行命令-sys_exec不起作用 - 堆栈内存溢出
- ubuntu英伟达显卡驱动
- java条码扫描_JAVA生成扫描条形码
- 剪辑器更新-去水印详解
- Android保存图片到系统图库并通知系统相册刷新
- 【SDOI2013】项链 题解
- oracle中vim设置行号,vim的常用操作
- 【蓝桥杯单片机(14)】PWM波实现呼吸灯
- WIN7系统 提示缺少msvcr120.DLL,msvcr110.DLL的 请戳这里。
- influxDB自定义查询时区
- 解决数组转对象后,key自动排序
- 第11篇- 抓取免费代理IP并搭建自己的代理IP池
- sony xz2c android升9,坐稳放宽,索尼Xperia XZ2/XZ2 Compact安卓9 Pie已开始逐步推送
- 192.168.1.1是什么?192.168.1.1详细解释!
热门文章
- 谷歌双标?拒绝给员工涨薪后,转头将高管工资提高到100万美元
- 使用C++模拟拳皇出招
- G4Studio开源产品简介
- phpize Can't find PHP headers in /usr/include/php
- KMPlayer的运行时错误 - 修复的Kmplayer运行时错误的简便方法!
- 计算机主机光驱弹不出来怎么办,光驱弹不出来怎么办?电脑光驱弹不出来如何解决?...
- Java 多行字符串
- java html导出excel插件_excel插件实现html表格生成excel
- word数据源mysql,Word2013中使用Access数据库的方法
- 「宋希仁」‘九州-衷幗’傳統倫理學的特點之水哥诵読文稿2019年7月10日 星期三