基于Redis实现的分布式唯一编号生成工具类

 更新时间:2025年11月20日 09:39:28   作者:她说..  
这篇文章主要介绍了基于Redis实现的分布式唯一编号生成工具类,核心功能是生成格式为 业务编码+日期+3位自增序号(如 JJ20250826001)的全局唯一编号,适用于分布式系统中需要有序、不重复编号的场景(如订单号、单据号等),以下是详细解析,需要的朋友可以参考下

首先,直接上代码:

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import com.baomidou.mybatisplus.core.toolkit.DateUtils;
import com.xxxxx.blade.redis.BladeRedis; // 根据实际包路径调整
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import com.xxxxx.common.exception.ServiceException; // 根据实际包路径调整

/**
 * 通用编号生成工具类
 * 功能:生成格式为【业务编码+日期+3位自增序号】的唯一编号(例如:JJ20250826001)
 * 特性:基于Redis实现分布式自增,通过Redisson分布式锁保证并发安全,序号按日期重置
 */
@Slf4j // Lombok注解,自动注入日志对象log
@Component // Spring组件注解,将该类注册为Bean,交由Spring容器管理
public class CommonNumber {

    /**
     * Redis操作客户端(静态变量)
     * 用于执行自增、过期时间设置等Redis命令
     */
    private static BladeRedis BLADE_REDIS;

    /**
     * Redisson客户端(静态变量)
     * 用于获取分布式锁,保证多实例环境下的并发安全
     */
    private static RedissonClient REDISSSON_CLIENT;

    /**
     * Redisson客户端(实例变量)
     * 由Spring容器注入,通过@Resource注解按名称匹配
     */
    @Resource
    private RedissonClient RedissonClient;

    /**
     * Redis操作客户端(实例变量)
     * 由Spring容器注入,Blade框架封装的Redis客户端
     */
    @Resource
    private BladeRedis bladeRedis;

    /**
     * 初始化方法(PostConstruct注解)
     * 作用:在Spring Bean初始化完成后,将实例变量赋值给静态变量
     * 原因:工具方法为static,无法直接注入Spring Bean,通过该方式间接获取容器中的Bean实例
     */
    @PostConstruct
    public void init() {
        BLADE_REDIS = bladeRedis;
        REDISSSON_CLIENT = RedissonClient;
    }

