雪花算法学习

一、基本概念

雪花算法(Snowflake)是一种生成全局唯一ID的分布式算法。它的主要功能是在分布式系统中生成一个全局唯一的ID,且ID是按照时间有序递增的。

1.核心思想
Snowflake算法使用一个64位的二进制数字作为ID。这64位long型ID被分割成四个部分:符号位、时间戳、工作机器ID、序列号。通过这几部分来表示不同的信息,将数据映射到具有特定结构的分布式系统中,实现数据的存储和查询。

该算法由一系列节点组成,每个节点负责存储数据的一部分。这些节点通过哈希函数将数据映射到特定的位置,形成类似于雪花结构的分布式系统。通过这种方式,雪花算法能够在分布式系统中保证ID的唯一性和有序性。

2.应用场合
雪花算法广泛应用于分布式系统中的唯一ID生成。它可以保证在分布式环境中生成的ID是唯一且有序的。常见的应用场合包括订单号生成、分布式数据库中的数据主键、分布式锁等。通过使用雪花算法生成全局唯一ID,可以方便地进行分布式系统的数据管理和查询。

二、实现原理

雪花算法将64位的long型ID分为四个部分:

1.符号位(1位)
始终为0,用于标识ID是正数。

2.时间戳(41位)
时间戳占据了整个ID的41位,精确到毫秒级,可以支持69年的时间戳。这使得雪花算法能够支持未来数十年的唯一性。时间戳部分还提供了排序的功能,可以根据时间戳来对数据进行排序。

3.机器ID(10位)
工作机器ID占据了ID的10位,可以支持最多1024个工作节点。这使得在同一台机器上运行的不同应用程序实例可以使用不同的工作机器ID来生成唯一的ID。

4.序列号(12位)
序列号占据了ID的12位,可以支持每个节点每毫秒产生4096个唯一的ID。这使得在同一台机器上运行的不同应用程序实例可以生成唯一的ID,即使在毫秒级别内也能保证唯一性。

注:上述划分中的工作机器ID位数(10位或5位)可能会因不同的实现方式而有所差异。在Twitter原始的雪花算法设计中,可能并没有直接使用10位来表示工作机器ID,而是将机器ID部分进一步细分为数据中心ID和工作机器ID,其中每个部分可能占用更少的位数(如各5位)。

优点:

系统环境ID不重复:能满足高并发分布式系统环境ID不重复,比如大家熟知的分布式场景下的数据库表的ID生成。
生成效率极高:在高并发,以及分布式环境下,除了生成不重复 id,每秒可生成百万个不重复 id,生成效率极高。
保证基本有序递增:基于时间戳,可以保证基本有序递增,很多业务场景都有这个需求。
不依赖第三方库:不依赖第三方的库,或者中间件,算法简单,在内存中进行。


缺点:

依赖服务器时间:服务器时钟回拨时可能会生成重复 id。

雪花算法生成ID重复的主要原因

  1. 时钟回拨‌:雪花算法依赖于‌系统时钟来生成ID,如果系统时钟回拨,即当前时间小于上一次生成ID的时间,会导致生成的ID重复。‌12
  2. 并发生成ID‌:在同一毫秒内,如果并发生成的ID超过了‌序列号的范围(12位),则会导致序列号部分重复。‌1
  3. 机器ID重复‌:如果部署的多台机器使用了相同的机器ID,则会导致生成的ID重复。
  4. 序列号溢出‌:如果序列号部分的位数不够,即超过了序列号的范围(12位),则会导致序列号部分重复。
  5. 虚拟机名称相同‌:在集群环境中,如果多台机器的虚拟机名称相同,可能会导致生成的机器ID相同,从而生成重复的ID。‌

解决雪花算法生成ID重复的策略

  1. 确保系统时钟准确‌:需要确保系统时钟是准确的,且不会回拨,可以通过网络时间协议(NTP)来同步时间。
  2. 限制并发生成ID的线程数量‌:可以通过限制并发生成ID的线程数量或者等待下一毫秒再生成ID来避免同一毫秒内生成过多ID。
  3. 保证每台机器使用唯一的机器ID‌:在部署多台机器时,需要确保每台机器使用唯一的机器ID。
  4. 使用更长的序列号位数‌:可以通过增加序列号的位数来避免序列号溢出。
  5. 确保虚拟机名称唯一‌:在集群环境中,需要确保每台机器的虚拟机名称唯一,可以通过在虚拟机名称中加入唯一标识符来实现。

