分布式ID生成方案详解与实战

分布式ID生成方案详解与实战

一、分布式ID概述

在分布式系统中,全局唯一ID是核心需求之一,用于标识业务实体。

1.1 ID生成要求

特性 说明
唯一性 全局唯一,永不重复
递增性 按时间递增,便于排序
高性能 高并发下低延迟
可扩展性 支持水平扩展
安全性 不可预测,防止枚举攻击

1.2 常见ID生成方案对比

方案 优点 缺点 适用场景
数据库自增 简单可靠 单点瓶颈 中小型系统
UUID 无中心依赖 无序、过长 对ID有序性要求低
Snowflake 高性能、有序 依赖时钟 分布式高并发
Redis自增 高性能 需要部署Redis 缓存场景
数据库分段 高可用 实现复杂 大型分布式系统

二、Snowflake算法

2.1 Snowflake结构

复制代码
┌─────────────────────────────────────────────────────────────────────┐
│                        64位Snowflake ID                            │
├─────────────────────────────────────────────────────────────────────┤
│  1位  │   41位时间戳    │   10位机器ID    │   12位序列号          │
│  符号  │  (毫秒级)       │  (5位数据中心+5位工作节点)  │          │
│       │  可表示约69年    │  支持1024个节点   │  每毫秒4096个ID     │
└─────────────────────────────────────────────────────────────────────┘

2.2 Java实现

java 复制代码
public class SnowflakeIdGenerator {
    
    private final long twepoch = 1609459200000L; // 2021-01-01 00:00:00
    private final long workerIdBits = 5L;
    private final long datacenterIdBits = 5L;
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    private final long sequenceBits = 12L;
    private final long workerIdShift = sequenceBits;
    private final long datacenterIdShift = sequenceBits + workerIdBits;
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);
    
    private long workerId;
    private long datacenterId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;
    
    public SnowflakeIdGenerator(long workerId, long datacenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException("Worker ID out of range");
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException("Datacenter ID out of range");
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }
    
    public synchronized long nextId() {
        long timestamp = timeGen();
        
        if (timestamp < lastTimestamp) {
            throw new RuntimeException("Clock moved backwards");
        }
        
        if (timestamp == lastTimestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }
        
        lastTimestamp = timestamp;
        
        return ((timestamp - twepoch) << timestampLeftShift)
                | (datacenterId << datacenterIdShift)
                | (workerId << workerIdShift)
                | sequence;
    }
    
    private long timeGen() {
        return System.currentTimeMillis();
    }
    
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }
}

2.3 时钟回拨处理

