• 雪花算法基本原理与实现


    what:SnowFlake算法是什么?

    是一个id生成算法,它使用一个64bit的long型的数字作为全局唯一的id。

    由于越来越多的公司都在使用分布式、微服务,那么就会对于不同的服务进行数据库拆分,然后当数据量上来的时候会进行分表,那么就会面临分表之后的id问题。

    什么是分布式?

    “将一个系统拆分成朵儿子系统并分布到不同设配的过程”
    实现一个分布式系统,最核心的部分1.如何拆分、2.如何连接

    什么是微服务?

    专注单一职责和功能的小型功能区块为基础,利用模组化的方式组合出的大象应用程序,各功能区块使用与语言无关的api集相互通信。

    如之前做单体项目中的一个表的数据主键id都是自增长的,例如mysql通过autoincrement来实现自增长,oracle通过序列来实现。但是当数据量上来,一般就会进行水平分表,阿里开发建议单表的数据了达到了500w的时候就需要进行分表。分表就是将一张表的数据分为多张表,那么问题就来了,由于之间的主键的id都是自增长,那么分表之后主键id就会产生重复的问题。这个之后就需要考虑用什么办法来解决主键id重复的问题。

    why:为什么要使用SnowFlake

    首先它主要为在分布式系统中生成唯一的ID,并且它可以满足每秒为上万条消息分配ID的请求,并且这些ID是唯一并且有导致的递增顺序,也便于Mysql的InnoDB进行数据存储。

    雪花算法与UUID进行对比,都能够实现唯一标识ID。

    雪花算法优势:

    毫秒数在高位,自增序列在低位,整个ID都是趋势递增,有利于Mysql存储。
    生成ID的性能高,并且可以根据系统业务的需要,通过改变bit位数,能够灵活的改变ID的位数

    雪花算法劣势:

    由于雪花算法依赖于机器的当前时间,如果机器时间回拨,将会导致ID重复。

    UUID优势:

    UUID本地生成,性能高,并且没有网络消耗

    UUID劣势:

    由于UUID是无序的会影响存储性能的问题
    UUID相对于数字来说存储空间大,并且查询也相对慢

    Who When Where雪花算法实现的原理

    雪花算法原理:使用64bit的long型数字作为全局唯一的id,这64bit中,第一个bit是不用的,之后的41个bit作为毫秒数,之后的10个bit作为工作机器的id,最后12个bit作为序列号。
    在这里插入图片描述
    在二进制中如果第一个bit是1,那么都是负数,但是我们生成的id一般都是正数,所以这儿的第一个bit同意都是0.

    41位bit:表示时间戳,单位是毫秒

    41位bit存储的是毫秒级别的时间戳,2^41/(1000606024365)=69.73所以大概可以使用接近70年。

    10位bit存储机器码
    5位机房id和5位机器id最多可以布置2^10=1024 台机器。

    12位bit
    12位bit表示最大整数 2 ^ 12 =4096,表示能在一毫秒内生成4096个不同的id

    雪花算法代码实现

    public class IdGenerate {
    
        //因为二进制里第一个 bit 为如果是 1,那么都是负数,但是我们生成的 id 都是正数,所以第一个 bit 统一都是 0。
    
        //机器ID  2进制5位  32位减掉1位 31个
        private long workerId;
        //机房ID 2进制5位  32位减掉1位 31个
        private long datacenterId;
        //代表一毫秒内生成的多个id的最新序号  12位 4096 -1 = 4095 个
        private long sequence;
        //设置一个时间初始值    2^41 - 1   差不多可以用69年
        private long twepoch = 1585644268888L;
        //5位的机器id
        private long workerIdBits = 5L;
        //5位的机房id
        private long datacenterIdBits = 5L;
        //每毫秒内产生的id数 2 的 12次方
        private long sequenceBits = 12L;
        // 这个是二进制运算,就是5 bit最多只能有31个数字,也就是说机器id最多只能是32以内
        private long maxWorkerId = -1L ^ (-1L << workerIdBits);
        // 这个是一个意思,就是5 bit最多只能有31个数字,机房id最多只能是32以内
        private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    
        private long workerIdShift = sequenceBits;
        private long datacenterIdShift = sequenceBits + workerIdBits;
        private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
        private long sequenceMask = -1L ^ (-1L << sequenceBits);
        //记录产生时间毫秒数,判断是否是同1毫秒
        private long lastTimestamp = -1L;
        public long getWorkerId(){
            return workerId;
        }
        public long getDatacenterId() {
            return datacenterId;
        }
        public long getTimestamp() {
            return System.currentTimeMillis();
        }
    
        public IdGenerate(){}
    
        public IdGenerate(long workerId, long datacenterId, long sequence) {
    
            // 检查机房id和机器id是否超过31 不能小于0
            if (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));
            }
            this.workerId = workerId;
            this.datacenterId = datacenterId;
            this.sequence = sequence;
        }
    
        // 这个是核心方法,通过调用nextId()方法,让当前这台机器上的snowflake算法程序生成一个全局唯一的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));
            }
    
            // 下面是说假设在同一个毫秒内,又发送了一个请求生成一个id
            // 这个时候就得把seqence序号给递增1,最多就是4096
            if (lastTimestamp == timestamp) {
    
                // 这个意思是说一个毫秒内最多只能有4096个数字,无论你传递多少进来,
                //这个位运算保证始终就是在4096这个范围内,避免你自己传递个sequence超过了4096这个范围
                sequence = (sequence + 1) & sequenceMask;
                //当某一毫秒的时间,产生的id数 超过4095,系统会进入等待,直到下一毫秒,系统继续产生ID
                if (sequence == 0) {
                    timestamp = tilNextMillis(lastTimestamp);
                }
    
            } else {
                sequence = 0;
            }
            // 这儿记录一下最近一次生成id的时间戳,单位是毫秒
            lastTimestamp = timestamp;
            // 这儿就是最核心的二进制位运算操作,生成一个64bit的id
            // 先将当前时间戳左移,放到41 bit那儿;将机房id左移放到5 bit那儿;将机器id左移放到5 bit那儿;将序号放最后12 bit
            // 最后拼接起来成一个64 bit的二进制数字,转换成10进制就是个long型
            return ((timestamp - twepoch) << timestampLeftShift) |
                    (datacenterId << datacenterIdShift) |
                    (workerId << workerIdShift) | sequence;
        }
    
        /**
         * 当某一毫秒的时间,产生的id数 超过4095,系统会进入等待,直到下一毫秒,系统继续产生ID
         * @param lastTimestamp
         * @return
         */
        private long tilNextMillis(long lastTimestamp) {
    
            long timestamp = timeGen();
    
            while (timestamp <= lastTimestamp) {
                timestamp = timeGen();
            }
            return timestamp;
        }
        //获取当前时间戳
        private long timeGen(){
            return System.currentTimeMillis();
        }
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119

    后记

    这篇博客是我对雪花算法学习的阶段性总结,其中有很多我不理解的地方,欢迎大家进行指正,和交流。
    参考博文:
    https://blog.csdn.net/lq18050010830/article/details/89845790
    https://blog.csdn.net/xiaoye319/article/details/105988057/

  • 相关阅读:
    【HMS Core】AOSP11安装/预置HMS Core 可以关闭限制广告跟踪吗?
    Coreldraw2020最新64位电脑完整版本下载教程
    【Linux】TCP协议
    iptables 防火墙配置
    CesiumJS【Basic】- #006 浏览器控制台查看位置角度
    安装使用RocketMQ一套保姆全教程-最快完成SpringBoot使用消息队列demo
    社科院与杜兰大学能源管理硕士项目——用你的脚步,走出自己的风景
    stm32----用状态机判断单双击
    Ngnix优化
    洗鞋软件开发,洗鞋店小程序功能介绍;
  • 原文地址:https://blog.csdn.net/wangwei021933/article/details/126540760