基于redis生成自增流水号(格式:标志位 + 年月日时分秒 + 自增流水号)

本文介绍了一种使用Redis生成自增流水号的方法,适用于需要快速生成唯一标识的场景。文章详细解释了如何将时间戳与递增ID结合,形成如T20190809135743000001的流水号格式,并讨论了并发处理和每日重置的实现细节。

       最近开发时,又遇到一个需求,后端需要生成自增的流水号,流水号格式一般就是:标志位 + 年月日时分秒 + 一定位数的流水号  组成,例如: T20190809135743000001 。

根据先前的查询和总结实现,现在对该需求的解决方法做一个整理记录。

       先前在网上看各位大佬对自增流水号的处理,一般分成三种:

      1. 通过在Java类中生成;

      2.依托数据库自增函数生成;

      3.依托redis自增生成

       因为我当前项目中本身就整合redis,而且redis是单线程,且基于内存操作,速度快,实现自增流水号代码也简单,所以我选用的是第三种。

        实现自增流水号,格式 标志位 + 年月日时分秒 + 一定位数的自增流水号 ,首先把这个分成两部分,一部分是前面的标志位 + 年月日时分秒,另一部分是一定位数的自增流水号,分别实现,最后拼接输出即可。

第一步:

生成流水号第一部分:标志位 + 年月日时分秒,这个很简单,不多说,代码如下:

        StringBuffer sbuffer = new StringBuffer();
        sbuffer.append("T");        //标志位
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        sbuffer.append(sdf.format(new Date()));     //年月日时分秒

第二步,调用redis的自增函数,获得流水号第二部分,自增

    //设置6自增6位,用于补全操作
    private static final String STR_FORMAT = "000000";


    /**
     * redis流水号自增
     * @param key        自己设置,保存当前自增值
     * @param liveTime   在redis中的缓存时间,方法中设置单位(秒/分/天……)
     * @return
     */
    public String incr(String key, long liveTime) {
        RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, stringRedisTemplate.getConnectionFactory());
        Long increment = entityIdCounter.getAndIncrement();

        if ((null == increment || increment.longValue() == 0) && liveTime > 0) {//初始设置过期时间
            entityIdCounter.expire(liveTime, TimeUnit.DAYS);    //设置自增值过期时间,liveTime 过期时间;TimeUnit.DAYS 过期时间单位,我这边设置为天
        }
        if (increment == 0) {           
            increment = increment + 1;
        } else if (increment > 999999){    
            increment = 1L;
        }
        //位数不够,前面补0 
        DecimalFormat df = new DecimalFormat(STR_FORMAT);    
        return df.format(increment);
    }

上述方法中,需要注意:

      1.保证每一天的流水号都是从01开始,所以我们的自增流水号对应的key必须要带上当天日期,例如:T20190809

SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");    
String incKey = "T" + dateFormat.format(new Date());

     2. 设置自增值的过期时间,即什么时候让redis销毁无用的保存自增值的key

     3.保证流水号输出的位数不变,需要对自增值做位数补0操作

     4.当前流水号大于设置的位数时,需要从头开始计数。

 

只要把上述两个代码片段结果拼接在一起,就能得到我们需要的自增流水号:T20190809135743000001

    @Override
    public String selectTaskNo() {
        //格式:T+yyyymmddHHmiss+6位流水
        StringBuffer sbuffer = new StringBuffer();
        sbuffer.append("T");
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        sbuffer.append(sdf.format(new Date()));
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
        String incKey = "T" + dateFormat.format(new Date());
        String no = incr(incKey, 1);
        sbuffer.append(no);
        System.out.println(">>>>>>>>>>>" + sbuffer.toString());
        return sbuffer.toString();
    }

用 postMan 并发测试当前接口(并发值:100),控制台输出正确值,没有出现乱序现象

