【需求变更】使用 Redis 和 Lua 脚本实现变更后方案编号的生成

作者: zhl 分类: Redis 发布时间: 2024-11-06 09:20

项目有部分需求变更,比如之前使用的 MyBatis-plus 封装的雪花算法,但现在一些业务比如某些方案编号,需要以特定格式生成,现在文件编号要求以 年-月-4位序号 的格式生成,当年或月变化时,4位序号需要重置,比如:2024-11-0001

这种需求类似于生成全局唯一 ID,并且要求自增,Redis 似乎非常合适,准备开干💪

  1. Redis 中 String 非常适合做计数器,因为我需要保存的 value 是整数值并且这个整数值可以用 long 类型来表示,Redis 底层会将整数值保存在字符串对象结构的 ptr属性里面,并将字符串对象的编码设置为 int
    除此之外,String 还可以保存一些常用的对象 JSON 数据,缓存 Session 信息,实现分布式锁等。


  2. 在项目中 Redis 中存储了大量经常查询的数据,为减少 Redis 的内存压力,设置过期时间为十分钟,同时为保证原子性和并发安全,使用 lua 脚本实现 redis 操作。


  /**
  * 初始化方案编号
  */
  private static final String LUA_GENERATE_PROJECT_CODE = "lua/generate_project_code.lua";

  @Resource
  private StringRedisTemplate stringRedisTemplate;

  private void initBaseInfo(ProjectVo vo) {
      if (ObjUtils.isEmpty(vo.getProjectCode())) {
          String operateDate = DateUtils.getCurDate();
          String[] parts = operateDate.split("-");
          String year = parts[0];
          String month = parts[1];
          String prefix = "PROJECT:PROJECT_CODE:";
          String key = prefix + year + ":" + month + ":";
          DefaultRedisScript<String> defaultRedisScript = new DefaultRedisScript<>();
          defaultRedisScript.setResultType(String.class);
          defaultRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource(LUA_GENERATE_PROJECT_CODE)));
          String sequence = stringRedisTemplate.execute(defaultRedisScript, Collections.singletonList(key));
          String projectCode = year + "-" + month + "-" + sequence;
          vo.setProjectCode(projectCode);
      }
  }
    --如果根据 key 获取 value == nil,那么就说明到了下个月,重置 计数器
    --否则 计数器++,返回值是格式化为四位的字符串
    local key = KEYS[1]
    local current = redis.call("get", key)
    if current == nil then
      -- 设置过期时间 31 天
      redis.call("set", key, "1", "EX", 2678400)
      current = "1"
    else
      redis.call("incr", key)
      current = redis.call("get", key)
    end
    local value = tonumber(current)
    return string.format("%04d", value)

需要注意即使设置过期时间为 10 分钟,由于当月或年发生变化,所以 key 不存在,会直接创建 key,并重置计数器的值为 1。

  • 在实现需求过程遇到的问题
    • 在项目中已经封装了一个 Redis 工具类,但功能较少,且使用的是 RedisTemplate 对象,我直接使用工具类中的对象生成时报了 序列化反序列化异常,还好之前学习 Redis 时,有记录一些笔记和思考,这是因为 Redis 默认使用 JDK 方式的序列化,实际上 Redis 已经帮助我们简化了序列化和反序列化的过程,可以直接注入 StringRedisTemplate,具体可参考之前的文章。

      RedisTemplate的默认序列化方式及改进【一】
      RedisTemplate的默认序列化方式及改进【二】


发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注