生成全局唯一的 `long` 类型 ID

发布时间:2026/11/6 3:29:07

生成全局唯一的 `long` 类型 ID 在分布式集群多台服务器环境下生成 全局唯一 的长整型ID当作订单ID、优惠券ID、业务主键替代数据库自增主键。基于 Redis 实现分布式 ID 生成器类似雪花算法思路时间戳(高位) 序列号(低位)解决集群环境下 ID 重复问题。组成结构long类型一共64位高 32 位时间戳从自定义起始时间到现在的秒数低 32 位Redis自增序列号同一天内从 1 开始递增优点不需要维护数据库Redis自增天然保证分布式唯一按日期做 key方便过期清理 Redis 数据。packagecom.hmdp.utils;importorg.springframework.data.redis.core.StringRedisTemplate;importorg.springframework.stereotype.Component;importjava.time.LocalDateTime;importjava.time.ZoneOffset;importjava.time.format.DateTimeFormatter;importjava.util.concurrent.CountDownLatch;importjava.util.concurrent.TimeUnit;ComponentpublicclassRedisIdWorker{// 开始时间戳privatestaticfinallongBEGIN_TIMESTAMP1640995200L;// 序列号的位数privatestaticfinalintCOUNT_BITS32;privateStringRedisTemplatestringRedisTemplate;publicRedisIdWorker(StringRedisTemplatestringRedisTemplate){this.stringRedisTemplatestringRedisTemplate;}publiclongnextId(StringkeyPrefix){// 1.生成时间戳/* LocalDateTime本身不带时区, 底层会读取JVM默认时区, 即操作系统时区。 如果服务器时区设置为 Asia/Shanghai北京时间 UTC8拿到的数字就是北京时间 如果服务器是 UTC 时区拿到的就是 0 时区时间数字就差 8 小时。*/LocalDateTimenowLocalDateTime.now();// 2026-09-16T11:31:43.510longnowSecondnow.toEpochSecond(ZoneOffset.UTC);// 1789558303longtimestampnowSecond-BEGIN_TIMESTAMP;// 1789558303 - 1640995200L 148563103// 2.生成序列号// 2.1.获取当前日期精确到天输出样式2022:01:02Stringdatenow.format(DateTimeFormatter.ofPattern(yyyy:MM:dd));// date 2026:09:16Stringkeyicr:keyPrefix:date;/* 2.2.自增长 opsForValue().increment()Redis的INCR命令key不存在的时候自动初始化为 0然后 1返回新值。 第一次执行这个 key 的 increment返回的 count 1第二次 count2第三次 count3以此类推。 Redis 的 INCR 命令规则 如果 key 不存在先把 key 的值初始化为 0然后执行 1返回新值 1。 如果 key 已存在直接对原有值 1返回增加之后的值。 例子 keyPrefixorderdate2022:01:02 拼接出来 Redis keyicr:order:2022:01:02 程序第一次调用 nextId(order)Redis 里没有 icr:order:2022:01:02 increment0 1 → 返回 1count1 同一秒 / 同一天再次调用 nextId(order)key 已经存在值是 1 increment1 1 → 返回 2count2 第三次调用 → count3 */// increment 为什么不会并发重复 Redis是单线程执行命令INCR是原子命令多请求排队执行。longcountstringRedisTemplate.opsForValue().increment(key);// count 1代表本次是第一次创建这个 key给 key 设置过期时间if(count1){stringRedisTemplate.expire(key,3,TimeUnit.DAYS);}// 3.拼接并返回returntimestampCOUNT_BITS|count;}}nextId()返回的是一个Java long 类型数字64位长整型不是字符串。公式ID (timestamp 32) | count举个实际例子帮你看懂假设timestamp 148563103距离基准时间的秒差本次Redis自增得到count 5timestamp 32把 148563103 左移32位放到long高32位低32位全部变成0| 5低32位填上序列号5计算最终得到ID638073668777279493这就是方法返回的值直接是数字可以存入 MySQL 的BIGINT字段作为主键。二进制视角64位long【高32位timestamp】 【低32位count序列号】 148563103的二进制 00000000 00000000 00000000 00000101多次调用看看返回值变化以同一天、同一业务前缀order举例第1次调用 → count1 → ID (148563103 32) | 1第2次调用 → count2 → ID (148563103 32) | 2同一秒第100次 → count100 → ID (148563103 32) | 100同一秒生成的ID高位完全一样只有低位数字递增过了1秒timestamp变大整个ID会跳到一个更大的数字所以ID大体递增数据库索引友好。不同天的情况到第二天Redis key 变成icr:order:2026:09:17count重新从1开始同时timestamp也变大生成的ID整体一定比昨天所有ID更大。⚠️ 重点注意两个特性返回值是数字不是拼接字符串很多新手误以为是时间戳 序列号拼接成字符串这个是位运算合并成一个64位数字和雪花算法一样。当timestamp过大时long最高位符号位被置1返回值变成负数Java long是有符号。64位第一位是符号位一旦 timestamp 超过 31 位timestamp 32就会触发负数存入数据库BIGINT也会是负数。这就是前面提到的经典坑。测试代码直接打印看效果// 注入RedisIdWorkerRedisIdWorkeridWorker;TestvoidtestId(){longid1idWorker.nextId(order);longid2idWorker.nextId(order);System.out.println(id1 id1);System.out.println(id2 id2);}输出类似id1 638073668777279493 id2 638073668777279494数据库存储MySQL 字段类型要使用BIGINT对应Java long不能用INTINT只有32位存不下。
延伸阅读

更多相关文章

2026/11/6 3:29:07

mybatis-plus - 乐观锁的使用

mybatis-plus - 乐观锁的使用 添加版本号更新插件 Bean public MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return inte…

2026/11/6 3:29:07

WSL2 AI 环境搭建实录:Docker 与 Miniconda 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/6 4:34:17

单链表实现与核心操作详解

1. 单链表基础概念解析单链表作为数据结构中最基础也最重要的线性结构之一,是每个程序员必须掌握的硬核技能。记得我初学链表时,曾被指针的指针绕得头晕目眩,直到亲手实现了一遍所有操作才真正理解其精髓。本文将用8000字的篇幅,带…

2026/11/6 4:34:17

测试工程师如何识别和预防需求缺陷

1. 需求战场的隐形地雷:测试工程师的排雷手册在软件开发的战场上,需求缺陷就像埋藏在前进道路上的地雷,随时可能引爆项目危机。作为测试工程师,我们不仅是最后的防线,更应该成为冲锋在前的排雷兵。根据行业数据&#x…

2026/11/6 4:34:17

云原生时代运维转型:从手动配置到DevBox自动化

1. 运维角色的历史演变与技术冲击2000年初期的运维工作场景与今天截然不同。那时的运维工程师需要亲自上架服务器、配置交换机、安装操作系统,工作内容高度依赖物理设备操作。我曾见过一位老运维的笔记本,上面密密麻麻记录着不同机房的设备位置、IP分配和…

2026/11/6 4:34:17

JS金额补零到两位小数:toFixed浮点坑与整数分方案

做前端这些年,被问得最多的一类“小问题”就是金额为什么少了个 0。JS 金额补充到 2 位小数、末尾自动补零、整数后面补上“.00”——单看每一条都简单得不像个问题,真写进项目里,边界情况能把人折腾一下午。前阵子帮朋友看一个记账小工具&am…

2026/11/6 4:34:16

动态规划解决子序列问题:从LIS到最大子序和

1. 动态规划在子序列问题中的应用概述动态规划(Dynamic Programming)是解决子序列问题的利器,尤其擅长处理那些需要全局最优解的序列匹配问题。在算法领域,子序列问题通常分为两大类:连续子序列和非连续子序列。连续子…

2026/11/6 4:29:16

数据库连接操作详解:从内连接到外连接

1. 数据库连接操作的本质理解在数据库查询中,连接(Join)操作就像是在两个数据表之间搭建桥梁。想象你手上有两份Excel表格:一份记录着公司所有员工的基本信息,另一份存储着各部门的绩效数据。当我们需要分析"每个…

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/6 1:03:39

MCU晶振电路设计全解析:从负载电容计算到PCB布局调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