likes
comments
collection
share

SnowFlake 雪花算法 生成分布式唯一ID

作者站长头像
站长
· 阅读数 52

背景

公司近期需要重构已有项目,打算重新从架构开始底层搭建,所采用的是市面上最流行的分布式架构搭建,但是业务中有非常之多的数据需要在整个系统中都保持唯一标识,那么问题就来了,分布式项目中id的生成是采用数据库Mysql的唯一自增主键呢?又或者是采用UUID的方式呢? 这两种方法弊端都很明显:

  • 【Mysql自增主键生成唯一id】生成的方式是调用了数据库,网络I/O开销和数据库开销非常大。
  • 【UUID生成唯一id】性能倒是非常之高,但是所有的数据都得入库呀,但UUID的生成方式又是无序的,就拿Mysql来说,新增性能非常差。

有没有更好的可以生成分布式唯一ID的方式呢? 对的,相信很多人都知道,twitter家的雪花算法生成唯一id(现在百度的UidGenerator..都是基于雪花算法的),虽然听过,但是很多时候很多业务,真的就是不接触就不了解,又或者是很多时候你在用的都快用烂的一些玩意儿,就是没有深入的看过原理。那就好好的了解一下这个到底是怎么样生成的吧。

JAVA 源码

/**
 * 雪花算法生成分布式id
 */
public class SnowUtil {

    private long sequence = 0L;// 序列号
    private long machineId = 1L;// 自定义 节点
    private static long lastTimestamp = -1L;// 上次时间
    private final static long START_TIME_STAMP = 1698372440174L;// 开始时间戳 2023-10-27

    /**
     * 每一部分占用的位数
     */
    private final static long SEQUENCE_BIT = 11L;
    private final static long MACHINE_BIT = 10L;

    /**
     * 每一部分的最大值
     */
    // 1024/节点
    private static long MACHINE_MAX = -1L ^ (-1L << MACHINE_BIT);
    // 2048/序号
    private final static long SEQUENCE_MAX = -1L ^ (-1L << SEQUENCE_BIT);

    /**
     * 每一部分向左的位移
     */
    // 11位
    private final static long MACHINE_ID_LEFT = SEQUENCE_BIT;
    // 22位
    private final static long TIME_STAMP_LEFT = SEQUENCE_BIT + MACHINE_BIT;

    private static SnowUtil snowUtil = null;

    static {
        snowUtil = new SnowUtil();
    }

    /**
     * 生成唯一id
     * @return
     */
    public static synchronized long nextId() {
        return snowUtil.getNextId();
    }


    private SnowUtil() {
        this.machineId = machineId & MACHINE_MAX;
    }

    public synchronized long getNextId() {
        long timestamp = getCurrentTimeStamp();
        if (timestamp < lastTimestamp) {
            try {
                throw new Exception("");
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & SEQUENCE_MAX;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0;
        }
        lastTimestamp = timestamp;
        long nextId = ((timestamp - START_TIME_STAMP) << TIME_STAMP_LEFT)  | (machineId << MACHINE_ID_LEFT) | sequence;
        return nextId;
    }

    /**
     * 再次获取时间戳直到获取的时间戳与现有的不同
     *
     * @param lastTimestamp
     * @return 下一个时间戳
     */
    private long tilNextMillis(final long lastTimestamp) {
        long timestamp = this.getCurrentTimeStamp();
        while (timestamp <= lastTimestamp) {
            timestamp = this.getCurrentTimeStamp();
        }
        return timestamp;
    }

    /**
     * 返回以毫秒为单位的当前时间
     */
    protected long getCurrentTimeStamp() {
        return System.currentTimeMillis();
    }

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        for(long i = 0 ; i < 500000L ;  i ++){
            long nextId = SnowUtil.nextId();
        }
        long end = System.currentTimeMillis();
        Long cost = end-start;
        System.out.println("生成50wid总共花费了" + cost.toString() + "毫秒");
    }

}

性能测试

可能是因为在本地的缘故,同事用C#同一套思想(同步方法不同,也不是很清楚是不是这个问题),但是C#是运行在Windows平台直接用PowerShell运行速度非常快。

C#的性能测试

SnowFlake 雪花算法 生成分布式唯一ID

java本地环境IDEA运行的测试数据

SnowFlake 雪花算法 生成分布式唯一ID

虽然如此,但是生成id的速度还是让人瞠目结舌。

代码中注意的几个点

1、建议采用单例模式

  • 防止重复创建对象,影响性能
private static SnowUtil snowUtil = null;

static {
    snowUtil = new SnowUtil();
}

2、同步代码块

  • 防止序列号自增产生的并发问题
public synchronized long getNextId() {}

3、需要修改开始的时间戳

因为时间的位数只有1-42 (41位),总共能生成的id只有 (0,2^42-1) (139年)。

  • 为了这个生成id的方法可以多用几年,一定要修改开始的时间戳。
private final static long START_TIME_STAMP = 1698372440174L;// 开始时间戳 2023-10-27

原理

Id的数据类型采用Long数据类型,8byte=64bit。

地址(高到低)位数用途存储数值范围
0-11符号位0
1-4242时间差(0,2^42-1) (139年)
42-5210节点编号(0,2^10-1) (1024节点)
52-6411序列数(0,2^11-1)(2048/ms)

也可以根据自己的实际需求自定义适合自己实际状况的id生成器。

觉得作者写的不错的,值得你们借鉴的话,就请点一个免费的赞吧!这个对我来说真的很重要。૮(˶ᵔ ᵕ ᵔ˶)ა

转载自:https://juejin.cn/post/7294189695285690402
评论
请登录