最近了解到SnowFlake算法,网上查了查资料,这里记录一下。

前言

分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。
       有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。
       而twitter的SnowFlake解决了这种需求,最初Twitter把存储系统从MySQL迁移到Cassandra,因为Cassandra没有顺序ID生成机制,所以开发了这样一套全局唯一ID生成服务。

1 理解分布式id生成算法SnowFlake

1.1 概述

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

0 - 41位时间戳 - 5位数据中心标识 - 5位机器标识 - 12位序列号

说明:

  • 1) 1位,不用。二进制中最高位为1的都是负数,但是我们生成的id一般都使用整数,所以这个最高位固定是0
  • 2) 41位,用来记录时间戳(毫秒)。

41位可以表示2^41−1个数字,如果只用来表示正整数(计算机中正数包含0),可以表示的数值范围是:0 至 2^41−1,减1是因为可表示的数值范围是从0开始算的,而不是1。

也就是说41位可以表示2^41−1个毫秒的值,转化成单位年则是(2^41−1)/(1000∗60∗60∗24∗365)=69年

  • 3) 10位,用来记录工作机器id。

可以部署在2^10=1024个节点,包括5位datacenterId和5位workerId

5位(bit)可以表示的最大正整数是2^5−1=31,即可以用0、1、2、3、....31这32个数字,来表示不同的datecenterId或workerId

  • 4) 12位,序列号,用来记录同毫秒内产生的不同id。

12位(bit)可以表示的最大正整数是2^12−1=4095,即可以用0、1、2、3、....4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号

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

1.2 雪花算法的作用

SnowFlake可以保证:

所有生成的id按时间趋势递增整个分布式系统内不会产生重复id(因为有datacenterId和workerId来做区分)

2 算法实现(Java)

Twitter官方给出的算法实现 是用Scala写的,这里不做分析,可自行查看。

Java版算法实现:搬运自煲煲菜的博客

2.1 实现1

public class IdWorker{//下面两个每个5位,加起来就是10位的工作机器idprivate long workerId;    //工作idprivate long datacenterId;   //数据id//12位的序列号private long sequence;public IdWorker(long workerId, long datacenterId, long sequence){// sanity check for workerIdif (workerId > maxWorkerId || workerId < 0) {throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0",maxWorkerId));}if (datacenterId > maxDatacenterId || datacenterId < 0) {throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0",maxDatacenterId));}System.out.printf("worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d",timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId);this.workerId = workerId;this.datacenterId = datacenterId;this.sequence = sequence;}//初始时间戳private long twepoch = 1288834974657L;//长度为5位private long workerIdBits = 5L;private long datacenterIdBits = 5L;//最大值private long maxWorkerId = -1L ^ (-1L << workerIdBits);private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);//序列号id长度private long sequenceBits = 12L;//序列号最大值private long sequenceMask = -1L ^ (-1L << sequenceBits);//工作id需要左移的位数,12位private long workerIdShift = sequenceBits;//数据id需要左移位数 12+5=17位private long datacenterIdShift = sequenceBits + workerIdBits;//时间戳需要左移位数 12+5+5=22位private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;//上次时间戳,初始值为负数private long lastTimestamp = -1L;public long getWorkerId(){return workerId;}public long getDatacenterId(){return datacenterId;}public long getTimestamp(){return System.currentTimeMillis();}//下一个ID生成算法public synchronized long nextId() {long timestamp = timeGen();//获取当前时间戳如果小于上次时间戳,则表示时间戳获取出现异常if (timestamp < lastTimestamp) {System.err.printf("clock is moving backwards.  Rejecting requests until %d.", lastTimestamp);throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds",lastTimestamp - timestamp));}//获取当前时间戳如果等于上次时间戳(同一毫秒内),则在序列号加一;否则序列号赋值为0,从0开始。if (lastTimestamp == timestamp) {sequence = (sequence + 1) & sequenceMask;if (sequence == 0) {timestamp = tilNextMillis(lastTimestamp);}} else {sequence = 0;}//将上次时间戳值刷新lastTimestamp = timestamp;/*** 返回结果:* (timestamp - twepoch) << timestampLeftShift) 表示将时间戳减去初始时间戳,再左移相应位数* (datacenterId << datacenterIdShift) 表示将数据id左移相应位数* (workerId << workerIdShift) 表示将工作id左移相应位数* | 是按位或运算符,例如:x | y,只有当x,y都为0的时候结果才为0,其它情况结果都为1。* 因为个部分只有相应位上的值有意义,其它位上都是0,所以将各部分的值进行 | 运算就能得到最终拼接好的id*/return ((timestamp - twepoch) << timestampLeftShift) |(datacenterId << datacenterIdShift) |(workerId << workerIdShift) |sequence;}//获取时间戳,并与上次时间戳比较private long tilNextMillis(long lastTimestamp) {long timestamp = timeGen();while (timestamp <= lastTimestamp) {timestamp = timeGen();}return timestamp;}//获取系统时间戳private long timeGen(){return System.currentTimeMillis();}//---------------测试---------------public static void main(String[] args) {IdWorker worker = new IdWorker(1,1,1);for (int i = 0; i < 30; i++) {System.out.println(worker.nextId());}}}

2.2 实现2

java版的雪花算法:

