一、概述

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

●  1位,不用。二进制中最高位为1的都是负数,但是我们生成的id一般都使用整数,所以这个最高位固定是0

●  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$年

●  10位,用来记录工作机器id。
         ○  可以部署在$2^{10} = 1024$个节点,包括 5位datacenterId 和 5位workerId
         ○  5位(bit)可以表示的最大正整数是$2^{5}-1 = 31$,即可以用0、1、2、3、....31这32个数字,来表示不同的datecenterId或workerId

●  12位,序列号,用来记录同毫秒内产生的不同id。
         ○  12位(bit)可以表示的最大正整数是$2^{12}-1 = 4095$,即可以用0、1、2、3、....4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号

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

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

二、使用

网上的教程一般存在两个问题:
          1. 机器ID(5位)和数据中心ID(5位)配置没有解决,分布式部署的时候会使用相同的配置,任然有ID重复的风险。
          2. 使用的时候需要实例化对象,没有形成开箱即用的工具类。

本文针对上面两个问题进行解决,笔者的解决方案是,workId使用服务器hostName生成,dataCenterId使用IP生成,这样可以最大限度防止10位机器码重复,但是由于两个ID都不能超过32,只能取余数,还是难免产生重复,但是实际使用中,hostName和IP的配置一般连续或相近,只要不是刚好相隔32位,就不会有问题,况且,hostName和IP同时相隔32的情况更加是几乎不可能的事,平时做的分布式部署,一般也不会超过10台容器。

使用上面的方法可以零配置使用雪花算法,雪花算法10位机器码的设定理论上可以有1024个节点,生产上使用docker配置一般是一次编译,然后分布式部署到不同容器,不会有不同的配置,这里不知道其他公司是如何解决的,即使有方法使用一套配置,然后运行时根据不同容器读取不同的配置,但是给每个容器编配ID,1024个(大部分情况下没有这么多),似乎也不太可能,此问题留待日后解决后再行补充。

