分布式 ID 生成方案实战指南:从选型到落地的全场景避坑手册(三)

五、方案 4:Redis 自增 ID------ 极高并发的轻量选择

5.1 问题场景:直播平台礼物 ID 生成

某直播平台,峰值 QPS 达 10 万(用户送礼物),需生成全局唯一、高性能的礼物 ID,无需强有序(仅需唯一标识),且避免依赖数据库 / ZooKeeper。

5.2 方案原理:利用 Redis 的 INCR 命令

Redis 的INCR key命令是原子操作,支持每秒万级 / 十万级自增,核心思路:

  1. 用 Redis 的INCR生成自增 ID(如INCR gift:id);

  2. 为避免 Redis 单点故障,部署 Redis 集群(主从 + 哨兵);

  3. 可选:添加业务前缀(如礼物类型 + 日期),增强 ID 语义(如gift_1_20240520_123456)。

5.3 实战代码:Redis 自增 ID 实现(Spring Boot)

5.3.1 1. 依赖配置(pom.xml)
xml 复制代码
<!-- Spring Data Redis -->

<dependency>

   <groupId>org.springframework.boot\</groupId>

   <artifactId>spring-boot-starter-data-redis\</artifactId>

</dependency>

<!-- Redis客户端(Lettuce,支持集群) -->

<dependency>

   <groupId>io.lettuce\</groupId>

   <artifactId>lettuce-core\</artifactId>

</dependency>
5.3.2 2. Redis 配置(application.yml)
yml 复制代码
spring:

 redis:

   # Redis集群地址(主从+哨兵)

   sentinel:

     master: mymaster # 主节点名称

     nodes: 192.168.1.100:26379,192.168.1.101:26379,192.168.1.102:26379

   # 连接池配置

   lettuce:

     pool:

       max-active: 16 # 最大连接数

       max-idle: 8 # 最大空闲连接数

       min-idle: 4 # 最小空闲连接数

   # 密码(生产环境必填)

   password: Redis@123456

   # 超时时间

   timeout: 2000ms
5.3.3 3. Redis 自增 ID 服务实现(Java)
java 复制代码
import org.springframework.data.redis.core.RedisTemplate;

import org.springframework.data.redis.core.ValueOperations;

import org.springframework.stereotype.Service;

import javax.annotation.Resource;

import java.text.SimpleDateFormat;

import java.util.Date;

import java.util.concurrent.TimeUnit;

@Service

public class GiftIdGenerator {

   // Redis key前缀(区分不同业务)

   private static final String REDIS\_KEY\_PREFIX = "gift:id:";

   // 时间格式(用于ID前缀,如20240520)

   private static final SimpleDateFormat DATE\_FORMAT = new SimpleDateFormat("yyyyMMdd");

   // 本地缓存ID的过期时间(5分钟,避免频繁访问Redis)

   private static final long CACHE\_EXPIRE\_MINUTES = 5;

   @Resource

   private RedisTemplate\<String, Long> redisTemplate;

   // 生成礼物ID(格式:gift\_礼物类型\_日期\_自增ID,如gift\_1\_20240520\_123456)

   public String generateGiftId(int giftType) {

       // 1. 构建Redis key(按日期分片,避免单key自增过大)

       String date = DATE\_FORMAT.format(new Date());

       String redisKey = REDIS\_KEY\_PREFIX + date;

       // 2. 执行Redis INCR命令(原子自增)

       ValueOperations\<String, Long> valueOps = redisTemplate.opsForValue();

       Long incrId = valueOps.increment(redisKey);

       // 3. 设置Redis key过期时间(按天过期,避免key堆积)

       if (incrId != null && incrId == 1) { // 首次生成时设置过期时间(2天)

           redisTemplate.expire(redisKey, 2, TimeUnit.DAYS);

       }

       // 4. 构建带业务前缀的ID(增强语义)

       return String.format("gift\_%d\_%s\_%d", giftType, date, incrId);

   }

   // 优化:本地缓存ID(减少Redis访问,适合极高并发)

   private volatile Long localCacheId;

   private volatile long cacheExpireTime;