因为雪花算法有序自增,保障了 MySQL 中 B+ Tree 索引结构插入高性能。所以,日常业务使用中,雪花算法更多是被应用在数据库的主键 ID 和业务关联主键。

使用雪花id或uuid作为Mysql主键,被老板怼了一顿!

public class SnowflakeIdWorker{
    /** 开始时间截 (2015-01-01) */
    private final long twepoch = 1288834974657L;
 
    /** 机器id所占的位数 */
    private final long workerIdBits = 5L;
 
    /** 数据标识id所占的位数 */
    private final long datacenterIdBits = 5L;
 
    /** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
 
    /** 支持的最大数据标识id,结果是31 */
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
 
    /** 序列在id中占的位数 */
    private final long sequenceBits = 12L;
 
    /** 机器ID向左移12位 */
    private final long workerIdShift = sequenceBits;
 
    /** 数据标识id向左移17位(12+5) */
    private final long datacenterIdShift = sequenceBits + workerIdBits;
 
    /** 时间截向左移22位(5+5+12) */
    private final long timestampLeftShift = sequenceBits + workerIdBits
            + datacenterIdBits;
 
    /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);
 
    /** 工作机器ID(0~31) */
    private long workerId;
 
    /** 数据中心ID(0~31) */
    private long datacenterId;
 
    /** 毫秒内序列(0~4095) */
    private long sequence = 0L;
 
    /** 上次生成ID的时间截 */
    private long lastTimestamp = -1L;
 
    /**
     * 构造函数
     *
     * @param workerId
     *            工作ID (0~31)
     * @param datacenterId
     *            数据中心ID (0~31)
     */
    public SnowflakeIdWorker(long workerId, long datacenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format(
                    "worker Id can't be greater than %d or less than 0",
                    maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format(
                    "datacenter Id can't be greater than %d or less than 0",
                    maxDatacenterId));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }
 
    /**
     * 获得下一个ID (该方法是线程安全的)
     *
     * @return SnowflakeId
     */
    public synchronized long nextId() {
        long timestamp = timeGen();
 
        // 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(
                    String.format(
                            "Clock moved backwards.  Refusing to generate id for %d milliseconds",
                            (lastTimestamp - timestamp)));
        }
 
        // 如果是同一时间生成的,则进行毫秒内序列
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            // 毫秒内序列溢出
            if (sequence == 0) {
                // 阻塞到下一个毫秒,获得新的时间戳
                timestamp = tilNextMillis(lastTimestamp);
            }
        }
        // 时间戳改变,毫秒内序列重置
        else {
            sequence = 0L;
        }
 
        // 上次生成ID的时间截
        lastTimestamp = timestamp;
 
        // 移位并通过或运算拼到一起组成64位的ID
        return ((timestamp - twepoch) << timestampLeftShift) //
                | (datacenterId << datacenterIdShift) //
                | (workerId << workerIdShift) //
                | sequence;
    }
 
    /**
     * 阻塞到下一个毫秒,直到获得新的时间戳
     *
     * @param lastTimestamp
     *            上次生成ID的时间截
     * @return 当前时间戳
     */
    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }
 
    /**
     * 返回以毫秒为单位的当前时间
     *
     * @return 当前时间(毫秒)
     */
    protected long timeGen() {
        return System.currentTimeMillis();
    }
 
    //测试方法
    public static void main(String[] args) {
 
        // 假设我们有一个工作机器ID为1,数据中心ID为1的环境
        long workerId = 1L;
        long datacenterId = 1L;
        
        // 创建一个SnowflakeIdWorker实例
        SnowflakeIdWorker idWorker = new SnowflakeIdWorker(workerId, datacenterId);
        
        // 生成并打印10个ID作为示例
        for (int i = 0; i < 10; i++) {
            long id = idWorker.nextId();
            System.out.println(id);
        }
    }
 
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值