uuid-snowflake

  1. UUID算法
    1. 案例:
    2. 优势:
    3. 局限性:
  2. Snowflake算法
    1. 案例:
    2. 优势:
    3. 局限性:
  3. 使用场景
  4. 总结:

场景:

在n个分布式数据库中,用数据库自带的自增ID作为主键是不合理的,过滤出用户是很有重复

需求:在分布式系统中生成一个全局唯一的ID,满足唯一性、有序性、可扩展性等要求。

  • 唯一性
  • 有序性
  • 可扩展性

UUID算法

UUID(Universally Unique Identifier)是一个128位的全局唯一标识符,标准形式包含32个十六进制数字,以连字号分为五段(8-4-4-4-12)

UUID 是一个 128 位的数字,通常以 36 个字符的字符串形式表示,如:

bb0fc150-5d3a-11f0-84ac-ec2e9871c874

不同版本的UUID实现原理不大相同

版本 名称 特点
v1 基于时间戳和 MAC 地址 可追溯生成时间和设备
v3 基于命名空间的 MD5 哈希 输入相同则输出相同
v4 基于随机数 最常用,完全随机
v5 基于命名空间的 SHA-1 哈希 类似 v3,但更安全
v6+ 新提案,优化排序性能 尚未广泛支持

案例:

时间戳 + MAC地址

简单的模拟:

import time
import random
import uuid as py_uuid
import struct


def get_mac_address():
    """获取本机 MAC 地址(48 位)"""
    mac = py_uuid.getnode()
    return mac & 0xFFFFFFFFFFFF


def get_timestamp():
    """获取 UUID v1 所需的时间戳(100ns 单位,自 1582-10-15 起)"""
    uuid_epoch = 12219292800  # 与 Unix 时间戳的差值(秒)
    timestamp = int((time.time() + uuid_epoch) * 1e7)
    return timestamp


def generate_uuid_v1():
    timestamp = get_timestamp()
    clock_seq = random.getrandbits(14)
    node = get_mac_address()

    time_low = timestamp & 0xFFFFFFFF
    time_mid = (timestamp >> 32) & 0xFFFF
    time_hi_and_version = ((timestamp >> 48) & 0x0FFF) | (1 << 12)
    clock_seq_low = clock_seq & 0xFF
    clock_seq_hi_and_reserved = ((clock_seq >> 8) & 0x3F) | 0x80

    fields = struct.pack(">IHHBB6s",
                         time_low,
                         time_mid,
                         time_hi_and_version,
                         clock_seq_hi_and_reserved,
                         clock_seq_low,
                         node.to_bytes(6, 'big'))

    return py_uuid.UUID(bytes=fields)


if __name__ == "__main__":
    print("手动生成的 UUID v1:", generate_uuid_v1())

优势:

实现简单,所有主流语言都有原生支持 , 完全分布式生成,无需中心节点协调 - 碰撞概率极低(v4版本理论碰撞概率为10^-37) - 跨系统、跨组织保证唯一性.

局限性:

最大的问题就是无序性,在分布式数据库中,最理想的情况下 新ID > 老ID

早期版本安全性有些问题,无内置时间戳

Snowflake算法

雪花算法是Twitter开源的一种分布式ID生成算法,生成的ID是64位的长整型,其精妙的位分配结构如下:

+------+----------------------+----------------+------------+
| 符号位 |      时间戳(41位)     | 工作节点(10位)  | 序列号(12位) |
+------+----------------------+----------------+------------+
  1bit          41bits              10bits         12bits
  1. 时间戳部分:毫秒级精度,从自定义纪元开始(通常为算法实现时间),可用约69年
  2. 工作节点ID:通常分为5位数据中心ID和5位机器ID,支持32个数据中心×32台机器
  3. 序列号:每毫秒重新计数,支持单机每毫秒4096个ID生成

案例:


class SimpleSnowflake:
    """简单版 Snowflake 算法实现(64位整数)"""
    def __init__(self, datacenter_id=0, worker_id=0):
        self.datacenter_id = datacenter_id & 0x1F  # 5位
        self.worker_id = worker_id & 0x1F          # 5位
        self.sequence = 0
        self.last_timestamp = -1
        self.epoch = 1609459200000  # 2021-01-01 00:00:00 UTC (自定义起始时间)

    def _timestamp(self):
        return int(time.time() * 1000)

    def next_id(self):
        timestamp = self._timestamp()
        if timestamp == self.last_timestamp:
            self.sequence = (self.sequence + 1) & 0xFFF  # 12位
            if self.sequence == 0:
                while self._timestamp() <= self.last_timestamp:
                    pass
                timestamp = self._timestamp()
        else:
            self.sequence = 0
        self.last_timestamp = timestamp
        id = ((timestamp - self.epoch) << 22) | (self.datacenter_id << 17) | (self.worker_id << 12) | self.sequence
        return id
    
    
if __name__ == "__main__":
    snowflake = SimpleSnowflake(datacenter_id=1, worker_id=1)
    print("简单 Snowflake ID:", snowflake.next_id())

优势:

  • 性能优异:本地生成无网络IO,单机QPS可达400万+
  • 存储高效:仅需8字节长整型存储
  • 索引友好:趋势递增特性大幅提升B+树索引效率
  • 时间可溯:可通过ID反解析出生成时间戳
  • 业务集成:可将业务信息编码到工作节点ID中

局限性:

  1. 时钟回拨问题:需要设计时钟同步机制或异常处理策略
  2. 节点配置:需要维护机器ID的分配
  3. 扩展限制:单数据中心最多32台机器,每毫秒4096ID的限制
  4. 寿命限制:41位时间戳约69年后会溢出

使用场景

优先UUID的场景:

  • 快速开发,无其他依赖
  • 多个系统需要唯一标识符时
  • noSQL情况: 如MongoDB
  • 临时存储:如会话ID、一次性令牌等

优先雪花的场景:

  • 关系型数据库:MySQL InnoDB等聚簇索引存储
  • 与时间相关的场景
  • 需要携带业务信息

总结:

这两个传统的算法都很原始,但是各种优化版本都没问题,认清楚场景再选择,才能发挥出其最大的作用。

github