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

相关推荐
别动我齐刘海8 小时前
C++ 工程化学习
java·linux·前端·c++·学习·机器学习·机器人
code2cat9 小时前
Java进阶篇之AtomicLongFieldUpdater:把累计额度留在对象字段中
java·cas·并发编程
WeiXin_DZbishe9 小时前
基于springboot城市公交实时运营与调度系统-计算机毕设 附源码【课程设计】66173
java·vue.js·spring boot·后端·python·php·课程设计
泡茶喝茶写代码9 小时前
A股量化数据工程:从 REST 接口到策略信号(第 18 篇):放量缩量异动扫描
java·python·股票数据api·股票数据api接口·股票api数据接口·股票量化数据api·股票量化数据接口
wewowilling10 小时前
认识微服务
java·运维·微服务·springcloud
代码什么用10 小时前
Java Web 7-10
java·servlet
老三牛擦11 小时前
订单履约流程
jvm
蜗牛互联网12 小时前
GPT-6 与 Intelligent UI:从回答到可审计操作的产品迁移
java·人工智能·后端
SL_staff12 小时前
运维救火实录:钉钉考勤API变更引发工资计算异常,我们用JVS-Logic可视化逻辑引擎实现分钟级热修复
java·mysql·全栈