<think>嗯,用户想用Redis生成唯一递流水号,需要详细的代码示例和解释。首先,我得回忆一下Redis中适合生成ID的命令。记得INCR和INCRBY这些命令是原子性的,可以确保并发安全。那用户可能用的是Java,因为之前的问题里提到了Java中使用Redis生成单号,引用[2]里也提到了Jedis的方法。 接下来,我需要确定如何设计这个流水号。通常流水号需要日期前缀,比如年月日,再加上自序列号。比如202310200001这样的格式。所以需要每天重置序列号,这时候就需要用日期作为key的一部分,比如order_id:20231020,这样每天的key不同,INCR后就可以从1开始。 然后考虑代码结构。用户需要完整的示例,所以应该包括引入Jedis依赖,连接Redis生成流水号的方法,以及关闭连接的部分。需要处理异常,比如连接失败或者Redis操作出错的情况。还要注意线程安全,使用Jedis连接池可能更好,但示例可能简化直接用单连接。 引用[2]中提到NX和XX参数,但这里可能不需要,因为直接用INCR的话,如果key不存在会自动创建并初始化为0,然后加到1。不过如果需要设置过期时间,比如保留一天的key,可以用EXPIRE命令。但用户可能只是需要每天重置,所以过期时间可能不是必须的,但加上可以避免key过多。 然后代码步骤大概是:获取当前日期,拼接key,使用INCR命令获取序列号格式化为4位数字,拼接成流水号。需要处理序列号不足位数补零的情况,比如1变成0001。Java中用String.format("%04d", seq)可以实现。 测试部分也要考虑,比如多线程并发情况下是否真的唯一,这时候需要说明Redis的原子性保证了这一点。可能还需要提到集群环境下如果有多个Redis实例,可能需要用不同的key策略或者使用Redis的分布式锁,但用户可能只是单实例使用,所以暂时不展开。 错误处理部分,比如捕获Jedis异常,记录日志,或者抛出运行时异常让调用者处理。用户可能需要知道如何处理连接问题,比如检查Redis服务是否启动,配置是否正确,引用[1]中提到了连接和关闭Redis的命令,可能相关。 最后,总结关键点:原子性INCR、日期key、格式序列号、异常处理。然后生成相关问题,比如如何保证集群下的唯一性,如何添加其他业务标识,性能如何,这些可以引导用户进一步学习。</think>### Redis生成唯一递流水号实现详解 通过Redis的原子性操作实现唯一递流水号,核心是使用`INCR`命令和日期格式组合。以下是Java+Jedis的完整实现: ```java import redis.clients.jedis.Jedis; import java.text.SimpleDateFormat; import java.util.Date; public class RedisSerialGenerator { private static final String REDIS_HOST = "127.0.0.1"; private static final int REDIS_PORT = 6379; private static final String SERIAL_KEY_PREFIX = "serial:"; // 生成流水号格式示例:20231020-0001 public String generateSerial(String bizType) { Jedis jedis = null; try { jedis = new Jedis(REDIS_HOST, REDIS_PORT); // 1.生成日期键 String dateStr = new SimpleDateFormat("yyyyMMdd").format(new Date()); String redisKey = SERIAL_KEY_PREFIX + bizType + ":" + dateStr; // 2.原子递(自动处理key不存在情况) Long seq = jedis.incr(redisKey); // 3.设置key过期时间(30天) jedis.expire(redisKey, 30 * 24 * 3600); // 4.生成完整流水号 return String.format("%s-%04d", dateStr, seq); } finally { if (jedis != null) jedis.close(); } } } ``` #### 关键点说明 1. **原子性保证**:`INCR`命令是原子操作,即使并发请求也能保证序列号唯一性[^2] 2. **键名设计**:`serial:业务类型:日期`结构支持多业务类型和每日重置序列 3. **自动补零**:`%04d`格式确保序列号固定4位长度,不足补零 4. **过期策略**:设置30天过期时间避免无效key堆积[^1] #### 测试验证 ```java public static void main(String[] args) { RedisSerialGenerator generator = new RedisSerialGenerator(); // 输出:20231020-0001 System.out.println(generator.generateSerial("order")); // 输出:20231020-0002 System.out.println(generator.generateSerial("order")); } ``` #### 异常处理建议 - 添加重试机制应对网络抖动 - 捕获`JedisConnectionException`处理连接故障 - 使用连接池提升性能(推荐JedisPool)
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值