在分布式项目中经常会用到生成唯一主键,或者生成顺序号,现通过Redis实现顺序号的生成。
话不多说,直接上代码
1、生成唯一主键工厂方法接口如下
/**
* 获取唯一序列工厂方法
*/
public interface SequenceFactory {
/**
* 根据指定的键 获取下一个序列
* @param seqKey 键值
* @return 返回唯一序列
*/
long nextValue(String seqKey);
/**
* 根据指定的键,获取每天的唯一序列 设置键的过期时间为 24小时
* @param seqKey 键值
* @return 序列
*/
long dailyNextValue(String seqKey);
}
1、实现方式一
/**
* 通过 Lua 脚本执行
*/
public class RedisLuaSequenceFactory implements SequenceFactory {
private static final long SEQ_START_ID = 0L;
private static final int DEFAULT_EXPIRE = 3600 * 24;
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
private static final String LUA_SCRIPT = "local key = KEYS[1]\n" +
"local expire = ARGV[1]\n" +
"local isExp = nil ~= expire and -1 ~= tonumber(expire)\n" +
"local exists = redis.call('EXISTS', key)\n" +
"local seq = 0\n" +
"if( exists == 0) then\n" +
" redis.call('SET', key, 0)\n" +
"else\n" +
" seq = redis.call('INCR', key)\n" +
" if( seq < 0) then\n" +
" redis.call('SET', key, 0)\n" +
" seq = 0\n" +
" end\n" +
"end\n" +
"\n" +
"local ttl = redis.call('TTL', key)\n" +
"if( isExp and ttl == -1) then\n" +
" redis.call('EXPIRE', key, expire)\n" +
"end\n" +
"return seq";
private Jedis jedis;
public RedisLuaSequenceFactory(Jedis jedis){
this.jedis = jedis;
}
@Override
public long nextValue(String seqKey) {
return value(seqKey, "-1");
}
@Override
public long dailyNextValue(String seqKey) {
String today = LocalDate.now().format(FORMATTER);
String key = today + ":" + seqKey;
return value(key, String.valueOf(DEFAULT_EXPIRE));
}
private long value(String key, String expire){
Object eval = jedis.eval(LUA_SCRIPT, Collections.singletonList(key), Collections.singletonList(expire));
return (Long) eval;
}
}
lua 脚本如下所示
--[[
获取全局唯一ID
--]]
local key = KEYS[1]
local expire = ARGV[1]
local isExp = nil ~= expire and -1 ~= tonumber(expire) -- 判断是否设置过期时间
local seq = 0
local exists = redis.call('EXISTS', key) --首先判断key是否存在
if( exists == 0) then
redis.call('SET', key, 0) -- 如果不存在 则设置 初始值为0
else
-- 如果存在 则判断当前值是否为负值,如果为负值,如果为负值则初始为0
seq = redis.call('INCR', key)
if( seq < 0) then
redis.call('SET', key, 0)
seq = 0
end
end
-- 判断是否存在过期时间,并且是否设置过期时间,如果是则执行设置过期时间
local ttl = redis.call('TTL', key)
if( isExp and ttl == -1) then
redis.call('EXPIRE', key, expire)
end
return seq