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

概述

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

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

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

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

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

一、概念

1、原理

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

算法描述:

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

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

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

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

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

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

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

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

1、代码

public class SnowIdUtils {
    /**
     * 私有的 静态内部类
     */
    private static class SnowFlake {

        /**
         * 内部类对象(单例模式)
         */
        private static final SnowIdUtils.SnowFlake SNOW_FLAKE = new SnowIdUtils.SnowFlake();
        /**
         * 起始的时间戳
         */
        private final long START_TIMESTAMP = 1557489395327L;
        /**
         * 序列号占用位数
         */
        private final long SEQUENCE_BIT = 12;
        /**
         * 机器标识占用位数
         */
        private final long MACHINE_BIT = 10;
        /**
         * 时间戳位移位数
         */
        private final long TIMESTAMP_LEFT = SEQUENCE_BIT + MACHINE_BIT;
        /**
         * 最大序列号  (4095)
         */
        private final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);
        /**
         * 最大机器编号 (1023)
         */
        private final long MAX_MACHINE_ID = ~(-1L << MACHINE_BIT);
        /**
         * 生成id机器标识部分
         */
        private long machineIdPart;
        /**
         * 序列号
         */
        private long sequence = 0L;
        /**
         * 上一次时间戳
         */
        private long lastStamp = -1L;

        /**
         * 构造函数初始化机器编码
         */
        private SnowFlake() {
            //模拟这里获得本机机器编码
            long localIp = 4321;
            //localIp & MAX_MACHINE_ID最大不会超过1023,在左位移12位
            machineIdPart = (localIp & MAX_MACHINE_ID) << SEQUENCE_BIT;
        }
        /**
         * 获取雪花ID
         */
        public synchronized long nextId() {
            long currentStamp = timeGen();
            //避免机器时钟回拨
            while (currentStamp < lastStamp) {
                // //服务器时钟被调整了,ID生成器停止服务.
                throw new RuntimeException(String.format("时钟已经回拨.  Refusing to generate id for %d milliseconds", lastStamp - currentStamp));
            }
            if (currentStamp == lastStamp) {
                // 每次+1
                sequence = (sequence + 1) & MAX_SEQUENCE;
                // 毫秒内序列溢出
                if (sequence == 0) {
                    // 阻塞到下一个毫秒,获得新的时间戳
                    currentStamp = getNextMill();
                }
            } else {
                //不同毫秒内,序列号置0
                sequence = 0L;
            }
            lastStamp = currentStamp;
            //时间戳部分+机器标识部分+序列号部分
            return (currentStamp - START_TIMESTAMP) << TIMESTAMP_LEFT | machineIdPart | sequence;
        }
        /**
         * 阻塞到下一个毫秒,直到获得新的时间戳
         */
        private long getNextMill() {
            long mill = timeGen();
            //
            while (mill <= lastStamp) {
                mill = timeGen();
            }
            return mill;
        }
        /**
         * 返回以毫秒为单位的当前时间
         */
        protected long timeGen() {
            return System.currentTimeMillis();
        }
    }

    /**
     * 获取long类型雪花ID
     */
    public static long uniqueLong() {
        return SnowIdUtils.SnowFlake.SNOW_FLAKE.nextId();
    }
    /**
     * 获取String类型雪花ID
     */
    public static String uniqueLongHex() {
        return String.format("%016x", uniqueLong());
    }

    /**
     * 测试
     */
    public static void main(String[] args) throws InterruptedException {
        //计时开始时间
        long start = System.currentTimeMillis();
        //让100个线程同时进行
        final CountDownLatch latch = new CountDownLatch(100);
        //判断生成的20万条记录是否有重复记录
        final Map<Long, Integer> map = new ConcurrentHashMap();
        for (int i = 0; i < 100; i++) {
            //创建100个线程
            new Thread(() -> {
                for (int s = 0; s < 2000; s++) {
                    long snowID = SnowIdUtils.uniqueLong();
                    log.info("生成雪花ID={}",snowID);
                    Integer put = map.put(snowID, 1);
                    if (put != null) {
                        throw new RuntimeException("主键重复");
                    }
                }
                latch.countDown();
            }).start();
        }
        //让上面100个线程执行结束后,在走下面输出信息
        latch.await();
        log.info("生成20万条雪花ID总用时={}", System.currentTimeMillis() - start);
    }
}

2、测试结果

从图中我们可以得出

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

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

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

我们思考41的二进制,最大值也就41位都是1,也就是也就是说41位可以表示个毫秒的值,转化成单位年则是

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

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和之前都没有重复过。

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

(0)

