利用 Redis 实现高并发全局 ID 生成器,核心在于利用 Redis 单线程模型和原子操作命令(如 INCR/INCRBY)来保证序列号的唯一性和递增性。实用高效的方法主要包括:纯自增模式、时间戳 + 自增序列号模式、以及按天分割 Key 的模式。其中时间戳 + 自增方案最为推荐,它将 ID 分为时间戳部分和序列号部分,既保证了全局唯一和趋势递增,有利于数据库索引性能,又通过 Redis 集群保证了高可用和高性能,同时避免了数据库自增主键的单点瓶颈和 UUID 的无序性问题。
简单实用!利用 Redis 轻松实现高并发全局 ID 生成器
1、全局递增 ID 目标:一直递增的全局 ID。 /** * 一直递增的全局 id * * @param redisTemplate redis 客户端对象 * @param busId 业务 id,可以按需配置 * @param step 步长,即每次递增的间隔 */ publicstaticStringgetNo(RedisTemplate
Redis 分布式全局唯一 ID 生成方案:时间戳 + Redis 自增
一、为什么我们需要自己生成 ID? 在业务系统里,很多对象都需要唯一标识,例如:订单 ID 支付单号 优惠券领取记录 ID 秒杀订单 ID 用户业务编号 最容易想到的做法是数据库自增主键,但在分布式场景下,它往往不够理想。1. 数据库自增 ID 的问题 数据库自增虽然简单,但有几个明显缺点:依赖单库,扩展性一般 高并发下容易成为瓶颈 不适合多服务、多节点同时生成 业务上有时不希望直接暴露连续主键 2.UUID 的问题 UUID 也能保证唯一,但它也有不足:字符串过长,不利于存储和索引 无序,数据库索引性能不够友好 可读性较差 因此,很多系统会选择一种折中方案:生成一个 long 型、全局唯一、趋势递增、适合分布式场景的业务 ID。二、这套方案的核心思路 这套方案的核心思想很简单:ID = 时间戳部分 + 序列号部分 其中:时间戳部分:用于体现大致时间顺序 序列号部分:用于保证同一时刻生成多个 ID 时仍不重复 而序列号不是在本地内存里递增,而是交给 Redis 来完成,因为 Redis 的 INCR 操作具有原子性,非常适合在分布式环境下生成全局递增序号。三、示例代码 下面是一段典型实现:publiclongnextId(StringkeyPrefix){// 1. 生成时间戳 LocalDateTimenow=LocalDateTime.now();longnowSecond=now.toEpochSecond(ZoneOffset.UTC);longtimestamp=nowSecond-BEGIN_TIMESTAMP;// 2. 生成序列号 Stringdate=now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));longcount=stringRedisTemplate.opsForValue().increment("icr:"+keyPrefix+":"+date);// 3. 拼接并返回 returntimestamp< 2. Redis 实现全局唯一 Id RedisIdWorker 全局唯一 ID 生成器类:@Component publicclassRedisIdWorker{ //开始时间戳 2025 年 1 月 1 日 0 分 0 秒 privatestaticfinallongBEGIN_TIMESTAMP=1735689600L; //序列号的位数 privatestaticfinalintCOUNT_BITS=32; @Autowired privateStringRedisTemplate stringRedisTemplate; publiclongnextId(String prefix){ //1.生成时间戳 LocalDateTimenow=LocalDateTime.now(); longnewSecond=now.toEpochSecond(ZoneOffset.UTC); longtimeStamp=newSecond - BEGIN_TIMESTAMP; //2.生成序列号 Redis 的自增操作 //Redis 单个实例能够支持的 Key 数量最多可达 2^32 个,所以 key 必须设置有上限的,// 所以我们 key 可以增加具体的哪一天日期进行区分//2.1 获取当前的日期 Stringdate=now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd")); //2.2 自增长 longcount=stringRedisTemplate.opsForValue().increment("incr:"+ prefix +":"+ date); //3.拼接返回 returntimeStamp << COUNT_BITS | count; } } AI 写代码 java 运行 COUNT_BITS = 32,表示序列号占用的位数,timeStamp << 32 将时间戳左移 32 位,为序列号腾出空间,将序列号 count 放在 ID 的低位部分,由于时间戳左移后低位都是 0,按位或操作不会影响时间戳部分 (位移操作和异或运算,根据当前日期进行生成)(消息于 2025 年 11 月 17 日发布) Redis 自增::第 1 位是符号位,始终为 0;接下来的 31 位是时间戳,记录了 ID 生成的时间;最后的 32 位是序列号,生成 64 位的二进制最终形成 long 类型数据 snowflake(雪花算法):第 1 位是符号位,始终为 0;接下来的 41 位是时间戳,记录了 ID 生成的时间;然后的 10 位是工作进程 ID,用于区分不同的服务器或进程;最后的 12 位是序列号,用于在同一毫秒内生成不同的 ID,生成 64 位的二进制最终形成 long 类型数据 数据库自增:单独使用一张表来存生成的 id 值,其他要使用 id 的表就来查询即可 1.5.具体实现 (Redis 自增方案): 为什么可以实现:唯一:由于 Redis 是独立于数据库之外的 (不管有几张表或者是有几个数据库),我们的 Redis 始终是只有一个 (唯一),因此它的自增的 id 就永远唯一 高可用:利用集群,哨兵,主从方案 高性能:Redis 基于内存,数据库基于硬盘,因此性能更好 递增:Redis 自带命令可以实现自增 安全性:不会直接使用 Redis 的自增数值 (依旧是规律性太明显),采用拼接信息实现 怎么实现:我们采用拼接信息实现,而为了增加性能,我们采用数值类型 (long 类型),它占用空间小,对建立索引方便 实现步骤:拼接信息,第 1 位是符号位,始终为 0(0 位正,1 为负);接下来的 31 位是时间戳 (秒数),记录了 ID 生成的时间;最后的 32 位是序列号 (Redis 自增数),生成 64 位的二进制最终形成 long 类型数据(发布时间是 2025 年 10 月 11 日) Redis 生成 ID 如何保证唯一性? 利用 Redis 单线程特性及 INCR 命令的原子性,确保并发下读写同一 key 不会出现不同数据。 如何防止 Redis 重启导致 ID 重复? 需要开启 Redis AOF 持久化,或者使用异步机制将生成的最大 ID 持久化到 MySQL 中。 时间戳 + 自增方案相比纯自增有何优势? 趋势递增有利于数据库索引性能,且 ID 中包含时间信息,便于排查问题,同时避免单点瓶颈。你一定不能错过的高并发场景下的 redis 解决方案 (优惠券下单秒杀 库存超卖 一人一单 集群下的线程安全)
【Redis | 实战篇 秒杀实现】
FAQ