当前位置:首页 > 问答 > 正文

用Redis怎么快速又稳地拿订单号,顺带聊聊实现细节和注意点

用Redis拿订单号,核心就是要利用Redis单线程原子性和高性能的特点,确保每个订单号都是唯一的,并且生成速度要快,下面聊聊几种常见的做法和里面的门道。

最直接的方法:用INCR命令

这是最简单、最常用的一种方式,原理就是利用Redis的INCR命令,这个命令会把一个键的值增加1,并且这个操作是原子的,也就是说即使有成千上万个请求同时来执行这个命令,Redis也会让它们排好队,一个一个执行,绝对不会出现两个请求拿到同一个号码的情况。

  • 实现细节

    用Redis怎么快速又稳地拿订单号,顺带聊聊实现细节和注意点

    1. 你需要在Redis里设置一个键,比如叫 order_id_seed,可以给它设置一个初始值,比如当天的日期 20240520000000,表示从今天开始的第一单。
    2. 每次要生成新订单号时,应用程序就向Redis发送一个 INCR order_id_seed 命令。
    3. Redis会返回一个比上一次大1的数字,这个数字就是你的订单号。
  • 订单号组成:通常订单号不会只是一个纯数字,会包含日期、业务前缀等,你可以这样做:

    • 订单号 = 业务前缀(如"ORD") + 年月日(如"20240520") + Redis INCR生成的6位或更多位自增序列
    • ORD20240520000001ORD20240520000002
    • 这里有个关键点:如何实现按天重置? 你不能让这个序列号无限增长下去,通常需要每天清零,聪明的做法是,把Redis的键名也带上日期,order_id_seed:20240520,这样,第二天键名就变成了 order_id_seed:20240521INCR命令自然会从1重新开始,这种方式非常优雅,不需要额外的清理操作。
  • 注意点

    • Redis持久化:这是最重要的!如果你只是把数据存在Redis内存里,一旦Redis服务器重启或者崩溃,内存里的order_id_seed值就丢了,重启后可能会生成重复的订单号,所以必须开启Redis的持久化机制(RDB快照或AOF日志),这样即使重启,也能从磁盘上恢复最新的序列值,为了更保险,甚至可以同时使用RDB和AOF。
    • 键名管理:使用带日期的键名,久而久之会产生很多键(order_id_seed:20240520, order_id_seed:20240521...),这些键在过期后需要清理,可以给这些键设置一个过期时间,比如7天或30天后自动删除,避免占用太多内存。

应对更高并发:用INCRBY命令

用Redis怎么快速又稳地拿订单号,顺带聊聊实现细节和注意点

如果业务量非常大,觉得每次INCR增加1,网络通信次数太多成为瓶颈,可以考虑使用INCRBY命令。

  • 实现细节

    1. 应用程序不是每次来要一个号,而是一次性向Redis申请一个号段,比如1000个,命令是 INCRBY order_id_seed 1000
    2. Redis返回一个值,比如是1000,这意味着当前应用程序本地持有了1到1000这1000个订单号的使用权。
    3. 应用程序在本地内存中维护这个号段,从1开始分配,直到1000分配完毕,再去Redis申请下一个1000个号段。
  • 优势

    用Redis怎么快速又稳地拿订单号,顺带聊聊实现细节和注意点

    • 极大地减少了与Redis的交互次数,性能更高。
    • 即使Redis临时不可用,应用程序在本地号段用完之前还能继续工作一段时间,增强了系统的稳定性。
  • 注意点

    • 号段丢失问题:如果应用程序在本地分配了500个号之后突然崩溃重启,那么本地内存中还没分配的501-1000这个号段就浪费了,造成了“订单号空洞”,对于很多业务来说,订单号可以不连续,但绝对不能重复,所以这点空洞是可以接受的,属于用空间换时间和稳定性。
    • 号段大小设置:需要根据业务量合理设置号段大小,设得太小,频繁请求Redis,压力大;设得太大,万一应用重启,浪费的号段就多。

聊点其他的实现和注意点

  • 为什么不直接用UUID或雪花算法?

    • UUID虽然能保证唯一,但太长、无序,作为数据库主键插入时会影响性能(因为B+树需要频繁调整)。
    • 雪花算法(Snowflake)能生成趋势递增的ID,性能极高,不依赖数据库,但它通常要求解决机器ID的分配问题(防止不同机器生成重复ID),并且有时钟回拨的风险,对于简单的订单号场景,Redis方案更简单直接,依赖的中间件少。
  • Redis集群模式下的考虑: 如果你的Redis是集群模式,上面提到的INCR键可能会被哈希到某个特定的节点上,这意味着所有订单号生成的请求都会打到同一个Redis节点上,虽然Redis单节点性能很强,但这可能成为整个集群的瓶颈,这时可以考虑:

    1. 容忍方案:评估单个Redis节点的性能是否足以支撑业务峰值,如果可以,就接受这个热点key。
    2. 分散方案:不用一个key,而是用多个key,可以根据用户ID的最后一位或者商户ID哈希到不同的key上,如 order_id_seed:0, order_id_seed:1... 这样就把压力分散到集群的不同节点上了,但这样做的代价是订单号不再是全局严格递增的,只是趋势递增,并且管理起来稍复杂。

总结一下

对于大多数场景,使用带日期的键名进行INCR操作,并确保Redis开启了持久化,就是一个快速又稳的方案,如果并发量极高,可以考虑用INCRBY进行批量化获取来提升性能和可用性,选择哪种方案,最终要根据你的实际业务量、技术架构和对订单号连续性的要求来权衡,核心就是抓住“原子性”和“持久化”这两个牛鼻子。