[Java]基于Redis的分布式环境下的自增编号生成器

Java教程 2025-10-18

本文介绍一款在分布式环境下的自增编号生成器的设计与代码实现,可实现在分布式环境下的编号递增且唯一。 主要基于Redis+分布式锁实现。

1 设计与实现

此编号生成器可生成格式为:{前缀}-{年份}-{自增整数}的编号。如:Ticket-2025-0001,当跨年后自增整数会重置,并可配置自增整数的最少补全位数。

代码如下:

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.time.LocalDateTime;
import java.time.Year;
import java.util.Objects;
import java.util.function.Supplier;

/**
 * 自增编号生成器
 */
public class NumberGenerator {
    // 年份的redis key
    private static final String YEAR_KEY = "numberGenerateYear";

    private final StringRedisTemplate stringRedisTemplate;

    private final RedissonClient redissonClient;

    // 自增整数的redis key
    private final String numberKey;

    // 前缀
    private final String prefix;

    // 编号格式化字符串
    private final String format;

    /**
     * 初始化编号生成器
     * 
     *     编号生成格式:{prefix}-{当前年份}-{自增整数};
     *     当自增整数不足fillNum位时,会前补零,补齐到fillNum位;
     *     举例:(1)前缀为Ticket,当前为2023年,自增整数为12,fillNum为5时,
     *          生成编号:Ticket-2023-00012;
     *
     *          (2)前缀为Ticket,当前为2023年,自增整数d为121121,fillNum为5时,
     *          生成编号:Ticket-2023-121121
     * 
* * @param prefix 编号前缀 * @param fillNum 自增整数的最少补全位数 * @param initNumberSupplier 初始整数值提供器 * @param stringRedisTemplate StringRedisTemplate bean依赖注入 * @param redissonClient RedissonClient bean依赖注入 */
public NumberGenerator(String prefix, int fillNum, Supplier initNumberSupplier, StringRedisTemplate stringRedisTemplate, RedissonClient redissonClient) { this.prefix = prefix; this.format = "%s-%d-%0" + fillNum + "d"; this.numberKey = "numberGenerate:" + prefix; // 注入依赖bean this.stringRedisTemplate = stringRedisTemplate; this.redissonClient = redissonClient; // 初始化自增整数 String numberValue = stringRedisTemplate.opsForValue().get(numberKey); if (numberValue == null) { Integer initNumber = initNumberSupplier.get(); initNumber = initNumber == null ? 0 : initNumber; // 保存到redis stringRedisTemplate.opsForValue().set(numberKey, String.valueOf(initNumber)); } // 初始化年份 stringRedisTemplate.opsForValue().setIfAbsent(YEAR_KEY, Year.now().toString()); } /** * 获取下一个编号 * * @return 编号 */ public String nextNumber() { // 加分布式锁 RLock lock = redissonClient.getLock("lock:numberGenerate:" + prefix); lock.lock(); try { return doNextNumber(); } finally { if (lock.isLocked()) { lock.unlock(); } } } private String doNextNumber() { int year = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get(YEAR_KEY))); int currentYear = LocalDateTime.now().getYear(); if (currentYear > year) { // 跨年的情况 year = currentYear; // 刷新redis缓存 stringRedisTemplate.opsForValue().set(YEAR_KEY, String.valueOf(currentYear)); stringRedisTemplate.opsForValue().set(numberKey, "0"); // 重新递增 } Long number = stringRedisTemplate.opsForValue().increment(numberKey); // 编号递增 ++i return String.format(format, prefix, year, number); } }

2 使用示例

创建一个审核单编号生成器,前缀为AUDIT,最少补全位数为4,将其注册为Spring Bean,在需要的地方依赖注入即可使用。

代码如下:

import com.example.mapper.AuditMapper;

import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.Year;

import javax.annotation.Resource;

/**
 * 

审核单编号生成器

*/
@Component public class AuditNoGenerator implements InitializingBean { // 编号生成器 private NumberGenerator numberGenerator; // 审核单记录表Mapper @Resource private AuditMapper auditMapper; @Resource private StringRedisTemplate stringRedisTemplate; @Resource private RedissonClient redissonClient; private AuditNoGenerator() { } /** * Bean初始化完成后执行 */ @Override public void afterPropertiesSet() { initNumberGenerator(); } /** * 生成下一个编号 * * @return 编号 */ public String nextNo() { return numberGenerator.nextNumber(); } /** * 初始化编号生成器 */ private void initNumberGenerator() { numberGenerator = new NumberGenerator("AUDIT", 4, () -> auditMapper.selectMaxAuditNumByYear(Year.now().getValue()), stringRedisTemplate, redissonClient); } }

auditMapper.selectMaxAuditNumByYear(Year.now().getValue())是自定义的数据库查询方法,用于获取某个年份的审核单号的最大值,比如:今年(2025)的最大编号为AUDIT-2025-1201,则返回1201。

selectMaxAuditNumByYear的方法定义:

/**
 * 获取某个年份的审核单号的最大值
 * 

* 比如:2023年的最大编号为AUDIT-2023-1986,则返回1986 * * @param year 年份 * @return 某个年份的审核单号的最大值 */ Integer selectMaxAuditNumByYear(int year);

对应的Mybatis mapper xml代码如下:

<select id="selectMaxAuditNumByYear" resultType="java.lang.Integer">
    
    select MAX(CONVERT(SUBSTR(audit_no, 12), UNSIGNED))
    from t_audit
    where audit_no like concat('AUDIT-', #{year}, '%')
select>

使用方式

使用方式如下:

@Service
public class AuditService {
    @Resource // 注入依赖
    private AuditNoGenerator auditNoGenerator;
    
    public void createAudit() {
        // 模拟创建审核单
        AuditEntity auditEntity = new AuditEntity();
        auditEntity.setAuditNo(auditNoGenerator.nextNo()); // 生成单号
        // ...
    }
}