 public class SnowFlake {/*** 起始的时间戳:这个时间戳自己随意获取,比如自己代码的时间戳*/private final static long START_STMP = 1543903501000L;/*** 每一部分占用的位数*/private final static long SEQUENCE_BIT = 12; //序列号占用的位数private final static long MACHINE_BIT = 5;  //机器标识占用的位数private final static long DATACENTER_BIT = 5;//数据中心占用的位数/*** 每一部分的最大值:先进行左移运算,再同-1进行异或运算;异或:相同位置相同结果为0,不同结果为1*//** 用位运算计算出最大支持的数据中心数量:31 */private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);/** 用位运算计算出最大支持的机器数量:31 */private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);/** 用位运算计算出12位能存储的最大正整数:4095 */private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);/*** 每一部分向左的位移*//** 机器标志较序列号的偏移量 */private final static long MACHINE_LEFT = SEQUENCE_BIT;/** 数据中心较机器标志的偏移量 */private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;/** 时间戳较数据中心的偏移量 */private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;private static long datacenterId;  //数据中心private static long machineId;    //机器标识private static long sequence = 0L; //序列号private static long lastStmp = -1L;//上一次时间戳/** 此处无参构造私有,同时没有给出有参构造,在于避免以下两点问题:1、私有化避免了通过new的方式进行调用,主要是解决了在for循环中通过new的方式调用产生的id不一定唯一问题问题,因为用于          记录上一次时间戳的lastStmp永远无法得到比对;2、没有给出有参构造在第一点的基础上考虑了一套分布式系统产生的唯一序列号应该是基于相同的参数*/private SnowFlake(){}/*** 产生下一个ID** @return*/public static synchronized long nextId() {/** 获取当前时间戳 */long currStmp = getNewstmp();/** 如果当前时间戳小于上次时间戳则抛出异常 */if (currStmp < lastStmp) {throw new RuntimeException("Clock moved backwards.  Refusing to generate id");}/** 相同毫秒内 */if (currStmp == lastStmp) {//相同毫秒内,序列号自增sequence = (sequence + 1) & MAX_SEQUENCE;//同一毫秒的序列数已经达到最大if (sequence == 0L) {/** 获取下一时间的时间戳并赋值给当前时间戳 */currStmp = getNextMill();}} else {//不同毫秒内,序列号置为0sequence = 0L;}/** 当前时间戳存档记录,用于下次产生id时对比是否为相同时间戳 */lastStmp = currStmp;return (currStmp - START_STMP) << TIMESTMP_LEFT //时间戳部分| datacenterId << DATACENTER_LEFT      //数据中心部分| machineId << MACHINE_LEFT            //机器标识部分| sequence;                            //序列号部分}private static long getNextMill() {long mill = getNewstmp();while (mill <= lastStmp) {mill = getNewstmp();}return mill;}private static long getNewstmp() {return System.currentTimeMillis();}}

参考:

https://github.com/twitter-archive/snowflake

https://juejin.im/post/6844903783437172743

https://oss.navercorp.com/works-mobile/oneapp-proxy/releases

https://github.com/beyondfengyu/SnowFlake

Twitter雪花算法SnowFlake介绍相关推荐

  1. Twitter的分布式雪花算法 SnowFlake 每秒自增生成26个万个可排序的ID (Java版)

    分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的. 有些时候我们希望能使用一种简单一 ...

  2. Twitter的分布式雪花算法 SnowFlake

    为什么80%的码农都做不了架构师?>>>    原理 Twitter的雪花算法SnowFlake,使用Java语言实现. SnowFlake算法产生的ID是一个64位的整型,结构如下 ...

