分布式部署ID全局配置之雪花算法

分布式部署ID全局配置之雪花算法

前言

为什么需要分布式全局唯一ID 以及分布式ID的业务需求?

  • 在复杂分布式系统中,往往需要对大量对数据和消息进行标识
  • 如在美团、支付、餐饮 中 系统的数据日渐增长,对数据分库分表需要有一个唯一来标识一条数据或消息
  • 此时一个能够生成全局唯一ID的系统是非常有必要的

ID生成规则部分硬性要求

  • 全局唯一 :不能出现重复的ID,要 唯一标识
  • 趋势递增 :在Mysql 的InnoDB引擎使用的是聚集索引,由于多数RDBMS 使用的是Btree数据结构来存储数据,在主键的选择上面我们应该尽量使用有序的主键保证数据写入
  • 单调递增 :保证下一个ID一定大于上一个ID,例如事物版本号,增量消息
  • 信息安全 :如果ID是连续的,恶意用户的扒取数据就非常容易来,直接按照顺序下载指定的URL,如果是订单号就更危险来,竞争对手可以知道我们一天的单量,所以在一些应用场景下,需要ID不规则
  • 含时间戳 :这样就能够在开发中快速了解这个分布式id的生成时间

ID生成系统的可用性要求

  • 高可用 :发一个获取分布式ID的请求,服务器就要保证99.99%的情况下给我创建一个唯一分布式ID
  • 低延迟 :发一个获取分布式ID的请求,服务器就是要快,极速
  • 高QPS :假如并发一口气10万个创建分布式ID请求同时杀过来,服务器要顶的住一下子成功创建10w个分布式ID

我们平时的方案

UUID 、 数据库自增主键 、基于Redis 生成全局ID策略

弊端

UUID 不能生成顺序,递增的数据,并且长,不是很推荐

数据库自增,集群多的情况下,扩容简直就是噩梦

Redis 使用Redis INCR 和 INCRBY 实现

snowflake(雪花算法)

Twitter的分布式自增ID算法:snowflake(雪花算法)

概述

最初 Twitter把存储系统从Mysql 迁移到 Cassandra (由Facebook 开发一套开源分布式Nosql系统) 因为Cassandra没有顺序ID生成机制,所以开发成了这样一套全局唯一 ID生成服务

Twitter 的分布式雪花算法SnowFlake , 经测试 snowflake 每秒能产出26 万个自增可排序的ID

  1. twitter的SnowFlake生成ID能够按照时间有序生成
  2. SnowFlake 算法生成id 的结果是一个64 bit 大小的整数,为一个Long 型(转换成字符后长度19位)
  3. 分布式系统不会产生ID碰撞(由datacenter 和 workerld 区分)并且效率较高

雪花算法原理讲解