    /**
     * 生成通用唯一编号
     * 格式:业务编码(code) + 年月日(yyyyMMdd) + 3位自增序号(不足补0)
     * 示例:code=JJ → JJ20250826001、JJ20250826002...
     *
     * @param code 业务编码(区分不同业务场景的编号前缀)
     * @return 格式化后的唯一编号
     * @throws ServiceException 当获取分布式锁失败或Redis操作异常时抛出
     */
    public static String getCommonNumber(String code) {
        // 1. 定义分布式锁key:按业务编码区分,避免不同业务锁竞争
        RLock lock = REDISSSON_CLIENT.getLock("common-number-lock:" + code);

        try {
            // 2. 尝试获取分布式锁:最多等待3秒,持有锁5秒(防止死锁)
            // tryLock返回false表示获取锁失败(并发过高)
            if (!lock.tryLock(3, 5, TimeUnit.SECONDS)) {
                throw new ServiceException("系统繁忙,请稍后重试");
            }

            // 3. 格式化当前日期为yyyyMMdd格式(用于序号按日期重置)
            String currentTime = DateUtils.format(new Date(), "yyyyMMdd");

            // 4. 定义Redis自增key:业务编码+日期,确保每天的序号从1开始
            String companyNumberKey = "common_number_key:" + code + currentTime;

            // 5. Redis自增操作:原子性递增,保证序号唯一(初始值为1,每次+1)
            Long incr = BLADE_REDIS.incr(companyNumberKey);

            // 6. 拼接最终编号:业务编码 + 日期 + 3位补0序号(例如:1→001,10→010,100→100)
            String companyNumber = code + currentTime + String.format("%03d", incr);

            // 7. 设置Rediskey过期时间:48小时(确保过期数据自动清理,节省Redis空间)
            BLADE_REDIS.expire(companyNumberKey, 60 * 60 * 48L);

            // 8. 返回生成的编号
            return companyNumber;

        } catch (InterruptedException e) {
            // 捕获线程中断异常(获取锁过程中线程被中断)
            log.error("获取编号时线程被中断,code:{}", code, e);
            throw new ServiceException("获取编号失败");
        } catch (ServiceException e) {
            // 抛出获取锁失败的自定义异常(无需额外日志,已在抛出时明确)
            throw e;
        } catch (Exception e) {
            // 捕获其他异常(Redis操作失败等)
            log.error("获取编号失败,code:{}", code, e);
            throw new ServiceException("获取编号失败");
        } finally {
            // 9. 释放分布式锁:必须在finally中执行,确保锁一定会释放
            // 先判断当前线程是否持有锁,避免释放其他线程的锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

```java

这是一个 基于Redis实现的分布式唯一编号生成工具类,核心功能是生成格式为 业务编码+日期+3位自增序号(如 JJ20250826001)的全局唯一编号,适用于分布式系统中需要有序、不重复编号的场景(如订单号、单据号等)。以下是详细解析:

一、类结构与依赖说明

1. 核心注解

  • @Slf4j:Lombok注解,自动生成日志对象 log,用于打印异常日志。
  • @Component:Spring注解,将该类注册为Spring容器中的Bean,支持依赖注入。

2. 依赖组件

  • BladeRedis:bladex框架封装的Redis操作工具(类似Spring Data Redis),用于执行incr(自增)、expire(设置过期时间)等Redis命令。
  • RedissonClient:Redisson框架的客户端,用于操作Redis分布式锁(解决分布式环境下的并发冲突)。
  • @Resource:Spring依赖注入注解,用于注入RedissonClientBladeRedis实例。
  • @PostConstruct:Spring生命周期注解,在Bean初始化完成后执行init方法,将注入的实例赋值给静态变量(因为getCommonNumber是静态方法,无法直接使用非静态成员变量)。

二、核心逻辑:编号生成流程

1. 方法定义

public static String getCommonNumber(String code)
  • 入参 code:业务编码(如JJ代表某种单据类型),用于区分不同业务场景的编号。
  • 出参:格式为 code + 日期(yyyyMMdd) + 3位自增序号 的唯一编号。

2. 关键步骤(带并发安全保障)

(1)分布式锁获取

RLock lock = REDISSSON_CLIENT.getLock("common-number-lock:"+code);
if (!lock.tryLock(3, 5, TimeUnit.SECONDS)) {
    throw new ServiceException("系统繁忙,请稍后重试");
}
  • 锁key设计common-number-lock: + 业务编码code,确保不同业务的锁相互隔离,避免锁竞争加剧。
  • 锁参数
    • 最多等待3秒(waitTime=3):线程获取锁时,最多等待3秒,超过则认为获取失败。
    • 锁持有时间5秒(leaseTime=5):即使线程未主动释放锁,5秒后Redis也会自动释放,避免死锁。
  • 作用:解决分布式环境下的并发冲突,确保同一业务编码的自增序号不会重复。

(2)Redis自增生成序号

// 1. 生成当天日期(格式:yyyyMMdd)
String currentTime = DateUtil.format(DateUtil.date(), "yyyyMMdd");
// 2. 构建Redis自增key(业务编码+日期,确保每天的序号独立重置)
String companyNumberKey = "common_number_key:" + code + currentTime;
// 3. Redis自增(原子操作,确保序号唯一)
Long incr = BLADE_REDIS.incr(companyNumberKey);
// 4. 序号补零(3位,不足3位前面补0,如1→001,10→010)
String seq = String.format("%03d", incr);
  • Redis key设计common_number_key: + 业务编码code + 日期currentTime,确保:
    • 不同业务编码的序号相互独立;
    • 同一业务编码每天的序号从1开始重置(因为日期变化后key会变化)。
  • 原子性保障:Redis的incr命令是原子操作,即使多个线程同时调用,也能保证自增结果唯一,无需额外加锁(但此处仍加分布式锁是为了避免极端情况下的并发问题,或配合序号补零等逻辑)。

(3)设置Redis key过期时间

BLADE_REDIS.expire(companyNumberKey, 60 * 60 * 48L); // 48小时过期
  • 自增key的过期时间设置为48小时,原因:
    • 确保当天的序号能正常自增(当天内key未过期);
    • 避免Redis中存储大量过期的自增key,节省存储空间;
    • 48小时覆盖跨天场景(如凌晨23:59生成的key,到次日仍可正常使用,避免提前过期)。

(4)组装最终编号

String companyNumber = code + currentTime + seq;
  • 例如:业务编码JJ + 日期20250826 + 序号001 → 最终编号JJ20250826001

3. 异常处理与锁释放

try {
    // 编号生成逻辑...
} catch (Exception e) {
    log.error("获取编号失败", e); // 打印异常堆栈,便于排查
    throw new ServiceException("获取编号失败"); // 抛出自定义业务异常,上层处理
} finally {
    // 确保锁一定释放(避免死锁)
    if (lock.isHeldByCurrentThread()) {
        lock.unlock();
    }
}
  • finally块释放锁:无论生成编号成功与否,都要释放分布式锁,避免锁资源泄露。
  • 锁持有判断lock.isHeldByCurrentThread() 确保当前线程确实持有锁时才释放,避免释放其他线程的锁(如线程等待锁超时后,未持有锁却执行解锁操作)。

三、核心设计亮点

1. 分布式并发安全

  • 双重保障:Redisincr原子操作 + Redisson分布式锁,确保序号唯一,无并发冲突。
  • 锁自动过期:避免因线程异常导致的死锁问题。

2. 序号合理性

  • 按天重置:每天的序号从1开始,编号可读性强(通过编号可直接看出日期)。
  • 固定长度:3位序号补零,确保编号长度一致(便于存储和展示)。

3. 资源优化

  • Redis key过期:48小时自动清理,避免Redis存储冗余数据。
  • 锁粒度细:按业务编码分锁,减少锁竞争,提高并发效率。

四、潜在问题与优化建议

1. 潜在问题

  • 锁竞争风险:如果同一业务编码的并发请求极高,分布式锁可能成为性能瓶颈(线程需等待3秒)。
  • Redis依赖风险:Redis服务不可用时,编号生成会失败(无降级方案)。
  • 序号溢出:3位序号最大支持999,如果单日同一业务编码的编号超过999,会生成code+日期+1000(如JJ202508261000),破坏3位固定长度格式。

2. 优化建议

  • 优化锁策略
    • 去掉分布式锁(Redisincr已保证原子性),仅在需要序号补零、特殊逻辑时加锁,提高并发效率。
    • 调整锁等待时间和持有时间(根据业务并发量动态调整)。
  • 降级方案
    • 当Redis不可用时,可临时使用本地缓存(如AtomicLong)+ 机器标识生成编号,避免服务不可用。
  • 序号扩容
    • 将3位序号改为4位(%04d),支持单日9999个编号,满足更高并发场景。
  • 防止重复生成
    • 可将生成的编号存入Redis或数据库,做最终去重校验(极端情况下Redis自增失败时兜底)。
  • 静态成员变量优化
    • 目前通过@PostConstruct给静态变量赋值,依赖Spring初始化顺序,可改为使用@Autowired + 非静态方法(去掉static),更符合Spring依赖注入规范(需将工具类注入使用,而非直接调用静态方法)。

五、使用场景

适用于分布式系统中需要生成 有序、唯一、可读 编号的场景,例如:

  • 订单编号、支付单号、物流单号;
  • 单据编号(如入库单、出库单);
  • 业务流水号等。

总结

该工具类基于Redis的原子自增和Redisson的分布式锁,实现了分布式环境下的唯一编号生成,设计简洁、实用性强,同时也存在一些可优化的细节(如锁策略、降级方案),可根据实际业务场景调整。

以上就是基于Redis实现的分布式唯一编号生成工具类的详细内容,更多关于Redis分布式唯一编号生成工具类的资料请关注脚本之家其它相关文章!

相关文章

  • redis requires ruby version2.2.2的解决方案

    redis requires ruby version2.2.2的解决方案

    本文主要介绍了redis requires ruby version2.2.2的解决方案,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2021-07-07
  • Redis实现查看服务状态、关闭和启动方式

    Redis实现查看服务状态、关闭和启动方式

    本文介绍了如何在Linux系统中查看、关闭和启动Redis服务,包括使用命令行工具和配置文件进行相关操作
    2025-11-11
  • Redis模拟延时队列实现日程提醒的方法

    Redis模拟延时队列实现日程提醒的方法

    文章介绍了如何使用Redis实现一个简单的延时任务队列,通过Redis的有序集合特性来存储和管理延时任务,通过定期检查集合中小于等于当前时间的任务并执行,可以实现延时任务的管理,感兴趣的朋友跟随小编一起看看吧
    2024-11-11
  • 使用 Redis 流实现消息队列的代码

    使用 Redis 流实现消息队列的代码

    这篇文章主要介绍了使用 Redis 流实现消息队列,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-11-11
  • Redis与MySQL数据一致性问题的策略模式及解决方案

    Redis与MySQL数据一致性问题的策略模式及解决方案

    开发中,一般会使用Redis缓存一些常用的热点数据用来减少数据库IO,提高系统的吞吐量,本文将给大家介绍了Redis与MySQL数据一致性问题的策略模式及解决方案,文中通过代码示例介绍的非常详细,需要的朋友可以参考下
    2024-07-07
  • Redis数据结构之链表与字典的使用

    Redis数据结构之链表与字典的使用

    这篇文章主要介绍了Redis数据结构之链表与字典的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-05-05
  • Redis如何实现投票功能

    Redis如何实现投票功能

    这篇文章主要介绍了Redis如何实现投票功能,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-05-05
  • Redis Scan命令的基本使用方法

    Redis Scan命令的基本使用方法

    这篇文章主要给大家介绍了关于Redis中Scan命令的基本使用方法,文中通过示例代码介绍的非常详细,对大家学习或者使用Redis具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-12-12
  • CentOS 6.6下Redis安装配置记录

    CentOS 6.6下Redis安装配置记录

    这篇文章主要介绍了CentOS 6.6下Redis安装配置记录,本文给出了安装需要的支持环境、安装redis、测试Redis、配置redis等步骤,需要的朋友可以参考下
    2015-03-03
  • redis-sentinel基础概念及部署流程

    redis-sentinel基础概念及部署流程

    Redis Sentinel是Redis的高可用解决方案,通过监控主从节点、自动故障转移、通知机制及配置提供,实现集群故障恢复与服务持续可用,核心组件包括Sentinel节点、主节点和从节点,部署需配置参数,验证主节点变化即成功
    2025-08-08

最新评论