snowflake(雪花算法) 生成分布式 ID

snowflake(雪花算法) 生成分布式 ID

1、常见的分布式 ID 实现

在如今的环境下,对于分布式 ID 的实现有以下几种方式:

  1. UUID
  2. Redis
  3. snowflake
  4. 美团 leaf - 雪花算法的变形
  5. 百度 UidGenerator - x雪花算法的变形
  6. 滴滴 Tinyid

这里对于 snowflake 做基本的介绍及实现。

2、snowflake 介绍

snowflake,Twitter 开源的一种分布式 ID 生成算法。基于64位数实现,下图为 snowflake 算法的ID构成图:

img

  • 第1位置为0。
  • 第2-42位是相对时间戳,通过当前时间戳减去一个固定的历史时间戳生成。
  • 第43-52位是机器号workerID,每个Server的机器ID不同。
  • 第53-64位是自增ID。

3、优缺点

3.1、优点

  • 不依赖数据库等第三方系统,生成 ID 的性能非常高。
  • 时间戳在高位,自增ID在低位,整个ID呈现趋势递增。
  • 根据不同业务分配 bit 位数,相当灵活。

3.2、缺点

  • 强烈依赖机器时钟,如果发生时钟回拨(即服务器当前取的时间小于上次取的时间),会导致发号重复。

    时钟回拨:首先假定当前的北京时间是 9:00:00。另外上次生成 ID 的时候,服务器获取的时间 lastTimestamp=10:00:00,而现在服务器获取的当前时间 timestamp=09:00:00,这就相当于服务器之前是获取了一个未来时间,现在突然跳跃到当前时间,这种场景我们称之为时钟回拨时钟跳跃

    出现原因:服务器时钟可能会因为各种原因发生不准,而网络中会提供 NTP 服务来做时间校准,因此在做校准的时候,服务器时钟就会发生时钟的跳跃或者回拨问题。

4、代码实现

以下是 snowflake 的代码实现,由于第一位是标志位没有用到,因此生成 63 位的 分布式 ID:

/**
 * 生成分布式ID(雪花算法)
 */
public class SnowflakeUtilV2 {

    //开始时间
    private final static long START_TIME = 16538551000L;

    //序列号位数
    private final static int SEQUENCE_BIT = 12;

    //服务器位数
    private final static int SERVER_BIT = 5;

    //机器位数
    private final static int MACHINE_BIT = 5;

    //时间左移位数
    private final static int TIME_LEFT_BIT = SEQUENCE_BIT + SERVER_BIT + MACHINE_BIT;

    //服务器左移位数
    private final static int SEVER_LEFT_BIT = SEQUENCE_BIT + SERVER_BIT;

    //时间左移位数
    private final static int MACHINE_LEFT_BIT = SEQUENCE_BIT;

    //序列号最大值
    private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);

    //服务器id默认值
    private final static long serverId = 1;

    //机器id默认值
    private final static long machineId = 1;

    //序列号默认值
    private long sequence = 0L;

    //上次时间戳默认值
    private long lastTime = -1L;

    /**
     * 返回63位的分布式id(不返回标识位)
     * 时间戳(41位) + 服务器id(5位) + 机器id(5位) + 序列号(12位)
     *
     * @return
     */
    public synchronized long generateId() {
        long currentTime = getCurrentTime();
        //当前时间小于上次时间,抛出异常
        if (currentTime < lastTime) {
            throw new RuntimeException("current time is error");
        }

        //同一个时间戳,序列号加1
        if (currentTime == lastTime) {
            sequence = sequence + 1;
            //当前时间的序列号已满,取下个时间的序列号
            if (sequence >= MAX_SEQUENCE) {
                currentTime = getNextTime();
            }
        } else {
            //当前时间大于上次时间,序列号置为0
            sequence = 0L;
        }
        //修改上次时间
        lastTime = currentTime;

        return (currentTime - START_TIME) << TIME_LEFT_BIT
                | serverId <<  SEVER_LEFT_BIT
                | machineId << MACHINE_LEFT_BIT
                | sequence;
    }

    /**
     * 获取当前时间戳
     *
     * @return
     */
    private static long getCurrentTime() {
        return System.currentTimeMillis();
    }

    /**
     * 获取下个时间戳
     *
     * @return
     */
    private long getNextTime() {
        long currentTime = getCurrentTime();
        while (currentTime == lastTime) {
            currentTime = getCurrentTime();
        }
        return currentTime;
    }
}

参考资料:

posted @ 2023-07-12 15:07  MyDistance  阅读(202)  评论(0编辑  收藏  举报