   public String generateGiftIdWithLocalCache(int giftType) {

       long currentTime = System.currentTimeMillis();

       // 1. 检查本地缓存是否过期

       if (localCacheId == null || currentTime > cacheExpireTime) {

           // 2. 从Redis获取自增ID(一次获取100个,批量缓存)

           String date = DATE\_FORMAT.format(new Date());

           String redisKey = REDIS\_KEY\_PREFIX + date;

           Long batchId = redisTemplate.opsForValue().increment(redisKey, 100); // 一次增100

           if (batchId == null) {

               throw new RuntimeException("Redis自增ID获取失败");

           }

           // 3. 更新本地缓存(起始ID=batchId-100,过期时间=当前+5分钟)

           localCacheId = batchId - 100;

           cacheExpireTime = currentTime + CACHE\_EXPIRE\_MINUTES \* 60 \* 1000;

       }

       // 4. 本地缓存ID递增

       long currentId = localCacheId++;

       // 5. 构建最终ID

       String date = DATE\_FORMAT.format(new Date());

       return String.format("gift\_%d\_%s\_%d", giftType, date, currentId);

   }

   // 测试:生成10个礼物ID

   public static void main(String\[] args) {

       // 模拟Spring注入RedisTemplate(实际项目中由Spring管理)

       GiftIdGenerator generator = new GiftIdGenerator();

       for (int i = 0; i < 10; i++) {

           // 输出示例:gift\_1\_20240520\_123456

           System.out.println(generator.generateGiftId(1));

       }

   }

}
5.3.4 4. Redis 集群一致性保障(RedLock)

若 Redis 集群存在脑裂风险(主节点下线,从节点未同步完数据),可使用 RedLock(红锁)确保 ID 生成一致性:

  1. 部署 3 个独立的 Redis 集群(无主从关系);

  2. 生成 ID 时,向 3 个集群同时执行INCR命令;

  3. 至少 2 个集群执行成功,才认为 ID 生成有效,避免脑裂导致的 ID 重复。

RedLock 实现代码(Java + Redisson)

java 复制代码
import org.redisson.Redisson;

import org.redisson.api.RLock;

import org.redisson.api.RedissonClient;

import org.redisson.config.Config;

import java.util.concurrent.TimeUnit;

public class RedLockGiftIdGenerator {

   // 3个独立Redis集群地址

   private static final String REDIS\_CLUSTER\_1 = "redis://192.168.1.100:6379";

   private static final String REDIS\_CLUSTER\_2 = "redis://192.168.1.101:6379";

   private static final String REDIS\_CLUSTER\_3 = "redis://192.168.1.102:6379";

   // 初始化3个Redisson客户端

   private RedissonClient redisson1 = createRedissonClient(REDIS\_CLUSTER\_1);

   private RedissonClient redisson2 = createRedissonClient(REDIS\_CLUSTER\_2);

   private RedissonClient redisson3 = createRedissonClient(REDIS\_CLUSTER\_3);

   // 创建Redisson客户端

   private RedissonClient createRedissonClient(String address) {

       Config config = new Config();

       config.useSingleServer().setAddress(address).setPassword("Redis@123456");

       return Redisson.create(config);

   }

   // RedLock生成礼物ID

   public String generateGiftIdWithRedLock(int giftType) {

       String date = new SimpleDateFormat("yyyyMMdd").format(new Date());

       String redisKey = "gift:id:" + date;

       long incrId = 0;

       int successCount = 0;

       // 1. 向3个Redis集群执行INCR

       try {

           // 集群1执行

           Long id1 = redisson1.getBucket(redisKey).incrementAndGet();

           if (id1 != null) {

               successCount++;

               incrId = id1;

           }

           // 集群2执行

           Long id2 = redisson2.getBucket(redisKey).incrementAndGet();

           if (id2 != null) {

               successCount++;

               incrId = id2;

           }

           // 集群3执行

           Long id3 = redisson3.getBucket(redisKey).incrementAndGet();

           if (id3 != null) {

               successCount++;

               incrId = id3;

           }

           // 2. 至少2个集群成功,才返回ID

           if (successCount >= 2) {

               return String.format("gift\_%d\_%s\_%d", giftType, date, incrId);

           } else {

               throw new RuntimeException("RedLock ID生成失败:成功集群数=" + successCount);

           }

       } finally {

           // 3. 关闭Redisson客户端(实际项目中无需关闭,全局单例)

           // redisson1.shutdown();

           // redisson2.shutdown();

           // redisson3.shutdown();

       }

   }

}

5.4 故障案例:Redis 宕机导致 ID 生成中断

5.4.1 问题背景

某短视频平台用 Redis 自增生成评论 ID,Redis 主节点宕机后,哨兵未及时切换从节点,导致评论服务无法生成 ID,评论功能中断 5 分钟。

5.4.2 根因分析
  1. Redis 集群仅部署 1 主 1 从,从节点同步延迟(约 1 秒),主节点宕机后,从节点数据未完全同步;

  2. 哨兵切换超时(配置为 5 秒),期间服务无法访问 Redis;

  3. 评论服务无 "降级方案",Redis 不可用时直接抛出异常,导致业务中断。

