7.雪花算法(高并发场景下自增id的生成)

本文深入解析了雪花算法,一种用于生成全局唯一ID的高效算法。详细介绍了算法的原理,包括时间戳、机房ID、机器ID及序列号的分配机制,并提供了Java实现代码,适用于高并发场景下的ID生成。

前言:

在高并发场景下,如果使用mysql数据库自增的id,那么同一时间内会有重复的id生成。


一、雪花算法的原理:

使用一个 64 bit 的 long 型的数字作为全局唯一 id
在这里插入图片描述

  • 第一个部分是1 个 bit:0,这个是无意义的。由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0

  • 第二个部分是 41 个 bit:表示的是时间戳。该时间戳存储的是时间截的差值(当前时间截 - 开始时间截)得到的值,这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69

  • 第三个部分是 5 个 bit:表示的是机房 id,10001。

  • 第四个部分是 5 个 bit:表示的是机器 id,1 1001。

  • 第五个部分是 12 个 bit:表示的序号,就是某个机房某台机器上这一毫秒内同时生成的 id 的序号,0000 00000000。

二、雪花算法的实现代码

import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import java.net.*;

/**
 * Twitter的分布式自增ID雪花算法snowflake (Java版)
 **/

public class SnowFlake {

    public static Long mac;
    public static Long ip;


    /**
     * 开始时间截 (2015-01-01)
     */
    private final static long twepoch = 1420041600000L;

    /**
     * 机器id所占的位数
     */
    private final static long workerIdBits = 5L;

    /**
     * 数据标识id所占的位数
     */
    private final static long datacenterIdBits = 5L;

    /**
     * 序列在id中占的位数
     */
    private final static long sequenceBits = 12L;

    /**
     * 机器ID向左移12位
     */
    private final static long workerIdShift = sequenceBits;

    /**
     * 数据标识id向左移17位(12+5)
     */
    private final static long datacenterIdShift = sequenceBits + workerIdBits;

    /**
     * 时间截向左移22位(5+5+12)
     */
    private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

    /**
     * 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)
     */
    private final static long sequenceMask = -1L ^ (-1L << sequenceBits);

    /**
     * 毫秒内序列(0~4095)
     */
    private static long sequence = 0L;

    /**
     * 上次生成ID的时间截
     */
    private static long lastTimestamp = -1L;

    /**
     * 获得下一个ID (该方法是线程安全的)
     *
     * @return SnowflakeId
     */
    private static 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;

        if (mac == null) {
            mac = getMac();
        }
        if (ip == null) {
            ip = getIp();
        }
        // 移位并通过或运算拼到一起组成64位的ID
        return ((timestamp - twepoch) << timestampLeftShift) //
                | (mac << datacenterIdShift) //
                | (ip << workerIdShift) //
                | sequence;
    }

    /**
     * 阻塞到下一个毫秒,直到获得新的时间戳
     *
     * @param lastTimestamp 上次生成ID的时间截
     * @return 当前时间戳
     */
    protected static long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

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


    public static Long getMac() {
        InetAddress adress = null;
        try {
            adress = InetAddress.getLocalHost();
            NetworkInterface net = NetworkInterface.getByInetAddress(adress);
            byte[] macBytes = net.getHardwareAddress();
            int sum = 0;
            for (int b : macBytes) {
                sum += Math.abs(b);
            }
            return (long) (sum % 32);
        } catch (Exception e) {
            e.printStackTrace();
            return RandomUtils.nextLong(0, 31);
        }

    }

    public static Long getIp() {
        try {
            String hostAddress = Inet4Address.getLocalHost().getHostAddress();
            int[] ints = StringUtils.toCodePoints(hostAddress);
            int sums = 0;
            for (int b : ints) {
                sums += b;
            }
            return (long) (sums % 32);
        } catch (UnknownHostException e) {
            e.printStackTrace();
            // 如果获取失败,则使用随机数备用
            return RandomUtils.nextLong(0, 31);
        }

    }

    public static String getNextId() {
        return nextId() + String.format("%02d", System.nanoTime() % 100);
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {

            System.out.println(SnowFlake.getNextId());
        }
    }
}
雪花算法高并发场景下具有出色的性能表现。其核心思想是通过组合时间戳、机器 ID 和序列号来生成全局唯一的 64 位长整型 ID,具有全局唯一性、有序性和高性能的特点,适合高并发场景[^1]。 经测试,雪花算法每秒可以产生 26 万个自可排序的 ID生成的结果是一个 64Bit 大小的整数,为一个 Long 型(转换成字符串后长度最多 19),在分布式系统内不会产生 ID 碰撞(由 datacenter 数据中心和 workerID 机器码做区分),并且效率较高[^2]。 雪花算法将 64 位整数划分为时间戳(41 位)、机器 ID(10 位)和序列号(12 位),结合高位位移运算实现毫秒级 ID 生成,单机每秒可承载超 400 万次请求。其核心逻辑通过时间有序性确保 ID 单调递,同时借助机器 ID 的分段设计支持横向扩展[^3]。 以下是一个简单的雪花算法 Python 实现示例: ```python import time # 起始时间戳,可自定义 START_TIMESTAMP = 1609459200000 # 机器 ID 所占位数 WORKER_ID_BITS = 5 # 数据中心 ID 所占位数 DATACENTER_ID_BITS = 5 # 序列号所占位数 SEQUENCE_BITS = 12 # 机器 ID 最大值 MAX_WORKER_ID = -1 ^ (-1 << WORKER_ID_BITS) # 数据中心 ID 最大值 MAX_DATACENTER_ID = -1 ^ (-1 << DATACENTER_ID_BITS) # 机器 ID 向左移位数 WORKER_ID_SHIFT = SEQUENCE_BITS # 数据中心 ID 向左移位数 DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS # 时间戳向左移位数 TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS # 序列号掩码 SEQUENCE_MASK = -1 ^ (-1 << SEQUENCE_BITS) class SnowflakeIDGenerator: def __init__(self, datacenter_id, worker_id): if datacenter_id > MAX_DATACENTER_ID or datacenter_id < 0: raise ValueError(f"Datacenter ID must be between 0 and {MAX_DATACENTER_ID}") if worker_id > MAX_WORKER_ID or worker_id < 0: raise ValueError(f"Worker ID must be between 0 and {MAX_WORKER_ID}") self.datacenter_id = datacenter_id self.worker_id = worker_id self.sequence = 0 self.last_timestamp = -1 def _get_current_timestamp(self): return int(time.time() * 1000) def _wait_for_next_millis(self, last_timestamp): timestamp = self._get_current_timestamp() while timestamp <= last_timestamp: timestamp = self._get_current_timestamp() return timestamp def next_id(self): timestamp = self._get_current_timestamp() if timestamp < self.last_timestamp: raise Exception("Clock moved backwards. Refusing to generate id for {} milliseconds".format( self.last_timestamp - timestamp)) if timestamp == self.last_timestamp: self.sequence = (self.sequence + 1) & SEQUENCE_MASK if self.sequence == 0: timestamp = self._wait_for_next_millis(self.last_timestamp) else: self.sequence = 0 self.last_timestamp = timestamp id = ((timestamp - START_TIMESTAMP) << TIMESTAMP_LEFT_SHIFT) | \ (self.datacenter_id << DATACENTER_ID_SHIFT) | \ (self.worker_id << WORKER_ID_SHIFT) | \ self.sequence return id ``` ### 使用示例 ```python # 创建雪花算法 ID 生成器实例 generator = SnowflakeIDGenerator(datacenter_id=1, worker_id=1) # 生成一个 ID id = generator.next_id() print(id) ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值