分布式ID的实现方式有多种:UUID、雪花算法、数据库自增ID、数据库序列(Oracle)、Redis等等。下面将以Redis为例实现一个简单的分布式ID。

一、Redis实现分布式ID的核心指令

Redis实现分布式ID的核心指令是incr系列指令,它们可以实现变量自增。

同时,Redis的time指令可以生成当前时间的时间戳,可以用来生成某些时间关联性较强的ID。

二、使用lua脚本获取分布式ID的关键信息

生成分布式ID所需信息的lua脚本如下所示:

-- 自增量累加1(KEYS[1]为ID所属的业务名称,如:ORDER_ID)
local increment = redis.call('incr', KEYS[1]); 
-- 当自增量达到上限时,重新开始计数(ARGV[1]为自增量上限,如:9999)
if(increment >= tonumber(ARGV[1])) then 
    increment = 1; 
    redis.call('set', KEYS[1], increment); 
end 
-- 获取当前时间戳
local time = redis.call('time')[1]; 
-- 返回当前时间戳与自增量
return {time,increment}; 

三、基于Redisson调用lua脚本实现分布式ID

下面将基于Redisson与上述的lua脚本,实现一个包含时间前缀与冗余自增量后缀的分布式ID。

如果对分布式ID的格式没有特殊需求,使用Redisson的RIdGenerator就足矣。

/**
 * 基于Redis的分布式ID生成器
 */
public class IdGenerator {
  private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyyMMddHHmmss");
  private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0000");
  private static final int INCREMENT_LIMIT = 9999;
    
  public static String getId(RedissonClient redissonClient, String sequenceName) {
    List<Number> idItems = getIdItems(redissonClient, sequenceName);
    return formatId(idItems);
  }
    
  private static String getLuaScript() {
    return "local increment = redis.call('incr', KEYS[1]); " +
            "if(increment >= tonumber(ARGV[1])) then " +
            "increment = 1; " +
            "redis.call('set', KEYS[1], increment); " +
            "end " +
            "local time = redis.call('time')[1]; " +
            "return {time,increment}; ";
  }
    
  private static List<Number> getIdItems(RedissonClient redissonClient, String sequenceName) {
    RScript script = redissonClient.getScript(new JsonJacksonCodec());
    return script.eval(
            Mode.READ_WRITE,
            getLuaScript(),
            ReturnType.VALUE,
            Collections.singletonList(sequenceName),
            INCREMENT_LIMIT);
  }
    
  private static String formatId(List<Number> idItems) {
    Integer timestamp = (Integer) idItems.get(0);
    Long increment = (Long) idItems.get(1);
    String idPrefix = SIMPLE_DATE_FORMAT.format(new Date(Long.parseLong(String.valueOf(timestamp))*1000));
    String idSuffix = DECIMAL_FORMAT.format(increment);
    return idPrefix + idSuffix;
  }
    
}