项目有部分需求变更,比如之前使用的 MyBatis-plus 封装的雪花算法,但现在一些业务比如某些方案编号,需要以特定格式生成,现在文件编号要求以 年-月-4位序号
的格式生成,当年或月变化时,4位序号需要重置,比如:2024-11-0001
。
这种需求类似于生成全局唯一 ID,并且要求自增,Redis 似乎非常合适,准备开干💪
-
Redis 中 String 非常适合做计数器 ,因为我需要保存的 value 是整数值并且这个整数值可以用
long
类型来表示,Redis 底层会将整数值保存在字符串对象结构的ptr
属性里面,并将字符串对象的编码设置为int
。除此之外,String 还可以保存一些常用的对象 JSON 数据,缓存 Session 信息,实现分布式锁等。
-
在项目中 Redis 中存储了大量经常查询的数据,为减少 Redis 的内存压力,设置过期时间为一个月,同时为保证原子性和并发安全,使用 lua 脚本实现 redis 操作。
java
/**
* 初始化方案编号
*/
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);
}
}
lua
--如果根据 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)
需要注意即使设置过期时间为 31 天,由于当月或年发生变化,所以 key 不存在,会直接创建 key,并重置计数器的值为 1。
- 在实现需求过程遇到的问题
- 在项目中已经封装了一个 Redis 工具类,但功能较少,且使用的是
RedisTemplate
对象,我直接使用工具类中的对象生成时报了 序列化反序列化异常 ,还好之前学习 Redis 时,有记录一些笔记和思考,这是因为 Redis 默认使用 JDK 方式的序列化,实际上 Redis 已经帮助我们简化了序列化和反序列化的过程,可以直接注入 StringRedisTemplate,具体可参考之前的文章。 RedisTemplate的默认序列化方式及改进【一】
- 在项目中已经封装了一个 Redis 工具类,但功能较少,且使用的是