相关推荐

  • java中gc算法实例用法

    在我们对gc中的算法有基本概念理解后,要把算法的理念实现还需要依托实际垃圾收集器的使用.因为光靠一些简单的原理不足以支撑整个程序的运行,在回收机制上有专门的收集器.下面我们就垃圾收集器的概念.使用注意事项.收集器图解进行介绍,然后带来两种常见的垃圾收集器供大家参考. 1.概念 垃圾收集器时之前列举的垃圾收集算法的具体实现. 2.注意事项 每一个回收器都存在Stop The World 的问题,只不过各个回收器在Stop The World 时间优化程度.算法的不同,可根据自身需求选择适合的回收器

  • Java算法之数组冒泡排序代码实例讲解

    冒泡排序是数组查找算法中最为简单的算法 冒泡排序原理: 假设一个数组长度为k(最高索引k-1),遍历前k - 1个(最高索引k-2)元素,若数组中的元素a[i]都与相邻的下一个元素a[i+1]进行比较,若a[i] > a[i+1] ,则这两个元素交换位置.以此类推,若a[i+1] > a[i+2],则交换位置-直至a[k-2]与a[k-1]比较完毕后,第0轮迭代结束.此时,a[k-1]为数组元素中的最大值. 第1轮迭代,再对数组a的前k-1个元素重复进行以上操作. - 第k-2轮迭代,对数组a

  • Java数据结构与算法入门实例详解

    第一部分:Java数据结构 要理解Java数据结构,必须能清楚何为数据结构? 数据结构: Data_Structure,它是储存数据的一种结构体,在此结构中储存一些数据,而这些数据之间有一定的关系. 而各数据元素之间的相互关系,又包括三个组成成分,数据的逻辑结构,数据的存储结构和数据运算结构. 而一个数据结构的设计过程分成抽象层.数据结构层和实现层. 数据结构在Java的语言体系中按逻辑结构可以分为两大类:线性数据结构和非线性数据结构. 一.Java数据结构之:线性数据结构 线性数据结构:常见的

  • Java实现雪花算法的原理

    SnowFlake 算法,是 Twitter 开源的分布式 id 生成算法.其核心思想就是:使用一个 64 bit 的 long 型的数字作为全局唯一 id.在分布式系统中的应用十分广泛,且ID 引入了时间戳,基本上保持自增的,后面的代码中有详细的注解. 这 64 个 bit 中,其中 1 个 bit 是不用的,然后用其中的 41 bit 作为毫秒数,用 10 bit 作为工作机器 id,12 bit 作为序列号. 给大家举个例子吧,比如下面那个 64 bit 的 long 型数字: 第一个部分

  • 使用java写的矩阵乘法实例(Strassen算法)

    Strassen算法于1969年由德国数学家Strassen提出,该方法引入七个中间变量,每个中间变量都只需要进行一次乘法运算.而朴素算法却需要进行8次乘法运算. 原理 Strassen算法的原理如下所示,使用sympy验证Strassen算法的正确性 import sympy as s A = s.Symbol("A") B = s.Symbol("B") C = s.Symbol("C") D = s.Symbol("D"

  • java实现同态加密算法的实例代码

    什么是同态加密? 同态加密是上世纪七十年代就被提出的一个开放问题,旨在不暴露数据的情况下完成对数据的处理,关注的是数据处理安全. 想象一下这样一个场景,作为一名满怀理想的楼二代,你每天过着枯燥乏味的收租生活,希望摆脱世俗的枷锁.铜臭的苟且去追求诗与远方. 你需要雇一个代理人去承担收租的粗活,但又不希望其窥探你每月躺赚的收入.于是,你请高人打造了一套装备,既能保证代理人顺利完成收租,又不会泄露收入信息. 这套装备包括信封.胶水.皮夹和神奇剪刀,每一样东西都有奇特的功能: 信封一旦用胶水密封,只有神

  • java实现国产sm4加密算法

    前言 今天给大家带来一个国产SM4加密解密算法的java后端解决方案,代码完整,可以直接使用,希望给大家带来帮助,尤其是做政府系统的开发人员,可以直接应用到项目中进行加密解密. 画重点!是SM4哦,不是SM.哈哈,各位要在知识里遨游,不要想歪.正文开始~ 国产SM4加密解密算法概念介绍 SMS4算法是在国内广泛使用的WAPI无线网络标准中使用的加密算法,是一种32轮的迭代非平衡Feistel结构的分组加密算法,其密钥长度和分组长度均为128.SMS4算法的加解密过程中使用的算法是完全相同的,唯一

  • java递归实现科赫雪花

    今天来写k阶科赫雪花的递归实现,(K值需要你手动输入)至于科赫雪花是什么请大家自行百度. 首先来思考这个程序怎么写,当 count = 0 时就应该是一个三角形,这三个点是你一开始就确定的,以后的改变都依据这三个点发展的.当不是0的时候就需要计算相对于这个三角形的9个点,分别是每条边上的两个点,和它对应的三角形第三个顶点. 首先在JFrame中添加一个panel,我们需要在这个panel上画图. 大家再来看这个图片,这张图介绍了通过两个点来计算其他三个点的过程. 现在开始在panel中画图: s

  • Java实现雪花算法(snowflake)

    本文主要介绍了Java实现雪花算法(snowflake),分享给大家,具体如下: 简单描述 最高位是符号位,始终为0,不可用. 41位的时间序列,精确到毫秒级,41位的长度可以使用69年.时间位还有一个很重要的作用是可以根据时间进行排序.注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截) 后得到的值,这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序SnowFlake类的START_STMP属性).41位的时间截

  • java分形绘制科赫雪花曲线(科赫曲线)代码分享

    首先我们举个例子:我们可以看到西兰花一小簇是整个花簇的一个分支,而在不同尺度下它们具有自相似的外形.换句话说,较小的分支通过放大适当的比例后可以得到一个与整体几乎完全一致的花簇.因此我们可以说西兰花簇是一个分形的实例.分形一般有以下特质:在任意小的尺度上都能有精细的结构: 太不规则,以至难以用传统欧氏几何的语言描述: (至少是大略或任意地)自相似豪斯多夫维数会大於拓扑维数: 有著简单的递归定义.(i)分形集都具有任意小尺度下的比例细节,或者说它具有精细的结构.(ii)分形集不能用传统的几何语言来

随机推荐