概述

在生成表主键ID时,我们可以考虑主键自增 或者 UUID,但它们都有很明显的缺点

主键自增:1、自增ID容易被爬虫遍历数据。2、分表分库会有ID冲突。

UUID: 1、太长,并且有索引碎片,索引多占用空间的问题 2、无序。

雪花算法就很适合在分布式场景下生成唯一ID,它既可以保证唯一又可以排序。为了提高生产雪花ID的效率,

在这里面数据的运算都采用的是位运算

一、概念

1、原理

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

java算法之静态内部类实现雪花算法

算法描述:

1bit 因为二进制中最高位是符号位,1表示负数,0表示正数。生成的ID都是正整数,所以最高位固定为0。

41bit-时间戳 精确到毫秒级,41位的长度可以使用69年。时间位还有一个很重要的作用是可以根据时间进行排序。

10bit-工作机器id 10位的机器标识,10位的长度最多支持部署1024个节点。

12bit-序列号 序列号即一系列的自增id,可以支持同一节点同一毫秒生成多个ID序号。
12位(bit)可以表示的最大正整数是java算法之静态内部类实现雪花算法,即可以用0、1、2、3、….4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号。

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

二、静态类部类单例模式生产雪花ID代码

下面生成雪花ID的代码可以用于线上分布式项目中来生成分布式主键ID,因为设计采用的静态内部类的单例模式,通过加synchronized锁来保证在

同一个服务器线程安全。至于不同服务器其实是不相关的,因为它们的机器码是不一致的,所以就算同一时刻两台服务器都产生了雪花ID,那也不会一样的。

1、代码

