当前位置: 首页 > news >正文

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

分布式 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)
<!-- 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)
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)
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)

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 生成” 从理论到落地的无缝衔接。

http://www.hskmm.com/?act=detail&tid=18664

相关文章:

  • 队列+宽搜(BFS)-662.二叉树最大宽度-力扣(LeetCode) - 指南
  • JWT攻防实战:混淆、破解与红队利用技术详解
  • “中国英伟达”投资人,赚翻了
  • The 3rd UCUP Stage 29: Metropolis(QOJ contest 1913) 总结
  • 空白金兰契的多维解构与实践路径:从价值表征困境到人机共生伦理
  • 2025中国制造企业500强榜单发布
  • 读 WPF 源代码 了解获取 GlyphTypeface 的 CharacterToGlyphMap 的数量耗时原因
  • 张江,首个万亿市值巨头诞生!
  • Java 与智慧交通:车联网与自动驾驶支持
  • 9月26号
  • 初衷的澄明:空白金兰契的深意
  • Aidoku - 专为iOS/iPadOS打造的免费开源漫画阅读器
  • windos的hyper-v安装的宝塔面板,在面板里面点击重启服务器后再也无法启动面板。
  • Obsidia Git同步方法(偏安卓)
  • 什么是 FullGC
  • Unity渲染时的排序规则
  • AI智慧的三重跃升:从「数理魔兽」到「悬荡悟空」的文明协作者
  • 新学期每日总结(第 5天)
  • codeforces round 1054(e.f)
  • 【SimpleFOC-小项目】驱动电机正转3周
  • 联合体union的基本用法
  • 弱结构光三维扫描重建
  • 9.27 git与pycharm
  • PCA降维
  • docker复制文件到宿主机
  • 【SimpleFOC】SimpleFOC的运动规划器(Motion Planner)和梯形速度规划
  • Day22多态详解
  • rad/s RPM之间的换算
  • 再见Playwright!谷歌官方Chrome DevTools MCP正式发布,AI编程效率再翻倍
  • Markdown 之——清单の语法