java 复制代码
public synchronized long nextId() {
    long timestamp = timeGen();
    
    if (timestamp < lastTimestamp) {
        long offset = lastTimestamp - timestamp;
        if (offset <= 5) {
            try {
                wait(offset << 1);
                timestamp = timeGen();
                if (timestamp < lastTimestamp) {
                    throw new RuntimeException("Clock moved backwards");
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        } else {
            throw new RuntimeException("Clock moved backwards too much");
        }
    }
    
    if (timestamp == lastTimestamp) {
        sequence = (sequence + 1) & sequenceMask;
        if (sequence == 0) {
            timestamp = tilNextMillis(lastTimestamp);
        }
    } else {
        sequence = 0L;
    }
    
    lastTimestamp = timestamp;
    return ((timestamp - twepoch) << timestampLeftShift)
            | (datacenterId << datacenterIdShift)
            | (workerId << workerIdShift)
            | sequence;
}

三、数据库自增ID

3.1 单库自增

sql 复制代码
CREATE TABLE `sequence` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `biz_type` VARCHAR(64) NOT NULL COMMENT '业务类型',
    `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_biz_type` (`biz_type`)
) ENGINE=InnoDB AUTO_INCREMENT=100000 DEFAULT CHARSET=utf8mb4;

3.2 分段ID生成

java 复制代码
public class SegmentIdGenerator {
    
    private volatile long currentId;
    private volatile long maxId;
    private final Object lock = new Object();
    private final JdbcTemplate jdbcTemplate;
    
    public long nextId(String bizType) {
        if (currentId >= maxId) {
            synchronized (lock) {
                if (currentId >= maxId) {
                    fetchSegment(bizType);
                }
            }
        }
        return ++currentId;
    }
    
    private void fetchSegment(String bizType) {
        String sql = "UPDATE sequence SET id = LAST_INSERT_ID(id + ?) WHERE biz_type = ?";
        jdbcTemplate.update(sql, 1000, bizType);
        
        String querySql = "SELECT LAST_INSERT_ID()";
        long newId = jdbcTemplate.queryForObject(querySql, Long.class);
        
        this.currentId = newId;
        this.maxId = newId + 1000 - 1;
    }
}

3.3 多库多表方案

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                      ID生成器集群                              │
├─────────────────────────────────────────────────────────────────┤
│  DB1: sequence_0    DB2: sequence_1    DB3: sequence_2       │
│     step=3, start=0    step=3, start=1    step=3, start=2    │
│     IDs: 0,3,6,9...   IDs: 1,4,7,10...  IDs: 2,5,8,11...   │
└─────────────────────────────────────────────────────────────────┘

四、Redis自增ID

4.1 基本实现

java 复制代码
public class RedisIdGenerator {
    
    private final StringRedisTemplate redisTemplate;
    private final String keyPrefix = "sequence:";
    private final long step = 100;
    
    public long nextId(String bizType) {
        String key = keyPrefix + bizType;
        Long current = redisTemplate.opsForValue().increment(key);
        
        if (current <= step) {
            redisTemplate.opsForValue().set(key, String.valueOf(current + step * 10));
        }
        
        return current;
    }
}

4.2 Lua脚本保证原子性

lua 复制代码
local key = KEYS[1]
local step = tonumber(ARGV[1])
local current = redis.call('INCR', key)

if current <= step then
    redis.call('SET', key, current + step * 10)
end

return current

五、UUID方案

5.1 UUID生成

java 复制代码
// 标准UUID
String uuid = UUID.randomUUID().toString();

// 去除横线
String compactUuid = uuid.replace("-", "");

// 简化UUID(前8位)
String shortUuid = compactUuid.substring(0, 8);

5.2 基于时间戳的UUID

java 复制代码
public class TimeBasedUUIDGenerator {
    
    public String generate() {
        long timestamp = System.currentTimeMillis();
        String timestampHex = Long.toHexString(timestamp);
        String randomPart = UUID.randomUUID().toString().substring(16);
        return timestampHex + randomPart;
    }
}

六、美团Leaf方案

6.1 Leaf架构

复制代码
┌─────────────────────────────────────────────────────────────┐
│                      Leaf ID Generator                     │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐   │
│  │  Segment    │    │  Snowflake  │    │   DB Backup │   │
│  │   Mode      │    │   Mode      │    │             │   │
│  └──────┬──────┘    └──────┬──────┘    └──────┬──────┘   │
│         │                  │                  │           │
│         └──────────────────┴──────────────────┘           │
│                            │                              │
│                            ▼                              │
│                  ┌───────────────┐                        │
│                  │   REST API    │                        │
│                  └───────────────┘                        │
└─────────────────────────────────────────────────────────────┘

6.2 Segment模式配置

yaml 复制代码
leaf:
  segment:
    enable: true
    datasource:
      url: jdbc:mysql://localhost:3306/leaf?useUnicode=true&characterEncoding=utf-8
      username: root
      password: password

6.3 Snowflake模式配置

yaml 复制代码
leaf:
  snowflake:
    enable: true
    zk:
      address: localhost:2181
      path: /leaf/snowflake

七、实战建议

7.1 方案选择策略

场景 推荐方案 原因
中小型系统 数据库自增 简单可靠,无需额外组件
高并发系统 Snowflake 高性能,毫秒级生成数万ID
跨数据中心 Leaf/Snowflake 支持多节点部署
对ID有序性要求高 Segment ID连续递增

7.2 性能对比

方案 QPS 延迟(ms) 优点 缺点
Snowflake 100万+ <1 极高性能 依赖时钟
Redis 50万+ 1-5 高性能 需要Redis
Segment 10万+ 5-10 ID连续 依赖DB
UUID 10万+ <1 无依赖 无序、长

7.3 部署建议

  1. Worker ID分配:通过配置中心或ZK自动分配
  2. 时钟同步:使用NTP服务同步时间
  3. 容灾备份:部署多节点,支持故障转移
  4. 监控告警:监控ID生成速率、异常情况

八、总结

分布式ID生成方案需要根据业务场景选择:

  • Snowflake适合高并发、对ID有序性有要求的场景
  • Segment适合需要连续ID的场景
  • UUID适合对ID无特殊要求的场景
  • Redis适合缓存场景或已有Redis集群的系统

通过合理选择和配置,可以构建可靠、高性能的分布式ID生成系统。

相关推荐
karry_k6 小时前
MyBatis批量insert-select踩坑:useGeneratedKeys=true 可能让PostgreSQL返回大量插入结果
java·后端
karry_k6 小时前
PostgreSQL 在 MyBatis 中执行正常 SQL 失效:一次 DELETE USING 踩坑记录
java·后端
SamDeepThinking10 小时前
从源码到代码:MyBatis-Flex 与 MyBatis-Plus 的逐项对比
java·后端·程序员
她的男孩13 小时前
Spring Boot 接 Flowable 工作流:用 3 个注解搭一个请假审批流程
java·后端·架构
荣码14 小时前
LLM结构化输出:让AI返回JSON而不是废话,我踩了4个坑
java·python
plainGeekDev16 小时前
Gson → kotlinx.serialization
android·java·kotlin
小bo波1 天前
Java Swing 图形用户界面实验 —— 从算术练习到游戏开发的完整实践
java·课程设计·gui·游戏开发·扫雷·swing
咖啡八杯1 天前
GoF设计模式——备忘录模式
java·后端·spring·设计模式