美文网首页
雪花算法

雪花算法

作者: zouhao1985 | 来源:发表于2023-03-25 18:07 被阅读0次

    一、概述

    MYSQL数据库主键常见的解决方案有三种,分别是自增主键、UUID以及雪花算法。其中自增ID不适合做分布式应用,系统水平扩展比较困难。UUID不是递增类型的主键,导致MYSQL B+ 树的分裂。
    雪花算法满足了全局唯一、单调递增(一般不连续,保证信息安全)等特性,同时满足高可用、低延迟以及高并发要求,因此在分布式场景下作为ID主键比较适合。

    二、实现原理

    雪花算法的结构:


    image.png
    image.png
    • 第一部分
      最高位是0,是符号位。1表示负数,2表示正数,所以最高位为0。
    • 第二部分
      41位的部分用来记录时间戳。
    • 第三部分
      5位的数据中心ID和5位的工作机器ID,5位的机器工作ID最大31,标识最多同一个数据中心可以部署31台机器。
    • 第四部分
      12位可以表示 4095,即表示来自同一个数据中心,同一个机器,同一个时间戳最多可以产生4095个ID序号。

    三、实现

    实现方式一,java代码。

    /**
     * twitter的snowflake算法 -- java实现
     */
    public class SnowFlake {
    
        /**
         * 起始的时间戳
         */
        private final static long START_STMP = 1480166465631L;
    
        /**
         * 每一部分占用的位数
         */
        private final static long SEQUENCE_BIT = 12; //序列号占用的位数
        private final static long MACHINE_BIT = 5;   //机器标识占用的位数
        private final static long DATACENTER_BIT = 5;//数据中心占用的位数
    
        /**
         * 每一部分的最大值
         */
        private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);
        private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
        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 long datacenterId;  //数据中心
        private long machineId;     //机器标识
        private long sequence = 0L; //序列号
        private long lastStmp = -1L;//上一次时间戳
    
        public SnowFlake(long datacenterId, long machineId) {
            if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
                throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
            }
            if (machineId > MAX_MACHINE_NUM || machineId < 0) {
                throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
            }
            this.datacenterId = datacenterId;
            this.machineId = machineId;
        }
    
        /**
         * 产生下一个ID
         *
         * @return
         */
        public 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 {
                //不同毫秒内,序列号置为0
                sequence = 0L;
            }
    
            lastStmp = currStmp;
    
            return (currStmp - START_STMP) << TIMESTMP_LEFT //时间戳部分
                    | datacenterId << DATACENTER_LEFT       //数据中心部分
                    | machineId << MACHINE_LEFT             //机器标识部分
                    | sequence;                             //序列号部分
        }
    
        private long getNextMill() {
            long mill = getNewstmp();
            while (mill <= lastStmp) {
                mill = getNewstmp();
            }
            return mill;
        }
    
        private long getNewstmp() {
            return System.currentTimeMillis();
        }
    
        public static void main(String[] args) {
            SnowFlake snowFlake = new SnowFlake(2, 3);
    
            for (int i = 0; i < (1 << 12); i++) {
                System.out.println(snowFlake.nextId());
            }
    
        }
    }
    

    实现方式二,引入hutool。

    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.3.1</version>
    </dependency>
    

    基于hutool的使用方式

    public class SnowFlakeDemo {
        private long workerId = 0;
        private long datacenterId = 1;
        private Snowflake snowFlake = IdUtil.createSnowflake(workerId, datacenterId);
    
        @PostConstruct
        public void init() {
            try {
                // 将网络ip转换成long
                workerId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 获取雪花ID
         * @return
         */
        public synchronized long snowflakeId() {
            return this.snowFlake.nextId();
        }
    
        public synchronized long snowflakeId(long workerId, long datacenterId) {
            Snowflake snowflake = IdUtil.createSnowflake(workerId, datacenterId);
            return snowflake.nextId();
        }
    
        public static void main(String[] args) {
            SnowFlakeDemo snowFlakeDemo = new SnowFlakeDemo();
            for (int i = 0; i < 20; i++) {
                new Thread(() -> {
                    System.out.println(snowFlakeDemo.snowflakeId());
                }, String.valueOf(i)).start();
            }
        }
    }
    

    四、总结

    START_STMP目测过去可以设置为某个时间,但是需要固定下来,比如2015-01-01 00:00:00。

    五、参考资料

    https://www.cnblogs.com/jingzh/p/16020906.htm

    相关文章

      网友评论

          本文标题:雪花算法

          本文链接:https://www.haomeiwen.com/subject/zasnrdtx.html