5.4.3 解决方案
  1. 部署 Redis 集群(3 主 3 从),减少单点故障风险;

  2. 缩短哨兵切换时间(配置down-after-milliseconds=1000failover-timeout=2000);

  3. 服务添加降级方案:Redis 不可用时,临时使用 "本地自增 + 节点 ID" 生成 ID(如comment_123_456,123 = 节点 ID,456 = 本地自增),Redis 恢复后合并数据;

  4. 配置 Redis 持久化(AOF+RDB),避免宕机后数据丢失。

5.5 避坑总结

适用场景:极高并发(10 万 + QPS)、无强有序要求、轻量依赖的场景(礼物 / 评论 / 点赞);

不适用场景:需强有序 ID、Redis 集群维护成本高的场景;

⚠️ 必避坑点

  1. Redis 必须部署集群(主从 + 哨兵),避免单点故障;

  2. 用 "按日期分片" 的 Redis key(如gift:id:20240520),避免单 key 自增过大;

  3. 必须配置持久化(AOF+RDB),防止 Redis 宕机后 ID 重置;

  4. 服务需添加降级方案,Redis 不可用时确保业务不中断。

六、方案对比与选型指南

6.1 四大方案核心指标对比

方案 唯一性 有序性 性能(QPS) 可用性 依赖服务 适用场景
UUID / 优化版 10 万 + 内部系统、低并发、无有序要求
数据库分段 / 号段 1 万 - 10 万 数据库 分库分表、中低并发、强有序
雪花算法 10 万 + ZooKeeper / 配置中心 高并发、强有序、水平扩容
Redis 自增 10 万 + Redis 集群 极高并发、无强有序、轻量依赖

6.2 选型决策树

6.3 实战选型案例

  1. 电商订单系统:强有序、并发 5 万 QPS→雪花算法;

  2. 物流单号系统:强有序、分库分表、并发 1 万 QPS→数据库号段模式;

  3. 直播礼物系统:无强有序、并发 10 万 QPS→Redis 自增;

  4. 内部 CRM 系统:无强有序、并发 1 千 QPS→UUID 优化版;

  5. 支付流水系统:强有序、高可用、并发 8 万 QPS→雪花算法(ZooKeeper+Redis 冗余校验)。

七、总结:分布式 ID 生成的核心原则

  1. 唯一性优先:无论选择哪种方案,全局无重复是底线(可通过冗余校验、机器 ID、时间戳确保);

  2. 性能匹配业务:低并发用简单方案(UUID),高并发用复杂方案(雪花 / Redis),避免过度设计;

  3. 可用性兜底:所有方案需考虑单点故障(如数据库主从、Redis 集群、ZooKeeper 集群),并添加降级方案;

  4. 可扩展预留:机器 ID、号段大小、Redis key 分片等设计需预留扩容空间(如雪花算法支持 1024 个节点);

  5. 故障早发现:添加 ID 生成监控(如 Prometheus 监控 ID 生成耗时、重复率),并配置告警(如生成耗时 > 1ms、重复率 > 0)。

通过本文的 4 大方案拆解与实战案例,你可根据业务场景快速选型,并避开 90% 的落地坑,真正实现 "分布式 ID 生成" 从理论到落地的无缝衔接。

相关推荐
有一个好名字2 小时前
万字 Apache ShardingSphere 完全指南:从分库分表到分布式数据库生态
数据库·分布式·apache
Aurora_eye2 小时前
记录之Ubuntu22.4虚拟机及hadoop为分布式安装
大数据·hadoop·分布式
斯班奇的好朋友阿法法4 小时前
rabbitmq服务端消费端实例(direct和fanout模式)
分布式·rabbitmq·ruby
斯班奇的好朋友阿法法4 小时前
rabbitmq的多交换机(扇出为例)监听实现
分布式·rabbitmq·ruby
没有bug.的程序员4 小时前
分布式架构未来趋势:从云原生到智能边缘的演进之路
java·分布式·微服务·云原生·架构·分布式系统
武话不港14 小时前
RabbitMQ异常,handleDelivery方法异常
分布式·消息队列·rabbitmq
^Moon^9 小时前
CycloneDDS:跨主机多进程通信全解析
c++·分布式·dds
长安城没有风13 小时前
从入门到精通【Redis】Redis 典型应⽤ --- 分布式锁
数据库·redis·分布式
爬山算法17 小时前
Redis(69)Redis分布式锁的优点和缺点是什么?
数据库·redis·分布式
virtuousOne1 天前
Kafka基础
分布式·kafka