mysql和redis实现自定义序号生成

13 篇文章 1 订阅
3 篇文章 0 订阅

背景

项目需要序号服务,该序号由标志字母+日期+自增数字。所以,考虑使用redis和mysql的方式去实现它。获取序号通过redis,可以保证线程安全。然后号段通过mysql去操作,对mysql的操作通过redisson分布式锁保证分布式一致性。

1,所需环境
  1. redis和redisson分布式锁
  2. mysql
  3. mybatisPlus
2,数据库表
CREATE TABLE `t_redis_sequence` (
  `biz_tag` varchar(64) NOT NULL COMMENT '业务标签',
  `prefix_abc` varchar(16) NOT NULL DEFAULT '' COMMENT '最开头 字母前缀',
  `prefix_date` varchar(16) NOT NULL DEFAULT '' COMMENT '日期前缀',
  `length` int(11) NOT NULL DEFAULT '7' COMMENT '字段长度',
  `current_id` bigint(20) NOT NULL DEFAULT '1' COMMENT '当前编号',
  `refresh` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否每日刷新,1 刷新  0 不刷新',
  PRIMARY KEY (`biz_tag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='redis取流水号表'
3,业务实现代码
@Component
@Slf4j
public class RedisSeqGenerator {

    @Autowired
    private TmsRedisUtils tmsRedisUtils;

    @Autowired
    private RedisSequenceMapper redisSequenceMapper;

    @Autowired
    private RedissonClient redisson;

    /**
     * 每次缓存的数量
     */
    private static final int SECTION_WIDTH = 10000;

    /**
     * 获取流水号
     */
    public String getNextSeq(RedisSeqEnums redisSeqEnums) {
        String bizTag = redisSeqEnums.getBizTag();
        String id =tmsRedisUtils.listleftPop(RedisKeyConstant.TMS_SEQUENCE_PREFIX + bizTag);
        if(id == null){
            Long current = 0L;
            RedisSequence seq = null;
            RLock lock = redisson.getLock(RedisKeyConstant.TMS_REDIS_LOCK_PREFIX + bizTag);
            try{
                lock.lock();
                //二次 判断
                id = tmsRedisUtils.listleftPop(RedisKeyConstant.TMS_SEQUENCE_PREFIX + bizTag);
                if(id != null){
                    return id;
                }
                //从数据库获取seq
                seq = redisSequenceMapper.selectById(bizTag);
                if(seq == null){
                    seq = new RedisSequence();
                    seq.setCurrentId(1L);
                    seq.setBizTag(bizTag);
                    seq.setPrefixAbc(redisSeqEnums.getPrefixAbc());
                    seq.setLength(redisSeqEnums.getLength());
                    seq.setRefresh(redisSeqEnums.getRefresh());
                    seq.setPrefixDate(LocalDateTime.now().format(TimeConstant.FORMATTER));
                    redisSequenceMapper.insert(seq);
                }
                current = seq.getCurrentId();
                seq.setCurrentId(seq.getCurrentId() + SECTION_WIDTH);
                redisSequenceMapper.updateById(seq);
                //存入redis缓存
                cache(bizTag, seq,current);
                id = tmsRedisUtils.listleftPop(RedisKeyConstant.TMS_SEQUENCE_PREFIX + bizTag);
            }finally {
                lock.unlock();
            }
        }
        return id;
    }


    /** 存入redis缓存 */
    private void cache(String bizTag, RedisSequence seq,Long  current) {
        List<String> ids = new ArrayList<>();
        for(long i = current;i<current + SECTION_WIDTH ;i++){
            ids.add(seq.getPrefixAbc() + seq.getPrefixDate()+ addZeroToSeq(i,seq.getLength()));
        }
        tmsRedisUtils.listRightPushAll(RedisKeyConstant.TMS_SEQUENCE_PREFIX + bizTag,ids);
    }

    /** 在前面加0 */
    private String addZeroToSeq(long currentId, int length){
        String c = String.valueOf(currentId);
        length = length - c.length();
        if(length > 0){
            for(int i = 0; i< length;i++){
                c = "0" + c;
            }
        }
        return c;
    }
}
4,所依赖的枚举类,实体类
@Getter
public enum RedisSeqEnums {

    订单号流水号("orderNoSeq", "T",7,1),
    运单号流水号("waybillNoSeq", "W",8,1),

    承运商流水号("carrierNoSeq", "SUP",3,1),
    短信验证码流水号("smsReqIdSeq", "",12,1)
    ;
    /**业务类型*/
    private String bizTag;

    /**最开头 字母前缀*/
    private String prefixAbc;

    /**长度*/
    private Integer length;

    /**是否每日刷新,1 刷新  0 不刷新*/
    private Integer refresh;

    RedisSeqEnums(String bizTag, String prefixAbc, Integer length, Integer refresh) {
        this.bizTag = bizTag;
        this.prefixAbc = prefixAbc;
        this.length = length;
        this.refresh = refresh;
    }
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("t_redis_sequence")
@Accessors(chain = true)
public class RedisSequence implements Serializable {

    /**业务标签*/
    @TableId
    private String bizTag;

    /**最开头 字母前缀*/
    private String prefixAbc;

    /**日期前缀*/
    private String prefixDate;

    /**当前编号*/
    private Long currentId;

    /**长度*/
    private Integer length;

    /**是否每日刷新,1 刷新  0 不刷新*/
    private Integer refresh;
}
5,序号的每日刷新

我通过定时任务的方式去刷新日期字符串,大家可以用的定时组件不一致,我这边只提供代码

       //修改数据库中的时间日期字符串
       String dateStr = LocalDateTime.now().format(TimeConstant.FORMATTER);
       List<RedisSequence> list = redisSequenceMapper.selectCurrentIdByRefresh();
       if(list != null && list.size()>0){
           list.forEach(v ->{
               v.setCurrentId(1L);
               v.setPrefixDate(dateStr);
               redisSequenceMapper.updateById(v);
           });
       }
       //清空redis缓存的流水号
        for (RedisSeqEnums value : RedisSeqEnums.values()) {
            tmsRedisUtils.lTrim(RedisKeyConstant.TMS_SEQUENCE_PREFIX + value.getBizTag(),1,0);
        }

总结

我这边只是提供了一种实现方式,大家可以参考。

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值