分布式ID JavaSE

2023-05-09 约 1619 字 阅读时长4 分钟

分布式ID

雪花算法

介绍

Snowflake 是 Twitter 开源的分布式 ID 生成算法。Snowflake 由 64 bit 的二进制数字组成,这 64bit 的二进制被分成了几部分,每一部分存储的数据都有特定的含义:

  • 第 0 位:符号位(标识正负),始终为 0,没有用,不用管。
  • 第 1~41 位:一共 41 位,用来表示时间戳,单位是毫秒,可以支撑 2 ^41 毫秒(约 69 年)
  • 第 42~52 位:一共 10 位,一般来说,前 5 位表示机房 ID,后 5 位表示机器 ID(实际项目中可以根据实际情况调整)。这样就可以区分不同集群/机房的节点。
  • 第 53~64 位:一共 12 位,用来表示序列号。 序列号为自增值,代表单台机器每毫秒能够产生的最大 ID 数(2^12 = 4096),也就是说单台机器每毫秒最多可以生成 4096 个 唯一 ID

优点:生成速度比较快、生成的 ID 有序递增、比较灵活(可以对 Snowflake 算法进行简单的改造比如加入业务 ID)

缺点:需要解决重复 ID 问题(依赖时间,当机器时间不对的情况下,可能导致会产生重复 ID)

Java实现

java
  1/**
  2 * twitter的snowflake算法 -- java实现
  3 * 下面代码每秒理论上可生成id数为:2048000
  4 *
  5 * @author 总共 64bit位 最大为 9,223,372,036,854,775,807(2^63 -1)
  6 */
  7public class SnowFlake {
  8
  9    /**
 10     * 起始的时间戳 2020-01-01 00:00:00
 11     */
 12    private static final long START_TIMESTAMP = 1577808000000L;
 13
 14    // 预留1 bit 符号位(默认为0)
 15
 16    /**
 17     * 序列号占用的位数 每毫秒可生成2048个序列号
 18     */
 19    private static final long SEQUENCE_BIT = 11;
 20    /**
 21     * 机器标识占用的位数 最大32
 22     */
 23    private static final long MACHINE_BIT = 5;
 24    /**
 25     * 数据中心占用的位数 最大32
 26     */
 27    private static final long DATACENTER_BIT = 5;
 28    /**
 29     * 时间戳占用的位数 139年左右
 30     */
 31    private static final long TIMESTAMP_BIT = 42;
 32
 33    /**
 34     * 机器id最大为31
 35     */
 36    private static final long MAX_MACHINE = ~(-1L << MACHINE_BIT);
 37
 38    /**
 39     * 数据中心id最大为31
 40     */
 41    private static final long MAX_DATACENTER = ~(-1L << DATACENTER_BIT);
 42
 43    /**
 44     * 序列最大大小
 45     */
 46    private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);
 47
 48    /**
 49     * 每一部分向左的位移
 50     */
 51    private static final long MACHINE_LEFT = SEQUENCE_BIT;
 52    private static final long DATACENTER_LEFT = MACHINE_LEFT + MACHINE_BIT;
 53    private static final long TIMESTAMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;
 54
 55    /**
 56     * 序列号
 57     */
 58    private long sequence = 1L;
 59
 60    /**
 61     * 上一次时间戳
 62     */
 63    private long lastTimestamp = -1L;
 64
 65    /**
 66     * 机器id
 67     */
 68    private long machineId = 1L;
 69
 70    /**
 71     * 数据中心id
 72     */
 73    private long datacenterId = 1L;
 74
 75
 76    public SnowFlake(long machineId, long datacenterId) {
 77        if (machineId > MAX_MACHINE) {
 78            throw new AppException("机器id最大为31");
 79        }
 80        if (datacenterId > MAX_DATACENTER) {
 81            throw new AppException("数据中心id最大为31");
 82        }
 83        this.machineId = machineId;
 84        this.datacenterId = datacenterId;
 85    }
 86
 87    /**
 88     * 产生一下个id
 89     *
 90     * @return long
 91     */
 92    public synchronized long nextId() {
 93        long currTimestamp = getNewTimestamp();
 94        if (currTimestamp < lastTimestamp) {
 95            throw new AppException("Clock moved backwards.  Refusing to generate id");
 96        }
 97        // 相同毫秒内,序列号自增
 98        if (currTimestamp == lastTimestamp) {
 99            sequence = (sequence + 1) & MAX_SEQUENCE;
100            // 同一毫秒的序列数已经达到最大,等待到下一毫秒
101            if (sequence == 0L) {
102                currTimestamp = getNextMill();
103            }
104        } else {
105            // 不同毫秒 序列置为 0
106            sequence = 1L;
107        }
108        lastTimestamp = currTimestamp;
109        // 这里隐藏了首位 0L
110        return (currTimestamp - START_TIMESTAMP) << TIMESTAMP_LEFT //时间戳部分
111                | datacenterId << DATACENTER_LEFT       //数据中心部分
112                | machineId << MACHINE_LEFT             //机器标识部分
113                | sequence; //序列号部分
114    }
115
116    private long getNextMill() {
117        long mill = getNewTimestamp();
118        while (mill <= lastTimestamp) {
119            mill = getNewTimestamp();
120        }
121        return mill;
122    }
123
124    private long getNewTimestamp() {
125        return System.currentTimeMillis();
126    }
127
128
129    public static final SnowFlake SNOW_FLAKE = new SnowFlake(1L, 1L);
130
131    public static long getSeq() {
132        return SNOW_FLAKE.nextId();
133    }
134
135    public static void main(String[] args) {
136        long start = System.currentTimeMillis();
137        List<Long> arr = Collections.synchronizedList(new ArrayList<>(100000));
138        new Thread(() -> {
139            while (System.currentTimeMillis() - start < 1000) {
140                arr.add(getSeq());
141            }
142        }).start();
143        new Thread(() -> {
144            while (System.currentTimeMillis() - start < 1000) {
145                arr.add(getSeq());
146            }
147        }).start();
148        try {
149            TimeUnit.SECONDS.sleep(1);
150        } catch (InterruptedException e) {
151            e.printStackTrace();
152        }
153        System.out.println(String.format("耗时:%sms,生成id数量:%s", System.currentTimeMillis() - start, arr.size()));
154
155
156    }
157}

UUID

介绍

UUID 是 Universally Unique Identifier(通用唯一标识符) 的缩写。UUID 包含 32 个 16 进制数字(8-4-4-4-12)

优点:生成速度比较快、简单易用

缺点:存储消耗空间大(32 个字符串,128 位)、 不安全(基于 MAC 地址生成 UUID 的算法会造成 MAC 地址泄露)、无序(非自增)、没有具体业务含义、需要解决重复 ID 问题(当机器时间不对的情况下,可能导致会产生重复 ID)

Java代码

java
1public class UUIDUtil {
2    public static void main(String[] args) {
3        UUID randomUUID = UUID.randomUUID();
4        // 示例:c1c74128-0fc4-454b-b4bc-248d945d25a9
5        String toString = randomUUID.toString();
6    }
7}
使用滚轮缩放
按住拖动