• 全局唯一ID


    前言

    • 基于数据库设置其实初始值,以及增量步长。
    • 基于ZK,Redis,改良雪花集中式服务生成,远程调用获取id。
    • 基于并行空间划分,Snowflake(8Byte字节64bit位),MongoDB ObjectId(12字节),UUID(16字节)
    • 基于全随机UUID(16字节)

    MongoDB ObjectId

    12-byte MongoDB ObjectId 的结构是:

    • a 4-byte value representing the seconds since the Unix epoch,.
    • a 3-byte machine identifier.
    • a 2-byte process id.
    • a 3-byte counter, starting with a random value.

    可以看出,这个方案所支持的最小划分粒度是「秒 * 进程实例」,单进程实例的每秒容量是 3-byte (24-bit),也就是接近16777216个ID。

    即每个实例每秒1600多W。

    看代码(MonogoDB 3.3.x Java Driver)

    1. Timestamp

    图片

    2. Machine Identifier

    图片

    3. Process ID

    图片

    4. COUNTER

    图片

    此处需要注意的是MongoDB的 NEXT_COUNTER 其初始值是一个随机数,这是有利于分库分表的。因为在小并发的条件下,非随机数的初始值,容易产生 偏库偏表, 不均匀的现象。

    Twitter Snowflake

    64-bit 长的 Snowflake ,它的结构是:

    • 1-bit reserved
    • 41-bit timestamp millisecond
    • 10-bit machine id
    • 12-bit sequence

    所支持的最小划分粒度是「毫秒 * 线程」,单线程(Snowflake 里对应的概念是 Worker)的每毫秒容量是12-bit,也就是接近4096。

    即每个实例每秒4096000(400w)

    初始化时间自己指定,例如:

    // Thu, 04 Nov 2010 01:42:54 GMT
    twepoch = 1288834974657L;
    
    • 1
    • 2

    1. Timestamp

     System.currentTimeMillis() - twepoch
    
    • 1

    2. Machine Identifier

    同上

    3. Process ID

    同上

    4. Sequence

    if (lastTimestamp == timestamp) {
        // 相同毫秒内,序列号自增
        sequence = (sequence + 1) & sequenceMask;
        if (sequence == 0) {
            // 同一毫秒的序列数已经达到最大, 则等待下一个毫秒时间,即使用下一秒 sequence默认0L
            timestamp = tilNextMillis(lastTimestamp);
        }
    } else {
        
        // 不同毫秒内,序列号置为 [1 - 3) 随机数 即 1或者2 防止每毫秒初始值相同
        sequence = ThreadLocalRandom.current().nextLong(1, 3);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    sequence初始值是一个[1 - 3) 随机数,这是有利于分库分表的。因为在小并发的条件下,非随机数的初始值,容易产生 偏库偏表, 不均匀的现象。

    闰秒问题以及运行中时钟回拨问题

    闰秒会出现把协调世界时向前拨1秒(负闰秒,最后一分钟为59秒)或向后拨1秒(正闰秒,最后一分钟为61秒);

    // 当前时间小于上次时间,即出现闰秒或者时钟回溯了
    if (timestamp < lastTimestamp) {
        long offset = lastTimestamp - timestamp;
        if (offset <= 5) { // 回溯时差 如果小于等于 5 ms
            try {
                wait(offset << 1); //等个2倍时间 再获取时间戳
                timestamp = timeGen(); 
                if (timestamp < lastTimestamp) { // 还小则抛出异常
                    throw new RuntimeException("时钟回调了 拉了裤");
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        } else {
            throw new RuntimeException("时钟回调了 拉了裤");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    延迟等待解决,实际应用项目: 美团的leaf,。

    UUID

    参见另一篇UUID的5个版本

    • 版本1 - UUID 是根据时间和 节点ID(通常是MAC地址)生成;时间+MAC,不安全

    • 版本2 - UUID是根据标识符(通常是组或用户ID)、时间和节点ID生成;

    • 版本3、版本5 - 确定性UUID 通过散列(hashing)名字空间(namespace)标识符和名称生成;

    • 版本4 - UUID 使用随机性或伪随机性生成。

    一般的,使用的最多的是UUID Version 4,很大程度上是因为其依赖的其他服务最少。

    Java只支持生成版本3和版本4的UUID

    V4 随机UUID,UUID.randomUUID().toString()

    V4 随机UUID重复概率问题?

    V4随机版本有一定的重复概率但是极低,直接上结论,来着维基百科

    在 103 万亿 个 版本4 UUID 中找到重复的概率是(十亿分之一)。

    参考

    • 生成全局唯一ID的3个思路,来自一个资深架构师的总结 - 王延炯
  • 相关阅读:
    Python并发编程简介
    GPU算力租用平台推荐
    微服务项目雪崩的解决思路
    论文笔记 《FAST-LIO2: Fast Direct LiDAR-inertial Odometry》及 激光SLAM综述
    LabVIEW样式检查表1
    轮询和长轮询的区别
    【力扣算法简单五十题】08.删除排序链表中的重复元素
    【洛谷 P1147】连续自然数和 题解(等差数列求和+因式分解+解二元一次方程)
    微信小程序的双向数据绑定和vue的哪里不一样?下拉刷新的方式代码示例
    第12期 | GPTSecurity周报
  • 原文地址:https://blog.csdn.net/abu935009066/article/details/128037169