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

相关推荐
m0_474606787 小时前
JAVA - 使用Apache POI 自定义报表字段手写导出(支持-合并单元格)
java·开发语言·apache
zhz52147 小时前
Spring Boot 接入国密实战:传输加密(TLCP)+ 密码加密(SM4)
java·spring boot·后端·国密·sm4
人道领域8 小时前
【LeetCode刷题日记】617.合并二叉树(空间换安全,还是原地省内存)
java·数据结构·算法·leetcode
独自破碎E8 小时前
机器人Java后端算法笔试题解析
java·windows·算法
我是一颗柠檬8 小时前
【JDK8新特性】函数式接口Day2
java·开发语言·后端·intellij-idea
Bat U8 小时前
JavaEE|JVM
java·jvm·java-ee
Mahir088 小时前
Spring Boot 自动装配深度解密:从原理到自定义 Starter 实战
java·spring boot·后端·自动装配·自定义starter·大厂面试题
淘源码d8 小时前
产科系统源码,数字产科源码,Java(后端) + Vue + ElementUI(前端) + MySQL(数据库),确保系统稳定性与扩展性。
java·源码·数字产科·产科系统·智能化孕产服务·高危五色预警·智慧产科
wand codemonkey9 小时前
SpringbootWeb【入门】+MySQL【安装】+【DataDrip安装 】+【连接MySQL】
java·mysql·mybatis