分布式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生成系统。

相关推荐
周GZ20 分钟前
简单讲解Java中静态方法与 实例方法
java·开发语言
疯狂打码的少年31 分钟前
【数据结构】树的基本概念与二叉树定义
java·数据结构·笔记·算法
盗理者1 小时前
AI Agent 技能分享|SQL 性能诊断与优化
java·sql·spring·skill
GitLqr1 小时前
Java 26 终于原生支持 HTTP/3 了:告别 Netty,直接用 QUIC
java·netty·http3
用户40966601317511 小时前
Jackson 序列化:@JsonIgnore / @JsonProperty / @JsonFormat / @JsonInclude / @JsonUnwrapped 一次讲清楚
java·后端
码路漫漫1 小时前
用了 Caffeine,消息为什么还是被处理了两次?
java
云和数据.ChenGuang2 小时前
fastapi项目拆分实战数据模型
java·服务器·数据库·人工智能·深度学习·fastapi·强化学习
小龙报2 小时前
【优选算法】1.搜索插入位置 2.x的平方根
java·c语言·数据结构·c++·python·算法·蓝桥杯
山荷枝2 小时前
04-框架--SpringBoot
java·spring boot·后端
linux修理工2 小时前
内存占用 99% 且 Java 程序较多的优化建议
java·开发语言