在这里插入图片描述

  1. 1bit,不用,因为二进制中最高位是符号位,1表示负数,0表示正数。生成的id一般都是用整数,所以最高位固定为0。
  2. 41bit-时间戳,用来记录时间戳,毫秒级。
    - 41位可以表示[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Oll7dpVz-1605695234754)(https://math.jianshu.com/math?formula=2%5E%7B41%7D-1)]个数字,
    - 如果只用来表示正整数(计算机中正数包含0),可以表示的数值范围是:0 至 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LCzpD94C-1605695234762)(https://math.jianshu.com/math?formula=2%5E%7B41%7D-1)],减1是因为可表示的数值范围是从0开始算的,而不是1。
    - 也就是说41位可以表示[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xEQQjn42-1605695234764)(https://math.jianshu.com/math?formula=2%5E%7B41%7D-1)]个毫秒的值,转化成单位年则是[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZUNl2pqO-1605695234768)(https://math.jianshu.com/math?formula=(2%5E%7B41%7D-1)]%20%2F%20(1000%20*%2060%20*%2060%20*%2024%20*365)%20%3D%2069)年
  3. 10bit-工作机器id,用来记录工作机器id。
    - 可以部署在[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J7uNLXEq-1605695234771)(https://math.jianshu.com/math?formula=2%5E%7B10%7D%20%3D%201024)]个节点,包括5位datacenterId和5位workerId
    - 5位(bit)可以表示的最大正整数是[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-egRhWNu3-1605695234774)(https://math.jianshu.com/math?formula=2%5E%7B5%7D-1%20%3D%2031)],即可以用0、1、2、3、…31这32个数字,来表示不同的datecenterId或workerId
  4. 12bit-序列号,序列号,用来记录同毫秒内产生的不同id。
    - 12位(bit)可以表示的最大正整数是[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0YyvYCu9-1605695234774)(D:%5Ccustersofeware%5Ctypora%5Cimage%5Cmath)],即可以用0、1、2、3、…4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号。

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

SnowFlake可以保证:

  1. 所有生成的id按时间趋势递增
  2. 整个分布式系统内不会产生重复id(因为有datacenterId和workerId来做区分)

twitter的雪花算法:https://github.com/twitter-archive/snowflake

GitHub上java版的雪花算法:
https://github.com/beyondfengyu/SnowFlake/blob/master/SnowFlake.java

使用建议

1、改进

其实雪花算法就是把id按位打散,然后再分成上面这几块,用位来表示状态,这其实就是一种思想。
所以咱们实际在用的时候,也不必非得按照上面这种分割,只需保证总位数在64位即可

如果你的业务不需要69年这么长,或者需要更长时间
用42位存储时间戳,(1L << 42) / (1000L * 60 * 60 * 24 * 365) = 139年
用41位存储时间戳,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年
用40位存储时间戳,(1L << 40) / (1000L * 60 * 60 * 24 * 365) = 34年
用39位存储时间戳,(1L << 39) / (1000L * 60 * 60 * 24 * 365) = 17年
用38位存储时间戳,(1L << 38) / (1000L * 60 * 60 * 24 * 365) = 8年
用37位存储时间戳,(1L << 37) / (1000L * 60 * 60 * 24 * 365) = 4年

如果你的机器没有那么1024个这么多,或者比1024还多
用7位存储机器id,(1L << 7) = 128
用8位存储机器id,(1L << 8) = 256
用9位存储机器id,(1L << 9) = 512
用10位存储机器id,(1L << 10) = 1024
用11位存储机器id,(1L << 11) = 2048
用12位存储机器id,(1L << 12) = 4096
用13位存储机器id,(1L << 13) = 8192

如果你的业务,每个机器,每毫秒最多也不会4096个id要生成,或者比这个还多
用8位存储随机序列,(1L << 8) = 256
用9位存储随机序列,(1L << 9) = 512
用10位存储随机序列,(1L << 10) = 1024
用11位存储随机序列,(1L << 11) = 2048
用12位存储随机序列,(1L << 12) = 4096
用13位存储随机序列,(1L << 13) = 8192
用14位存储随机序列,(1L << 14) = 16384
用15位存储随机序列,(1L << 15) = 32768
注意,随机序列建议不要太大,一般业务,每毫秒要是能产生这么多id,建议在机器id上增加位

如果你的业务量很小,比如一般情况下每毫秒生成不到1个id,此时可以将随机序列设置成随机开始自增
比如从0到48随机开始自增,算是一种优化建议

如果你有多个业务,也可以拿出来几位来表示业务,比如用最后4位,支持16种业务的区分

如果你的业务特别复杂,可以考虑128位存储,不过这样的话,也可以考虑使用uuid了,但uuid无序,这个有序

如果你的业务很简单,甚至可以考虑32位存储,时间戳改成秒为单位…

2、总结:

合理的根据自己的实际情况去设计各个唯一条件的组合,雪花算法只是提供了一种相对合理的方式。
雪花算法这种用位来表示状态的,我们还可以用在其他方面,比如数据库存储,可以用更小的空间去表示不同的状态位
包括各种底层的比如序列化,也是有用到拆解位,充分利用存储

算法实现

经过学习,本人写了两种方式去获取全局ID。

方式1:使用工具类进行获取ID

package com.lyj.demo.utils;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;

import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadLocalRandom;

import static java.util.concurrent.Executors.newFixedThreadPool;

/**
 * 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左右。
 */

/**
 * @author
 * @date 2020/11/18 10:14
 * 雪花算法分布式唯一ID生成工具
 */
public class SnowflakeIdUtil {
    
    

    private static final Logger logger = LoggerFactory.getLogger(SnowflakeIdUtil.class);

    /** 开始时间戳 */
    private final long startTimeStamp = 1605665795726L;

    /** 机器ID所占位数 */
    private final long workIdBits = 5L;

    /** 数据标志Id所占位数 */
    private final long dataCenterIdBits = 5L;

    /** 支持的机器最大ID,结果是31,这里受机器设置的ID所占位数大小变化 */
    private final long maxSupportWorkId = -1L ^ (-1L << workIdBits);

    /** 支持的最大数据标识ID,结果是31,这里受数据标识ID所占位数大小变化 */
    private final long maxSupportDataCenterId = -1L ^ (-1L << dataCenterIdBits);

    /** 序列号ID所占位数 */
    private final long sequenceBits = 12L;

    /** 工作厂房ID向左移12位 */
    private final long workIdLeftShift = sequenceBits;

    /** 数据标识ID向左移17位(12+5) */
    private final long dataCenterIdLeftShift = sequenceBits + dataCenterIdBits;

    /** 时间戳向左移22位(12+5+5) */
    private final long timeStampLeftShift = sequenceBits + workIdBits + dataCenterIdBits;

    /** 生成序列的掩码,这里是4095,受序列号位数影响,(0b111111111111=0xfff=4095)2^{12}-1 = 4095 即可以用0、1、2、3、4094 这 4095个数字 */
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    /** 工作厂房ID(0-31) */
    private long workId;

    /** 数据中心ID(0-31) */
    private long dataCenterId;

    /** 毫秒内序列ID(0-4094) */
    private long sequence;

    /** 上次生成ID的时间戳 */
    private long lastTimeStamp = -1L;

    /** 全局ID生成器 */
    private static SnowflakeIdUtil snowflakeId;

    public SnowflakeIdUtil() {
    
    
    }

    static {
    
    
           // 静态加载服务所在系统的ID配置
        logger.info("SnowflakeIdUtil workId : {}, dataCenterId : {}", getWorkId(), getDataCenterId());
        snowflakeId = new SnowflakeIdUtil(getWorkId(), getDataCenterId());
    }

    /**
     * 获取全局唯一ID
     * @return
     */
    public static Long generateId() {
    
    
        return snowflakeId.nextId();
    }

    /**
     * 初始化构造函数
     *
     * @param workId    指定的工作厂房Id
     * @param dataCenterId 指定的数据中心ID(机器ID)
     */
    public SnowflakeIdUtil(long workId, long dataCenterId) {
    
    
        if (workId > maxSupportWorkId || workId < 0) {
    
    
            throw new IllegalArgumentException(MessageFormat.format("machineId can't greater than {0} or less than 0", maxSupportWorkId));
        }
        if (dataCenterId > maxSupportDataCenterId || dataCenterId < 0) {
    
    
            throw new IllegalArgumentException(MessageFormat.format("dataCenterId can't greater than {0} or less than 0",maxSupportDataCenterId));
        }
        this.workId = workId;
        this.dataCenterId = dataCenterId;
    }


    public synchronized long nextId () {
    
    
        long timeStamp = getCurrencyTime();

        // 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
        if (timeStamp < lastTimeStamp) {
    
    
            throw new RuntimeException(MessageFormat.format("Clock moved backwards.  Refusing to generate id for {0} milliseconds", (lastTimeStamp - timeStamp)));
        }

        // 如果是同一时间生成的,则进行毫秒内序列
        if (lastTimeStamp == timeStamp) {
    
    
            sequence = (sequence+1) & sequenceMask;
            if (sequence == 0) {
    
    
                // 阻塞到下一毫秒,获取新的时间戳
                timeStamp = blockNextMills(lastTimeStamp);
            }
            // 时间戳改变,毫秒内序列重置
        } else {
    
    
            sequence = 0L;
        }

        lastTimeStamp = timeStamp;

        // 移位并通过或运算拼到一起组成64位的ID
        return ((timeStamp - startTimeStamp) << timeStampLeftShift) |
                (dataCenterId << dataCenterIdLeftShift) |
                (workId << workIdLeftShift) |
                sequence;


    }


    /**
     * 阻塞到下一个毫秒,直到获得新的时间戳
     *
     * @param lastTimeStamp 上次生成的时间戳ID
     * @return 当前时间戳
     */
    private static long blockNextMills(long lastTimeStamp) {
    
    
        long timeStamp = getCurrencyTime();
        while (timeStamp <= lastTimeStamp) {
    
    
            timeStamp = getCurrencyTime();
        }

        return timeStamp;
    }

    /**
     * 获取当前系统时间
     *
     * @return 毫秒
     */
    private static long getCurrencyTime() {
    
    
        return System.currentTimeMillis();
    }

    /**
     * IP地址取模
     *
     * @return IP地址取模
     */
    private static long ipModule() {
    
    
        InetAddress localHost = null;
        try {
    
    
            localHost = InetAddress.getLocalHost();
        } catch (UnknownHostException e) {
    
    
            // 获取IP失败,使用随机数兜底
            return RandomUtils.nextInt(0, 31);
        }
        // 机器位占用5位,所以取模32,最大限度避免重复
        return ipv4ToLong(localHost.getHostAddress()) % 32;
    }

    /**
     * IP地址转long类型
     * @param strIP IP地址
     * @return long类型
     */
    private static long ipv4ToLong(String strIP) {
    
    
        Validator.validateIpv4(strIP, "Invalid IPv4 address!");
        long[] ip = (long[])Convert.convert(long[].class, StrUtil.split(strIP, '.'));
        return (ip[0] << 24) + (ip[1] << 16) + (ip[2] << 8) + ip[3];
    }

    /**
     * 获取数据中心ID(机器ID)
     *
     * @return 采用本地机器名称取32的模(降低分布式部署工作ID相同)
     */
    private static long getDataCenterId() {
    
    
        ThreadLocalRandom localRandom = ThreadLocalRandom.current();
        try {
    
    
            InetAddress localHost = InetAddress.getLocalHost();
            String hostName = localHost.getHostName();
            List<Integer> tenNums = string2Integer(hostName);
            if (CollectionUtils.isEmpty(tenNums)) {
    
    
                return localRandom.nextInt(0,31);
            }
            int sums = 0;
            for (int i = 0; i < tenNums.size(); i++) {
    
    
                sums += tenNums.get(i);
            }
            return (long) (sums % 32);
        } catch (UnknownHostException e) {
    
    
            return localRandom.nextInt(0, 31);
        }
    }


    /**
     * 获取工作工厂ID
     *
     * @return 采用本地IP地址取32的模(降低分布式部署工作ID相同)
     */
    private static long getWorkId() {
    
    
        ThreadLocalRandom localRandom = ThreadLocalRandom.current();
        try {
    
    
            InetAddress localHost = InetAddress.getLocalHost();
            String ip = localHost.getHostAddress();
            List<Integer> tenNums = string2Integer(ip);
            if (CollectionUtils.isEmpty(tenNums)) {
    
    
                // IP为空,则采用随机数指定工作ID
                return localRandom.nextInt(0,31);
            }
            int sums = 0;
            for (int i = 0; i < tenNums.size(); i++) {
    
    
                sums += tenNums.get(i);
            }
            return (long) (sums % 32);
        } catch (UnknownHostException e) {
    
    
            // ip 获取失败,则采用随机数指定工作ID
            return localRandom.nextInt(0,31);
        }
    }

    /**
     * 字符串转成10进制的数字集合
     *
     * @param str 字符串
     * @return 10进制的数字集合(这里的是将字符转成对应的ASICC码)
     */
    private static List<Integer> string2Integer(String str) {
    
    
        if (StringUtils.isBlank(str)) {
    
    
            return Collections.emptyList();
        }
        List<Integer> integers = Lists.newArrayListWithCapacity(str.length());
        for (int i = 0; i < str.length(); i++) {
    
    
            integers.add(Integer.valueOf(Integer.toString(str.charAt(i), 10)));
        }
        return integers;
    }



    public static void main(String[] args) throws UnknownHostException {
    
    
        // 并发测试
        ExecutorService threadPool = newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
    
    
            threadPool.submit(() -> {
    
    
                System.out.println(snowflakeId.nextId());
            });
//            threadPool.shutdown();

        }

        // 200W条数据,耗时在13ms左右,平均150条/ms
        System.out.println("++++++++++++++++++++++++++=");
        BufferedWriter bufferedWriter = null;
        try {
    
    
            //设置文件编码,解决文件乱码问题
            //将字节流转换为字符流,实际上使用了一种设计模式——适配器模式(生成本地项目根目录)
            bufferedWriter = new BufferedWriter(new FileWriter("./idSnow.txt"));
            System.out.println(System.currentTimeMillis());
            long startTime = System.nanoTime();
            for (int i = 0; i < 2000000; i++) {
    
    
                long id = snowflakeId.nextId();
                bufferedWriter.write(String.valueOf(id));
                bufferedWriter.newLine();//按行读取,写入一个分行符,否则所有内容都在一行显示
                System.out.println(id);
            }
            System.out.println((System.nanoTime()-startTime)/1000000+"ms");
        } catch (FileNotFoundException e) {
    
    
            e.printStackTrace();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (bufferedWriter != null) {
    
    
                    bufferedWriter.close();
                }
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
    }

}

方式二:交给spring管理,通过注入方式获取全局ID

package com.lyj.demo.config;

import com.google.common.collect.Lists;
import com.lyj.demo.utils.SnowflakeIdUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.util.CollectionUtils;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

/**
 * 网上的教程一般存在两个问题:
 * 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个
 * (大部分情况下没有这么多),似乎也不太可能,此问题留待日后解决后再行补充。
 */

/**
 * @author
 * @date 2020/11/18 16:53
 * 全局分布式ID配置(雪花算法)交给spring管理
 */
@Configuration
public class SnowflakeIdConfig {
    
    

    private static final Logger logger = LoggerFactory.getLogger(SnowflakeIdConfig.class);

    @Value("${snowflake.id.workId:noWorkId}")
    private String workId;

    @Value("${snowflake.id.dataCenterId:noDataCenterId}")
    private String dataCenterId;

    public SnowflakeIdConfig() {
    
    
    }

    @Bean(name = "snowflakeIdWorker")
    @Primary
    public SnowflakeIdUtil snowflakeId(){
    
    
        // 这里还是采用的是上面的工具类,静态加载的配置可以删掉,也可以不删除,因为这里已经重新new对象了,不是同一个对象
        return new SnowflakeIdUtil(getWorkId(workId), getDataCenterId(dataCenterId));
    }

    /**
     * workId配置
     *
     * @param workId workId
     * @return workId
     */
    private long getWorkId(String workId) {
    
    
        logger.info("SnowflakeIdConfig workId : {}", workId);
        if ("noWorkId".equalsIgnoreCase(workId)) {
    
    
            return getWorkId();
        }
        return num2Model(string2Integer(workId));
    }

    /**
     * dataCenterId配置
     *
     * @param dataCenterId 数据中心ID
     * @return dataCenterId
     */
    private long getDataCenterId(String dataCenterId) {
    
    
        logger.info("SnowflakeIdConfig dataCenterId : {}", dataCenterId);
        if ("noDataCenterId".equalsIgnoreCase(dataCenterId)) {
    
    
            return getDataCenterId();
        }
        return num2Model(string2Integer(dataCenterId));
    }

    /**
     * 获取数据中心ID(机器ID)
     *
     * @return 采用本地机器名称取32的模(降低分布式部署工作ID相同)
     */
    private long getDataCenterId() {
    
    
        ThreadLocalRandom localRandom = ThreadLocalRandom.current();
        try {
    
    
            InetAddress localHost = InetAddress.getLocalHost();
            String hostName = localHost.getHostName();
            List<Integer> tenNums = string2Integer(hostName);
            if (CollectionUtils.isEmpty(tenNums)) {
    
    
                return localRandom.nextInt(0,31);
            }
            return num2Model(tenNums);
        } catch (UnknownHostException e) {
    
    
            return localRandom.nextInt(0, 31);
        }
    }


    /**
     * 获取工作工厂ID
     *
     * @return 采用本地IP地址取32的模(降低分布式部署工作ID相同)
     */
    private long getWorkId() {
    
    
        ThreadLocalRandom localRandom = ThreadLocalRandom.current();
        try {
    
    
            InetAddress localHost = InetAddress.getLocalHost();
            String ip = localHost.getHostAddress();
            List<Integer> tenNums = string2Integer(ip);
            if (CollectionUtils.isEmpty(tenNums)) {
    
    
                // IP为空,则采用随机数指定工作ID
                return localRandom.nextInt(0,31);
            }
            return num2Model(tenNums);
        } catch (UnknownHostException e) {
    
    
            // ip 获取失败,则采用随机数指定工作ID
            return localRandom.nextInt(0,31);
        }
    }

    /**
     * 字符串转成10进制的数字集合
     *
     * @param str 字符串
     * @return 10进制的数字集合(这里的是将字符转成对应的ASICC码)
     */
    private List<Integer> string2Integer(String str) {
    
    
        if (StringUtils.isBlank(str)) {
    
    
            return Collections.emptyList();
        }
        List<Integer> integers = Lists.newArrayListWithCapacity(str.length());
        for (int i = 0; i < str.length(); i++) {
    
    
            integers.add(Integer.valueOf(Integer.toString(str.charAt(i), 10)));
        }
        return integers;
    }

    /**
     * 求和取模32
     *
     * @param nums 数字集合
     * @return long类型
     */
    private long num2Model(List<Integer> nums) {
    
    
        ThreadLocalRandom localRandom = ThreadLocalRandom.current();
        if (CollectionUtils.isEmpty(nums)) {
    
    
            return localRandom.nextInt(0,31);
        }
        int sums = 0;
        for (int i = 0; i < nums.size(); i++) {
    
    
            sums += nums.get(i);
        }
        return (long) (sums % 32);
    }

}

测试

   @Autowired
    private SnowflakeIdUtil snowflakeIdWorker;


 /**
     * 雪花算法分布式Id唯一测试
     * 1:工具类静态加载管理测试
     * 2:spring管理测试
     */
    @RequestMapping("/test/snowflake")
    public void snowflakeTest() {
    
    
        // 通过工具类静态加载获取
        System.out.println(SnowflakeIdUtil.generateId());
        // 通过spring管理优先注入获取
        System.out.println(snowflakeIdWorker.nextId());
    }

参考链接:https://developer.aliyun.com/article/772913

​ https://www.cnblogs.com/jpfss/p/11506973.html

https://www.cnblogs.com/austinspark-jessylu/p/11900527.html

猜你喜欢

转载自blog.csdn.net/qq_40093255/article/details/109047112