public%20class%20SnowIdUtils%20{ %20%20%20%20/** %20%20%20%20%20*%20私有的%20静态内部类 %20%20%20%20%20*/ %20%20%20%20private%20static%20class%20SnowFlake%20{ %20%20%20%20%20%20%20%20/** %20%20%20%20%20%20%20%20%20*%20内部类对象(单例模式) %20%20%20%20%20%20%20%20%20*/ %20%20%20%20%20%20%20%20private%20static%20final%20SnowIdUtils.SnowFlake%20SNOW_FLAKE%20=%20new%20SnowIdUtils.SnowFlake(); %20%20%20%20%20%20%20%20/** %20%20%20%20%20%20%20%20%20*%20起始的时间戳 %20%20%20%20%20%20%20%20%20*/ %20%20%20%20%20%20%20%20private%20final%20long%20START_TIMESTAMP%20=%201557489395327L; %20%20%20%20%20%20%20%20/** %20%20%20%20%20%20%20%20%20*%20序列号占用位数 %20%20%20%20%20%20%20%20%20*/ %20%20%20%20%20%20%20%20private%20final%20long%20SEQUENCE_BIT%20=%2012; %20%20%20%20%20%20%20%20/** %20%20%20%20%20%20%20%20%20*%20机器标识占用位数 %20%20%20%20%20%20%20%20%20*/ %20%20%20%20%20%20%20%20private%20final%20long%20MACHINE_BIT%20=%2010; %20%20%20%20%20%20%20%20/** %20%20%20%20%20%20%20%20%20*%20时间戳位移位数 %20%20%20%20%20%20%20%20%20*/ %20%20%20%20%20%20%20%20private%20final%20long%20TIMESTAMP_LEFT%20=%20SEQUENCE_BIT%20+%20MACHINE_BIT; %20%20%20%20%20%20%20%20/** %20%20%20%20%20%20%20%20%20*%20最大序列号%20%20(4095) %20%20%20%20%20%20%20%20%20*/ %20%20%20%20%20%20%20%20private%20final%20long%20MAX_SEQUENCE%20=%20~(-1L%20<<%20SEQUENCE_BIT); %20%20%20%20%20%20%20%20/** %20%20%20%20%20%20%20%20%20*%20最大机器编号%20(1023) %20%20%20%20%20%20%20%20%20*/ %20%20%20%20%20%20%20%20private%20final%20long%20MAX_MACHINE_ID%20=%20~(-1L%20<<%20MACHINE_BIT); %20%20%20%20%20%20%20%20/** %20%20%20%20%20%20%20%20%20*%20生成id机器标识部分 %20%20%20%20%20%20%20%20%20*/ %20%20%20%20%20%20%20%20private%20long%20machineIdPart; %20%20%20%20%20%20%20%20/** %20%20%20%20%20%20%20%20%20*%20序列号 %20%20%20%20%20%20%20%20%20*/ %20%20%20%20%20%20%20%20private%20long%20sequence%20=%200L; %20%20%20%20%20%20%20%20/** %20%20%20%20%20%20%20%20%20*%20上一次时间戳 %20%20%20%20%20%20%20%20%20*/ %20%20%20%20%20%20%20%20private%20long%20lastStamp%20=%20-1L; %20%20%20%20%20%20%20%20/** %20%20%20%20%20%20%20%20%20*%20构造函数初始化机器编码 %20%20%20%20%20%20%20%20%20*/ %20%20%20%20%20%20%20%20private%20SnowFlake()%20{ %20%20%20%20%20%20%20%20%20%20%20%20//模拟这里获得本机机器编码 %20%20%20%20%20%20%20%20%20%20%20%20long%20localIp%20=%204321; %20%20%20%20%20%20%20%20%20%20%20%20//localIp%20&%20MAX_MACHINE_ID最大不会超过1023,在左位移12位 %20%20%20%20%20%20%20%20%20%20%20%20machineIdPart%20=%20(localIp%20&%20MAX_MACHINE_ID)%20<<%20SEQUENCE_BIT; %20%20%20%20%20%20%20%20} %20%20%20%20%20%20%20%20/** %20%20%20%20%20%20%20%20%20*%20获取雪花ID %20%20%20%20%20%20%20%20%20*/ %20%20%20%20%20%20%20%20public%20synchronized%20long%20nextId()%20{ %20%20%20%20%20%20%20%20%20%20%20%20long%20currentStamp%20=%20timeGen(); %20%20%20%20%20%20%20%20%20%20%20%20//避免机器时钟回拨 %20%20%20%20%20%20%20%20%20%20%20%20while%20(currentStamp%20<%20lastStamp)%20{ %20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20//%20//服务器时钟被调整了,ID生成器停止服务. %20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20throw%20new%20RuntimeException(String.format(“时钟已经回拨.%20%20Refusing%20to%20generate%20id%20for%20%d%20milliseconds”,%20lastStamp%20-%20currentStamp)); %20%20%20%20%20%20%20%20%20%20%20%20} %20%20%20%20%20%20%20%20%20%20%20%20if%20(currentStamp%20==%20lastStamp)%20{ %20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20//%20每次+1 %20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sequence%20=%20(sequence%20+%201)%20&%20MAX_SEQUENCE; %20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20//%20毫秒内序列溢出 %20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20(sequence%20==%200)%20{ %20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20//%20阻塞到下一个毫秒,获得新的时间戳 %20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20currentStamp%20=%20getNextMill(); %20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20} %20%20%20%20%20%20%20%20%20%20%20%20}%20else%20{ %20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20//不同毫秒内,序列号置0 %20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sequence%20=%200L; %20%20%20%20%20%20%20%20%20%20%20%20} %20%20%20%20%20%20%20%20%20%20%20%20lastStamp%20=%20currentStamp; %20%20%20%20%20%20%20%20%20%20%20%20//时间戳部分+机器标识部分+序列号部分 %20%20%20%20%20%20%20%20%20%20%20%20return%20(currentStamp%20-%20START_TIMESTAMP)%20<<%20TIMESTAMP_LEFT%20|%20machineIdPart%20|%20sequence; %20%20%20%20%20%20%20%20} %20%20%20%20%20%20%20%20/** %20%20%20%20%20%20%20%20%20*%20阻塞到下一个毫秒,直到获得新的时间戳 %20%20%20%20%20%20%20%20%20*/ %20%20%20%20%20%20%20%20private%20long%20getNextMill()%20{ %20%20%20%20%20%20%20%20%20%20%20%20long%20mill%20=%20timeGen(); %20%20%20%20%20%20%20%20%20%20%20%20// %20%20%20%20%20%20%20%20%20%20%20%20while%20(mill%20<=%20lastStamp)%20{ %20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mill%20=%20timeGen(); %20%20%20%20%20%20%20%20%20%20%20%20} %20%20%20%20%20%20%20%20%20%20%20%20return%20mill; %20%20%20%20%20%20%20%20} %20%20%20%20%20%20%20%20/** %20%20%20%20%20%20%20%20%20*%20返回以毫秒为单位的当前时间 %20%20%20%20%20%20%20%20%20*/ %20%20%20%20%20%20%20%20protected%20long%20timeGen()%20{ %20%20%20%20%20%20%20%20%20%20%20%20return%20System.currentTimeMillis(); %20%20%20%20%20%20%20%20} %20%20%20%20} %20%20%20%20/** %20%20%20%20%20*%20获取long类型雪花ID %20%20%20%20%20*/ %20%20%20%20public%20static%20long%20uniqueLong()%20{ %20%20%20%20%20%20%20%20return%20SnowIdUtils.SnowFlake.SNOW_FLAKE.nextId(); %20%20%20%20} %20%20%20%20/** %20%20%20%20%20*%20获取String类型雪花ID %20%20%20%20%20*/ %20%20%20%20public%20static%20String%20uniqueLongHex()%20{ %20%20%20%20%20%20%20%20return%20String.format(“%016x”,%20uniqueLong()); %20%20%20%20} %20%20%20%20/** %20%20%20%20%20*%20测试 %20%20%20%20%20*/ %20%20%20%20public%20static%20void%20main(String[]%20args)%20throws%20InterruptedException%20{ %20%20%20%20%20%20%20%20//计时开始时间 %20%20%20%20%20%20%20%20long%20start%20=%20System.currentTimeMillis(); %20%20%20%20%20%20%20%20//让100个线程同时进行 %20%20%20%20%20%20%20%20final%20CountDownLatch%20latch%20=%20new%20CountDownLatch(100); %20%20%20%20%20%20%20%20//判断生成的20万条记录是否有重复记录 %20%20%20%20%20%20%20%20final%20Map<Long,%20Integer>%20map%20=%20new%20ConcurrentHashMap(); %20%20%20%20%20%20%20%20for%20(int%20i%20=%200;%20i%20<%20100;%20i++)%20{ %20%20%20%20%20%20%20%20%20%20%20%20//创建100个线程 %20%20%20%20%20%20%20%20%20%20%20%20new%20Thread(()%20->%20{ %20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20(int%20s%20=%200;%20s%20<%202000;%20s++)%20{ %20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20long%20snowID%20=%20SnowIdUtils.uniqueLong(); %20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20log.info(“生成雪花ID={}”,snowID); %20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Integer%20put%20=%20map.put(snowID,%201); %20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20(put%20!=%20null)%20{ %20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20throw%20new%20RuntimeException(“主键重复”); %20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20} %20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20} %20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20latch.countDown(); %20%20%20%20%20%20%20%20%20%20%20%20}).start(); %20%20%20%20%20%20%20%20} %20%20%20%20%20%20%20%20//让上面100个线程执行结束后,在走下面输出信息 %20%20%20%20%20%20%20%20latch.await(); %20%20%20%20%20%20%20%20log.info(“生成20万条雪花ID总用时={}”,%20System.currentTimeMillis()%20-%20start); %20%20%20%20} }

2、测试结果

从图中我们可以得出

1、在100个线程并发下,生成20万条雪花ID的时间大概在1.6秒左右,所有所性能还是蛮ok的。

2、生成20万条雪花ID并没有一条相同的ID,因为有一条就会抛出异常了。

3、为什么说41位时间戳最长只能有69年

我们思考41的二进制,最大值也就41位都是1,也就是也就是说41位可以表示java算法之静态内部类实现雪花算法个毫秒的值,转化成单位年则是

我们可以通过代码泡一下就知道了。

public%20static%20void%20main(String[]%20args)%20{ %20%20%20%20//41位二进制最小值 %20%20%20%20String%20minTimeStampStr%20=%20″00000000000000000000000000000000000000000″; %20%20%20%20//41位二进制最大值 %20%20%20%20String%20maxTimeStampStr%20=%20″11111111111111111111111111111111111111111″; %20%20%20%20//转10进制 %20%20%20%20long%20minTimeStamp%20=%20new%20BigInteger(minTimeStampStr,%202).longValue(); %20%20%20%20long%20maxTimeStamp%20=%20new%20BigInteger(maxTimeStampStr,%202).longValue(); %20%20%20%20//一年总共多少毫秒 %20%20%20%20long%20oneYearMills%20=%201L%20*%201000%20*%2060%20*%2060%20*%2024%20*%20365; %20%20%20%20//算出最大可以多少年 %20%20%20%20System.out.println((maxTimeStamp%20-%20minTimeStamp)%20/%20oneYearMills); }

运行结果

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

以上就是java算法之静态内部类实现雪花算法的详细内容,更多关于java算法的资料请关注其它相关文章!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。