具体生成 workId 和 dataCenterId 的方法如下:

 private static Long getWorkId(){try {String hostAddress = Inet4Address.getLocalHost().getHostAddress();int[] ints = StringUtils.toCodePoints(hostAddress);int sums = 0;for(int b : ints){sums += b;}return (long)(sums % 32);} catch (UnknownHostException e) {// 如果获取失败,则使用随机数备用return RandomUtils.nextLong(0,31);}}private static Long getDataCenterId(){int[] ints = StringUtils.toCodePoints(SystemUtils.getHostName());int sums = 0;for (int i: ints) {sums += i;}return (long)(sums % 32);}

使用上面的方法需要增加Apache Commons lang3 的依赖,这也是此方法的缺点,但是在实际使用的时候,lang3这个类一般也是要引入的,非常非常好用,提高效率的利器 (注意:这里的commons-lang3必须是 3.8版本或者更高版本,否则低于这个版本会报没有toCodePoints(CharSequence str) 和 getHostName() 方法)。

<dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.8</version>
</dependency>

最终的完整代码如下:

package com.my.blog.website.utils;import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;import java.net.Inet4Address;
import java.net.UnknownHostException;/*** Twitter_Snowflake<br>* SnowFlake的结构如下(每部分用-分开):<br>* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>* 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br>* 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)* 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>* 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId<br>* 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br>* 加起来刚好64位,为一个Long型。<br>* SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。*/
public class SnowflakeIdWorker {// ==============================Fields===========================================/** 开始时间截 (2015-01-01) */private final long twepoch = 1489111610226L;/** 机器id所占的位数 */private final long workerIdBits = 5L;/** 数据标识id所占的位数 */private final long dataCenterIdBits = 5L;/** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */private final long maxWorkerId = -1L ^ (-1L << workerIdBits);/** 支持的最大数据标识id,结果是31 */private final long maxDataCenterId = -1L ^ (-1L << dataCenterIdBits);/** 序列在id中占的位数 */private final long sequenceBits = 12L;/** 机器ID向左移12位 */private final long workerIdShift = sequenceBits;/** 数据标识id向左移17位(12+5) */private final long dataCenterIdShift = sequenceBits + workerIdBits;/** 时间截向左移22位(5+5+12) */private final long timestampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits;/** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */private final long sequenceMask = -1L ^ (-1L << sequenceBits);/** 工作机器ID(0~31) */private long workerId;/** 数据中心ID(0~31) */private long dataCenterId;/** 毫秒内序列(0~4095) */private long sequence = 0L;/** 上次生成ID的时间截 */private long lastTimestamp = -1L;private static SnowflakeIdWorker idWorker;static {idWorker = new SnowflakeIdWorker(getWorkId(),getDataCenterId());}//==============================Constructors=====================================/*** 构造函数* @param workerId 工作ID (0~31)* @param dataCenterId 数据中心ID (0~31)*/public SnowflakeIdWorker(long workerId, long dataCenterId) {if (workerId > maxWorkerId || workerId < 0) {throw new IllegalArgumentException(String.format("workerId can't be greater than %d or less than 0", maxWorkerId));}if (dataCenterId > maxDataCenterId || dataCenterId < 0) {throw new IllegalArgumentException(String.format("dataCenterId can't be greater than %d or less than 0", maxDataCenterId));}this.workerId = workerId;this.dataCenterId = dataCenterId;}// ==============================Methods==========================================/*** 获得下一个ID (该方法是线程安全的)* @return SnowflakeId*/public synchronized long nextId() {long timestamp = timeGen();//如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常if (timestamp < lastTimestamp) {throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));}//如果是同一时间生成的,则进行毫秒内序列if (lastTimestamp == timestamp) {sequence = (sequence + 1) & sequenceMask;//毫秒内序列溢出if (sequence == 0) {//阻塞到下一个毫秒,获得新的时间戳timestamp = tilNextMillis(lastTimestamp);}}//时间戳改变,毫秒内序列重置else {sequence = 0L;}//上次生成ID的时间截lastTimestamp = timestamp;//移位并通过或运算拼到一起组成64位的IDreturn ((timestamp - twepoch) << timestampLeftShift)| (dataCenterId << dataCenterIdShift)| (workerId << workerIdShift)| sequence;}/*** 阻塞到下一个毫秒,直到获得新的时间戳* @param lastTimestamp 上次生成ID的时间截* @return 当前时间戳*/protected long tilNextMillis(long lastTimestamp) {long timestamp = timeGen();while (timestamp <= lastTimestamp) {timestamp = timeGen();}return timestamp;}/*** 返回以毫秒为单位的当前时间* @return 当前时间(毫秒)*/protected long timeGen() {return System.currentTimeMillis();}private static Long getWorkId(){try {String hostAddress = Inet4Address.getLocalHost().getHostAddress();int[] ints = StringUtils.toCodePoints(hostAddress);int sums = 0;for(int b : ints){sums += b;}return (long)(sums % 32);} catch (UnknownHostException e) {// 如果获取失败,则使用随机数备用return RandomUtils.nextLong(0,31);}}private static Long getDataCenterId(){int[] ints = StringUtils.toCodePoints(SystemUtils.getHostName());int sums = 0;for (int i: ints) {sums += i;}return (long)(sums % 32);}/*** 静态工具类** @return*/public static synchronized Long generateId(){long id = idWorker.nextId();return id;}//==============================Test=============================================/** 测试 */public static void main(String[] args) {System.out.println(System.currentTimeMillis());long startTime = System.nanoTime();for (int i = 0; i < 50000; i++) {long id = SnowflakeIdWorker.generateId();System.out.println(id);}System.out.println((System.nanoTime()-startTime)/1000000+"ms");}
}

分布式自增ID生成算法 - 雪花算法(SnowFlake)相关推荐

  1. [分布式] ------ 全局唯一id生成之雪花算法(Twitter_Snowflake)

    雪花算法(Twitter_Snowflake) 我们知道,分布式全局唯一id的生成,一般是以下几种: 基于雪花算法生成 基于数据库 基于redis 基于zookeeper 本文说下雪花算法,后面附源码 ...

  2. SpringBoot实战:设备唯一ID生成【雪花算法、分布式应用】

    目录 SpringBoot实战:设备唯一ID生成[雪花算法.分布式应用] 背景: snowflake(雪花算法)方案: 实现: 雪花算法生成ID: 二维码打包: 多线程优化-批量插入: 二维码识别+扫 ...

  3. 全局唯一id生成之雪花算法

    雪花算法(Twitter_Snowflake) 我们知道,分布式全局唯一id的生成,一般是以下几种: 基于雪花算法生成 基于数据库 基于redis 基于zookeeper 本文说下雪花算法,后面附源码 ...

  4. 微服务架构分布式全局唯一ID生成策略及算法

    全局唯一的 ID 几乎是所有系统都会遇到的刚需.这个 id 在搜索, 存储数据, 加快检索速度 等等很多方面都有着重要的意义.工业上有多种策略来获取这个全局唯一的id,针对常见的几种场景,我在这里进行 ...

  5. 分布式自增ID雪花算法snowflake (Java版)

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

  6. Twitter的分布式自增ID算法Snowflake实现分析及其Java、Php和Python版

    在分布式系统中,需要生成全局UID的场合还是比较多的,twitter的snowflake解决了这种需求,实现也还是很简单的,除去配置信息,核心代码就是毫秒级时间41位+机器ID 10位+毫秒内序列12 ...

  7. Twitter的分布式自增ID算法snowflake (Java版)

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

  8. [详解]Twitter开源分布式自增ID算法snowflake,附演算验证过程

    1.snowflake简介 互联网快速发展的今天,分布式应用系统已经见怪不怪,在分布式系统中,我们需要各种各样的ID,既然是ID那么必然是要保证全局唯一,除此之外,不同当业务还需要不同的特性,比如像并 ...

  9. 分布式自增ID算法-Snowflake详解

    1.Snowflake简介 互联网快速发展的今天,分布式应用系统已经见怪不怪,在分布式系统中,我们需要各种各样的ID,既然是ID那么必然是要保证全局唯一,除此之外,不同当业务还需要不同的特性,比如像并 ...

最新文章

  1. 俄罗斯的顶级数学家,到底有多恐怖?
  2. c++与java中子类中调用父类成员的方法
  3. Mysql半双工主从复制
  4. 怎么建立微信生态用户增长模型?
  5. 不同vlan之间如何ping通_【丰润达.安防百科】如何实现交换机不同VLAN、不同网段之间互访?...
  6. 2017.10.9 找相同字符 失败总结
  7. 上岗乌镇大会安防的智慧警眼“云镜”,是谁家的AR眼镜?
  8. python高性能_Python高性能分布式执行框架-Ray
  9. pycharm的安装及破解
  10. 有点意思!“古董级” 诺基亚功能机跑 Linux
  11. 云运维拓扑图_云平台网络拓扑图
  12. 中国室内定位系统LBS市场现状研究分析与发展前景预测报告(2022)
  13. 粒子群优化能做相机标定吗
  14. FFmpeg(5) -- 相关工具类
  15. OpenCV 图像梯度 :cv2.Sobel(),cv2.Schar(),cv2.Laplacian() + 数据类型设置:cv2.CV_8U,cv2.CV_16S,cv2.CV_64F
  16. Object.defineProperty方法的简单介绍
  17. STM32通过SDIO读取SD卡,FATFS文件管理系统
  18. 触宝纽交所上市:十年磨一剑 要打造世界一流AI公司
  19. Euraka 服务注册与发现
  20. php学生分班,“分班”内幕与分班的意义

热门文章

  1. IT项目管理 第五章 习题
  2. 计算机去掉word2007,电脑打不开所有word2007文档怎么弄?word2007启动不了的解决方法...
  3. 如何仿写iOS微信打飞机
  4. 百宝云网络验证V4免费版发卡操作流程
  5. 科技爱好者周刊(第 203 期):英国的名校签证,伯克利的计算机教育
  6. 家是文艺腔,更是烟火气
  7. 少儿编程,打响2021中国教育改革第一枪
  8. z7 kp7s1 linux,神舟Z7-KP7S1升级七代处理器上手评测
  9. 在线教育平台如何抓住知识付费“风口”带来的机会
  10. [摘录]高效人士七习惯—知己知彼原则