  3. 分布式主键解决方案----Twitter 雪花算法的原理(Java 版)

    SnowFlake 雪花算法 对于分布式系统环境,主键ID的设计很关键,什么自增intID那些是绝对不用的,比较早的时候,大部分系统都用UUID/GUID来作为主键,优点是方便又能解决问题,缺点是插入 ...

  4. java 有穷自动机_Java实现雪花算法(snowflake)

    本文主要介绍了Java实现雪花算法(snowflake),分享给大家,具体如下: 简单描述 最高位是符号位,始终为0,不可用. 41位的时间序列,精确到毫秒级,41位的长度可以使用69年.时间位还有一 ...

  5. mysql snowflake_雪花算法-snowflake

    雪花算法-snowflake 分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的. 有 ...

  6. java雪花_Java实现雪花算法(snowflake)

    本文主要介绍了Java实现雪花算法(snowflake),分享给大家,具体如下: 简单描述 最高位是符号位,始终为0,不可用. 41位的时间序列,精确到毫秒级,41位的长度可以使用69年.时间位还有一 ...

  7. 雪花算法snowflake分布式id生成原理详解,以及对解决时钟回拨问题几种方案讨论

    文章目录 一.前言 二.雪花算法snowflake 1.基本定义 2.snowflake的优缺点 三.Java代码实现snowflake 1.组装生成id 2.计算最大值的几种方式 3.反解析ID 4 ...

  8. 发号器:雪花算法(Snowflake)

    雪花算法(Snowflake)是twitter公司内部分布式项目采用的ID生成算法,开源后广受国内大厂的好评,在该算法影响下各大公司相继开发出各具特色的分布式生成器. 第一个bit位(1bit):Ja ...

  9. 雪花算法-Snowflake Snowflake

    雪花算法-Snowflake Snowflake,雪花算法是由Twitter开源的分布式ID生成算法,以划分命名空间的方式将 64-bit位分割成多个部分,每个部分代表不同的含义.而 Java中64b ...

最新文章

  1. 洛谷 1608 路径统计
  2. JVM的内存结构,Eden和Survivor比例;JVM中一次完整的GC流程,对象如何晋升到老年代,说说你知道的几种主要的JVM参数;CMS 常见参数解析;.你知道哪几种垃圾收集器,各自的优缺点
  3. linux升级补丁tar,Linux下Bash严重漏洞补丁升级方法
  4. 二叉搜索树的插入、删除、修剪、构造操作(leetcode701、450、669、108)
  5. android 隐藏键盘时ui延迟恢复,android 软键盘的显示与隐藏问题的研究
  6. linux 笔记之一mysql源码包安装
  7. 悬赏百万美金,检测Deepfake假视频,数据集有470G:很久不见这么壕的比赛
  8. 荣耀5G手机加速追赶:最快将于下半年登场
  9. 基于JAVA+SpringBoot+Mybatis+MYSQL的校园新闻管理系统
  10. 移动端报表JS开发示例
  11. Xcode 4-PBXcp error修复-No such file or directory
  12. 双点双向路由引入/路由重发布
  13. dp交换机命令_交换机常用指令总结
  14. linux 服务器远程开机,Linux 下实现远程开机
  15. 在excel的单元格中设置下拉菜单
  16. Package | 解决 Could not build wheels for opencv-python which use PEP 517 and cannot be installed
  17. Python发送多附件邮件的方法
  18. 【SVN】SVN搭建以及客户端使用|错误记录
  19. FXCM福汇官网 fx-aisa.com外汇交易中,你必须了解的八种主流货币知识
  20. 服务器 虚拟gpu,云服务器 虚拟gpu

热门文章

  1. 1MB等于多少BYTE?8086/8088寻址段地址与偏移地址解释
  2. JavaScript 数组升序降序 【超实用】
  3. 球球拼音输入发的用户体验
  4. 微信小程序使用阿里图标库(iconfont)封装自定义的icon图标组件
  5. IOS 传感器Core Motion相关简述
  6. 股票买入卖出 LeetCode 变形题 度小满
  7. 不可逆调速matlab,双闭环不可逆直流调速系统课程设计(matlab仿真设计).pdf
  8. 用python计算ph,用python下载PH上的学习视频
  9. 最小化安装debian11
  10. 【RT-Thread开源作品秀】供电所综合网关(1)