ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

分布式唯一ID生成方案全解析:从数据库自增到Snowflake实战

分布式唯一ID生成方案全解析:从数据库自增到Snowflake实战 大家好我是专注于技术分享的博主。今天我们来聊聊一个在分布式系统、微服务架构中极其重要却又常常被开发者忽视的环节——唯一ID生成。无论是电商系统的订单号、社交平台的消息ID还是你刚刚在闲鱼上发布的商品编号背后都需要一套稳定、高效、不重复的ID生成机制来支撑。想象一下如果你的系统生成的订单号重复了会导致用户支付错乱如果商品ID冲突会让买家看到完全不同的商品信息。尤其是在高并发场景下如何快速生成全局唯一的ID同时保证ID的可读性、趋势递增和一定的业务含义是每个后端开发者必须面对的挑战。本文将系统性地拆解唯一ID生成的多种方案从最基础的数据库自增ID到适合分布式环境的Snowflake算法再到结合中间件的成熟实践。我们会深入原理给出可运行的代码示例并分析各自的优缺点和适用场景。无论你是正在学习分布式基础的新手还是为现有系统寻找更优ID方案的架构师都能从中获得实用的参考。1. 唯一ID的核心诉求与常见方案概览在深入具体实现之前我们首先要明确一个优秀的分布式唯一ID生成器需要满足哪些核心诉求1.1 核心诉求全局唯一这是最基本的要求必须确保在分布式系统下任何两个ID都不相同。趋势递增ID最好能够大致有序递增。这并非严格要求绝对连续但趋势递增对数据库索引友好例如InnoDB的B树索引能有效避免页分裂提升写入性能。高性能生成速度要快不能成为系统的性能瓶颈。高可用生成服务要具备高可用性避免单点故障。信息安全ID本身不应透露业务敏感信息如订单数量、用户数即避免“可被猜测”。长度适中在满足存储和传输效率的前提下ID不宜过长通常控制在64位或更短。1.2 常见方案对比为了让大家有一个全局的认识我们先通过一个表格快速浏览几种主流方案方案实现方式优点缺点适用场景UUID标准算法生成128位字符串本地生成性能极高全球唯一。无序导致数据库写入性能差长度过长无业务含义。对性能要求极高且不关心存储与索引效率的场景。数据库自增ID利用数据库AUTO_INCREMENT实现简单绝对有序递增。强依赖DB存在单点故障和性能瓶颈分库分表时难以处理。单机或小规模应用并发不高的场景。数据库号段模式从数据库批量获取ID号段缓存在本地减轻数据库压力性能较高。仍依赖数据库服务器重启可能丢失号段需持久化。大部分分布式业务场景QPS在几千到几万级别。Snowflake算法时间戳 工作机器ID 序列号本地生成性能高趋势递增长度适中64位。依赖机器时钟时钟回拨会导致ID重复需要分配机器ID。并发量高机器时钟可靠的分布式环境。Redis INCR利用Redis的原子自增命令性能优于数据库可做集群。依赖Redis有网络开销持久化策略影响数据可靠性。已有Redis集群且对性能有一定要求的场景。Leaf/美团方案号段模式与Snowflake模式的结合与优化高可用、高吞吐、可监控。架构相对复杂需要引入独立服务。大型互联网公司对ID生成有极高要求。接下来我们将选取其中最具代表性的三种方案——数据库自增ID、Snowflake算法和数据库号段模式进行深度剖析和实战演示。2. 环境准备与项目说明在开始编码之前我们需要搭建一个简单的演示环境。本文将以Java语言为例使用Spring Boot框架快速构建演示项目。环境要求操作系统Windows / macOS / Linux 均可JDK1.8 或以上版本构建工具Maven 3.6IDEIntelliJ IDEA 或 Eclipse数据库MySQL 5.7 (用于演示数据库方案)Redis5.0 (可选用于演示Redis方案)创建Spring Boot项目你可以通过 Spring Initializr 网站或IDE直接创建。 所需依赖Spring WebSpring Data JPA (用于数据库操作)MySQL DriverLombok (简化代码可选)项目创建后pom.xml关键依赖如下dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency /dependencies数据库配置 (application.properties):spring.datasource.urljdbc:mysql://localhost:3306/id_demo?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai spring.datasource.usernameroot spring.datasource.passwordyourpassword spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver spring.jpa.hibernate.ddl-autoupdate spring.jpa.show-sqltrue spring.jpa.properties.hibernate.dialectorg.hibernate.dialect.MySQL5InnoDBDialect3. 方案一数据库自增ID这是最直观、历史最悠久的方案。我们通过一个简单的实体类来演示。3.1 原理与实现利用MySQL的AUTO_INCREMENT关键字每次插入新记录时数据库会自动为该字段生成一个比当前最大值大1的整数。创建实体类// 文件路径src/main/java/com/example/idemo/entity/OrderAutoId.java package com.example.idemo.entity; import lombok.Data; import javax.persistence.*; Entity Table(name t_order_auto) Data public class OrderAutoId { Id GeneratedValue(strategy GenerationType.IDENTITY) // 关键注解使用数据库自增 private Long id; // 自增ID private String orderNo; // 其他业务字段如订单号可由ID拼接而成 private String productName; private BigDecimal amount; // ... 其他字段 }GeneratedValue(strategy GenerationType.IDENTITY)是JPA中声明使用数据库自增主键的注解。创建Repository// 文件路径src/main/java/com/example/idemo/repository/OrderAutoIdRepository.java package com.example.idemo.repository; import com.example.idemo.entity.OrderAutoId; import org.springframework.data.jpa.repository.JpaRepository; public interface OrderAutoIdRepository extends JpaRepositoryOrderAutoId, Long { }编写测试Controller// 文件路径src/main/java/com/example/idemo/controller/AutoIdController.java package com.example.idemo.controller; import com.example.idemo.entity.OrderAutoId; import com.example.idemo.repository.OrderAutoIdRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; RestController public class AutoIdController { Autowired private OrderAutoIdRepository repository; PostMapping(/order/auto) public OrderAutoId createOrder(RequestBody OrderAutoId order) { // 无需设置id保存后会自动生成并返回 return repository.save(order); } }3.2 运行与验证启动Spring Boot应用。使用Postman或curl发送一个POST请求到http://localhost:8080/order/autoBody为JSON{ productName: iPhone 15, amount: 6999.00 }响应中你会看到返回的订单对象包含了数据库自动生成的id如{id: 1, ...}。连续插入几次id会依次递增。3.3 优缺点深度分析优点绝对简单无需任何额外开发数据库原生支持。绝对有序ID连续递增对索引极其友好。缺点强耦合与单点故障ID生成强依赖单一数据库。数据库若宕机整个ID生成服务乃至写服务都会不可用。性能瓶颈所有插入操作都需要访问数据库来获取ID在高并发下数据库的写压力会非常大容易成为瓶颈。扩展性差在分库分表架构下每个库或表都有自己的自增序列会导致全局ID重复。虽然可以设置auto_increment_increment和auto_increment_offset来设置步长和起始值但管理复杂扩容麻烦。安全性问题ID连续递增容易被爬虫遍历泄露业务数据量。结论仅适用于非常小型的、单数据库的、并发量极低的内部系统或原型验证阶段。4. 方案二Snowflake算法Twitter开源的Snowflake算法是分布式ID生成的经典解决方案。它生成一个64位的Long型ID结构如下0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 0000000000001位符号位始终为041位时间戳毫秒级约可使用69年10位工作机器ID5位数据中心ID 5位机器ID最多支持1024个节点12位序列号每毫秒可生成4096个ID4.1 核心实现我们来实现一个简化的Snowflake ID生成器。// 文件路径src/main/java/com/example/idemo/utils/SnowflakeIdWorker.java package com.example.idemo.utils; import org.springframework.stereotype.Component; Component public class SnowflakeIdWorker { // Fields /** 开始时间截 (2020-01-01) */ private final long twepoch 1577808000000L; /** 机器id所占的位数 */ private final long workerIdBits 5L; /** 数据标识id所占的位数 */ private final long datacenterIdBits 5L; /** 支持的最大机器id结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */ private final long maxWorkerId -1L ^ (-1L workerIdBits); /** 支持的最大数据标识id结果是31 */ private final long maxDatacenterId -1L ^ (-1L datacenterIdBits); /** 序列在id中占的位数 */ private final long sequenceBits 12L; /** 机器ID向左移12位 */ private final long workerIdShift sequenceBits; /** 数据标识id向左移17位(125) */ private final long datacenterIdShift sequenceBits workerIdBits; /** 时间截向左移22位(5512) */ private final long timestampLeftShift sequenceBits workerIdBits datacenterIdBits; /** 生成序列的掩码这里为4095 (0b1111111111110xfff4095) */ private final long sequenceMask -1L ^ (-1L sequenceBits); /** 工作机器ID(0~31) */ private long workerId; /** 数据中心ID(0~31) */ private long datacenterId; /** 毫秒内序列(0~4095) */ private long sequence 0L; /** 上次生成ID的时间截 */ private long lastTimestamp -1L; // Constructors /** * 构造函数 * param workerId 工作ID (0~31) * param datacenterId 数据中心ID (0~31) */ public SnowflakeIdWorker(long workerId, long datacenterId) { if (workerId maxWorkerId || workerId 0) { throw new IllegalArgumentException(String.format(worker Id can‘t be greater than %d or less than 0, maxWorkerId)); } if (datacenterId maxDatacenterId || datacenterId 0) { throw new IllegalArgumentException(String.format(datacenter Id can‘t be greater than %d or less than 0, maxDatacenterId)); } this.workerId workerId; this.datacenterId datacenterId; } // Methods /** * 获得下一个ID (该方法是线程安全的) * return SnowflakeId */ public synchronized long nextId() { long timestamp timeGen(); //如果当前时间小于上一次ID生成的时间戳说明系统时钟回退过这个时候应当抛出异常 if (timestamp lastTimestamp) { throw new RuntimeException( String.format(Clock moved backwards. Refusing to generate id for %d milliseconds, lastTimestamp - timestamp)); } //如果是同一时间生成的则进行毫秒内序列 if (lastTimestamp timestamp) { sequence (sequence 1) sequenceMask; //毫秒内序列溢出 if (sequence 0) { //阻塞到下一个毫秒,获得新的时间戳 timestamp tilNextMillis(lastTimestamp); } } //时间戳改变毫秒内序列重置 else { sequence 0L; } //上次生成ID的时间截 lastTimestamp timestamp; //移位并通过或运算拼到一起组成64位的ID return ((timestamp - twepoch) timestampLeftShift) // | (datacenterId datacenterIdShift) // | (workerId workerIdShift) // | sequence; } /** * 阻塞到下一个毫秒直到获得新的时间戳 * param lastTimestamp 上次生成ID的时间截 * return 当前时间戳 */ protected long tilNextMillis(long lastTimestamp) { long timestamp timeGen(); while (timestamp lastTimestamp) { timestamp timeGen(); } return timestamp; } /** * 返回以毫秒为单位的当前时间 * return 当前时间(毫秒) */ protected long timeGen() { return System.currentTimeMillis(); } }4.2 配置与使用我们需要为每个服务实例配置唯一的workerId和datacenterId。在生产环境中这些ID可以通过ZK/Consul/DB配置中心获取或者根据机器IP等信息计算得出。这里我们简化处理通过配置文件注入。在application.properties中配置# Snowflake 配置 snowflake.worker-id1 snowflake.datacenter-id1创建配置类// 文件路径src/main/java/com/example/idemo/config/SnowflakeConfig.java package com.example.idemo.config; import com.example.idemo.utils.SnowflakeIdWorker; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class SnowflakeConfig { Value(${snowflake.worker-id}) private long workerId; Value(${snowflake.datacenter-id}) private long datacenterId; Bean public SnowflakeIdWorker snowflakeIdWorker() { return new SnowflakeIdWorker(workerId, datacenterId); } }在Controller中使用// 文件路径src/main/java/com/example/idemo/controller/SnowflakeController.java package com.example.idemo.controller; import com.example.idemo.utils.SnowflakeIdWorker; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; RestController public class SnowflakeController { Autowired private SnowflakeIdWorker snowflakeIdWorker; GetMapping(/id/snowflake) public String getSnowflakeId() { long id snowflakeIdWorker.nextId(); return String.valueOf(id); } }访问http://localhost:8080/id/snowflake即可获得一个Snowflake ID。4.3 优缺点与时钟回拨问题优点高性能本地生成无网络I/O单机QPS可达数百万。趋势递增基于时间戳整体趋势是递增的。长度适中64位长整型存储和索引效率高。灵活可根据业务调整各部分的位数。缺点时钟回拨问题这是Snowflake最大的挑战。如果服务器时钟发生回拨如NTP同步、人工修改可能导致生成的ID重复。上述代码中通过抛出异常来应对但生产环境需要更健壮的策略如等待时钟追回、使用扩展的位存储过去时间等。机器ID分配需要一套机制来保证workerId和datacenterId的唯一性增加了系统复杂度。ID有规律虽然不连续但根据ID可以反推出生成时间和机器对部分需要隐藏信息的场景不友好。5. 方案三数据库号段模式Segment这是对数据库自增模式的优化核心思想是批量获取。服务每次从数据库申请一个号段比如1~1000加载到内存中然后在本机内存中依次分配。用完后再去数据库获取下一个号段。5.1 数据库表设计首先创建一张表来管理各个业务的号段。-- 文件路径src/main/resources/schema.sql (或直接在MySQL中执行) CREATE TABLE id_generator ( id int(11) NOT NULL AUTO_INCREMENT COMMENT ‘主键‘, biz_tag varchar(128) NOT NULL COMMENT ‘业务标识如order, user‘, max_id bigint(20) NOT NULL COMMENT ‘当前最大可用ID‘, step int(11) NOT NULL COMMENT ‘号段步长‘, version int(11) NOT NULL COMMENT ‘乐观锁版本号‘, description varchar(256) DEFAULT NULL COMMENT ‘描述‘, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_biz_tag (biz_tag) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT‘号段生成器表‘; -- 初始化一个业务标签 INSERT INTO id_generator (biz_tag, max_id, step, version, description) VALUES (‘order‘, 0, 1000, 0, ‘订单ID生成器‘);5.2 核心服务实现我们实现一个从数据库获取号段的服务。实体类// 文件路径src/main/java/com/example/idemo/entity/IdGenerator.java package com.example.idemo.entity; import lombok.Data; import javax.persistence.*; import java.util.Date; Entity Table(name id_generator) Data public class IdGenerator { Id GeneratedValue(strategy GenerationType.IDENTITY) private Integer id; Column(name biz_tag, unique true, nullable false) private String bizTag; Column(name max_id, nullable false) private Long maxId; Column(nullable false) private Integer step; Column(nullable false) private Integer version; private String description; Column(name update_time) private Date updateTime; }Repository// 文件路径src/main/java/com/example/idemo/repository/IdGeneratorRepository.java package com.example.idemo.repository; import com.example.idemo.entity.IdGenerator; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.transaction.annotation.Transactional; public interface IdGeneratorRepository extends JpaRepositoryIdGenerator, Integer { /** * 使用乐观锁更新max_id并返回更新后的实体 */ Modifying Transactional Query(UPDATE IdGenerator g SET g.maxId g.maxId g.step, g.version g.version 1 WHERE g.bizTag :bizTag AND g.version :version) int updateMaxId(Param(bizTag) String bizTag, Param(version) Integer version); IdGenerator findByBizTag(String bizTag); }号段服务类这是核心它管理本地内存中的号段。// 文件路径src/main/java/com/example/idemo/service/SegmentService.java package com.example.idemo.service; import com.example.idemo.entity.IdGenerator; import com.example.idemo.repository.IdGeneratorRepository; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; Service Slf4j public class SegmentService { Autowired private IdGeneratorRepository idGeneratorRepository; // 内存中存储各个业务的号段信息 private ConcurrentHashMapString, SegmentBuffer cache new ConcurrentHashMap(); /** * 获取下一个ID */ public long getNextId(String bizTag) { SegmentBuffer buffer cache.get(bizTag); if (buffer null) { synchronized (this) { buffer cache.get(bizTag); if (buffer null) { buffer new SegmentBuffer(bizTag); cache.put(bizTag, buffer); // 首次加载号段 loadNextSegment(buffer); } } } // 从当前号段获取ID long id buffer.currentValue.incrementAndGet(); if (id buffer.max) { // 当前号段用完异步加载下一个号段 synchronized (buffer) { // 双重检查防止重复加载 if (buffer.currentValue.get() buffer.max) { loadNextSegment(buffer); } id buffer.currentValue.incrementAndGet(); } } return id; } /** * 从数据库加载下一个号段到内存 */ private void loadNextSegment(SegmentBuffer buffer) { boolean success false; int retryTimes 3; // 乐观锁更新失败重试 while (!success retryTimes-- 0) { IdGenerator idGenerator idGeneratorRepository.findByBizTag(buffer.bizTag); if (idGenerator null) { throw new RuntimeException(Biz tag not found: buffer.bizTag); } int updated idGeneratorRepository.updateMaxId(idGenerator.getBizTag(), idGenerator.getVersion()); if (updated 0) { // 更新成功 long newMaxId idGenerator.getMaxId() idGenerator.getStep(); buffer.currentValue.set(idGenerator.getMaxId()); // 设置当前值为旧的最大值 buffer.max newMaxId - 1; // 新号段的最大值 log.info(Loaded new segment for bizTag: {}, range: [{}, {}], buffer.bizTag, buffer.currentValue.get(), buffer.max); success true; } else { // 乐观锁更新失败重试 log.warn(Failed to update segment for bizTag: {}, retrying..., buffer.bizTag); try { Thread.sleep(100); // 短暂等待后重试 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } if (!success) { throw new RuntimeException(Failed to load segment after retries for bizTag: buffer.bizTag); } } /** * 内部类用于存储一个业务号段的当前状态 */ private static class SegmentBuffer { String bizTag; AtomicLong currentValue new AtomicLong(0); volatile long max 0; // 当前号段的最大值 SegmentBuffer(String bizTag) { this.bizTag bizTag; } } // 应用启动时预加载可选 PostConstruct public void init() { // 可以在这里预加载常用业务的号段避免第一次请求的延迟 // loadNextSegment(new SegmentBuffer(order)); } }Controller// 文件路径src/main/java/com/example/idemo/controller/SegmentController.java package com.example.idemo.controller; import com.example.idemo.service.SegmentService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; RestController public class SegmentController { Autowired private SegmentService segmentService; GetMapping(/id/segment/{bizTag}) public String getSegmentId(PathVariable String bizTag) { long id segmentService.getNextId(bizTag); return bizTag _ id; } }访问http://localhost:8080/id/segment/order即可获得类似order_1001的ID。5.3 方案优缺点与优化优点性能优异ID在内存中生成性能接近Snowflake。数据库压力小仅在校准号段时访问。可扩展通过增加step号段长度可以轻松应对流量增长。数据库可以水平扩展通过不同的biz_tag区分业务。趋势递增ID是连续递增的对数据库索引非常友好。高可用即使数据库短暂不可用服务也能依靠内存中剩余的号段继续工作一段时间。缺点ID不连续由于是号段式发放如果服务重启当前号段未用完的部分会丢失导致ID出现空洞。可以通过将当前号段持久化到本地文件或Redis来缓解。复杂度提升需要自己管理号段加载、并发控制乐观锁和故障恢复。业务侵入需要为不同业务定义不同的biz_tag。优化方向双Buffer优化类似美团Leaf的方案预加载下一个号段实现无缝切换消除获取号段时的毛刺。号段持久化服务关闭时将当前号段进度持久化启动时恢复减少ID空洞。监控告警监控号段使用率在达到阈值前提前异步加载下一个号段。6. 常见问题与排查思路在实际使用中你可能会遇到以下问题问题现象可能原因排查思路与解决方案Snowflake生成ID重复1. 时钟回拨。2. 多台机器配置了相同的workerId。1. 检查服务器时钟同步NTP配置确保时钟稳定。在代码中增强时钟回拨处理逻辑如短暂等待或报警。2. 检查机器ID分配系统确保全局唯一。号段模式服务重启后ID不连续出现空洞服务重启时内存中未使用完的号段丢失。1. 实现号段持久化在关闭时将(currentValue, max)保存到本地文件或Redis启动时读取。2. 将step设置得小一些减少单次丢失的ID数量。数据库号段表更新失败乐观锁冲突多个实例同时请求更新同一个biz_tag的号段。1. 增加重试机制如上文代码所示。2. 增加step大小减少更新频率。3. 为每个实例配置不同的biz_tag前缀如order_01,order_02在应用层聚合。生成的ID长度超出数据库字段范围数据库字段类型如int范围小于ID生成器的范围如Snowflake的64位。1. 将数据库表主键或业务ID字段类型改为BIGINT。2. 如果使用字符串存储确保长度足够。高并发下ID生成服务成为瓶颈1. 数据库自增数据库写压力大。2. Snowflake时钟或序列号竞争。1. 放弃数据库自增改用Snowflake或号段模式。2. 对于Snowflake检查sequence部分是否在单机单毫秒内耗尽超过4096如果是可以考虑减少机器ID位数增加序列号位数或使用更高精度的时间戳。7. 最佳实践与工程建议选择哪种方案取决于你的具体业务场景、团队技术栈和运维能力。以下是一些通用的最佳实践评估业务需求是第一要务QPS要求低并发1000/s可考虑数据库自增或号段高并发必须使用Snowflake或Leaf。是否分库分表如果分库分表必须使用分布式ID生成方案。ID是否需绝对有序金融交易等场景可能需要严格有序此时号段模式优于Snowflake。ID长度与存储考虑数据库索引效率和传输开销64位整型通常是好选择。生产环境部署要点Snowflake务必确保机器时钟同步并部署NTP服务。建立完善的机器ID分配和管理体系如使用ZooKeeper持久顺序节点。号段模式数据库需要高可用架构主从、集群。号段长度step需要根据业务增长量合理设置并设置监控告警在号段使用率达到一定阈值如80%时触发异步加载下一个号段。服务化对于中大型系统建议将ID生成器独立成一个微服务RPC或HTTP统一管理方便监控、扩容和升级。监控与告警监控ID生成服务的QPS、耗时、错误率。监控Snowflake的时钟偏移情况。监控号段模式中各个biz_tag的号段使用率。设置关键错误告警如时钟回拨、数据库连接失败、号段耗尽等。安全与数据保护避免使用连续且可猜测的ID如纯自增暴露给前端可以考虑在内部自增ID基础上通过可逆的加密算法如Hashids生成对外ID。在Snowflake算法中如果担心泄露生成时间和机器信息可以对生成的ID进行一层简单的混淆注意不要影响趋势递增性。测试与验证单元测试必须覆盖时钟回拨、并发获取、服务重启等边界情况。进行压测验证ID生成服务在预期峰值流量下的稳定性。验证生成ID的全局唯一性可以通过分布式测试将生成的ID集中校验。通过本文的梳理相信你对分布式唯一ID生成的常见方案有了系统的认识。从简单的数据库自增到经典的Snowflake再到兼顾性能与扩展性的数据库号段模式每种方案都有其适用场景。在实际项目中你需要像挑选工具一样仔细评估业务的技术约束和非功能需求选择最合适的那一个甚至组合使用。例如可以同时部署Snowflake和号段服务根据业务类型路由。技术没有银弹理解原理灵活运用才能构建出健壮的系统。
返回列表