分布式唯一ID生成器

264 阅读2分钟

#原理 来自于Twitter的snowflake算法,国内实现较好的是百度的uid-generator项目,开源地址:github.com/baidu/uid-g… 基本原理就是把一个64位的long类型的值按位进行拆分,39位表示时间戳,5位表示业务线,5位表示机器码,3位表示机房,12位表示自增的sequence,然后按位或组装依赖形成10进制的数,就是唯一的ID。 #变化

public class OrderNoGenerator {

private final static String ORDER_CENTER_WORKID_PREFIX = "order_center_workId";//机器号ID的前缀
private static final String CURRENT_MAX_ID = "_current_max_id";//当前最大的机器ID
private static final String INIT_LOCK = "_init_lock";//初始化锁
private static final int MAX_WORKER_ID = 99;//机器号增长到99将重新归0
//机器号的超时时间,防止服务挂掉机器ID仍然长期存在于Redis
private static final int WORK_ID_LIVE_TIME = 60 * 5;
//同一时间段中,单机自增
private static AtomicInteger sequence = new AtomicInteger(0);
private static Long workId;//当前机器的机器号
private static volatile String lastTimeStamp;//标识时间段


public static String nextOrderNo(OrderSourceEnum orderSourceEnum) {
    String timeStamp = DateUtils.dateFormat(DateUtils.ORDER_NO_PREFIX_PATTERN);
    String sequence = StringUtils.leftPad(String.valueOf(getSequence(timeStamp)), 4, '0');
    String workIdStr = StringUtils.leftPad(String.valueOf(workId), 2, '0');
    return MessageFormat.format("{0}{1}{2}{3}", timeStamp, workIdStr, sequence, orderSourceEnum.getCode());
}
//单机序列生成,加锁防止并发问题
private static synchronized int getSequence(String timeStamp) {
    if (timeStamp != null && !timeStamp.equals(lastTimeStamp)) {
        lastTimeStamp = timeStamp;
        sequence = new AtomicInteger(0);
        return sequence.get();
    }
    return sequence.incrementAndGet();
}
//机器启动时初始化机器码,需要加分布式锁
public static Long intWorkId() {
    String lockKey = ORDER_CENTER_WORKID_PREFIX + INIT_LOCK;
    if (!tryLock(lockKey, 1)) {
        throw new BusinessException("OrderNoGenerator.getWorkId 加锁异常");
    }
    Long workId = nextWorkId();
    RedisUtils.set(ORDER_CENTER_WORKID_PREFIX + ":" + workId, workId, WORK_ID_LIVE_TIME);
    unLock(lockKey);
    return workId;
}

private static void unLock(String lockKey) {
    RedisUtils.del(lockKey);
}
//分布式锁,尝试加三次
private static boolean tryLock(String lockKey, int tryTimes) {
    Boolean setNX = RedisUtils.setNX(lockKey, "lock");
    if (!setNX) {
        log.info("加锁失败,睡眠10s.lockKey={},tryTimes={}", lockKey, tryTimes);
        if (tryTimes > 3) {
            return false;
        }
        try {
            Thread.sleep(10 * 1000);
        } catch (InterruptedException e) {
            log.error("OrderNoGenerator.tryLock,sleep 异常:", e);
        }
        return tryLock(lockKey, ++tryT);imes
    }
    return true;
}

private static Long nextWorkId() {
    String maxIdKey = ORDER_CENTER_WORKID_PREFIX + CURRENT_MAX_ID;
    //从Redis中申请一个机器码,一定范围内机器码是递增的
    Long workId = RedisUtils.getAndIncrement(maxIdKey, 0L);
    //如果机器码大于99,则重新从0开始
    if (workId > MAX_WORKER_ID) {
        RedisUtils.set(maxIdKey, 0L);
        return nextWorkId();
    }
    //如果机器码已存在,则递归重新获取
    if (RedisUtils.exists(ORDER_CENTER_WORKID_PREFIX + ":" + workId)) {
        return nextWorkId();
    }
    return workId;
}

@Component
public static class WorkIdInitialize implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {//服务启动时调用
        workId = intWorkId();
    }

}

@Component
private static class WorkIdKeepLive {
    @Scheduled(cron = "0 0/5 * * * *")
    public void reLiveWorkId() {//当前机器码在Redis会过期,每隔一段时间重新set一次,相当于心跳
        try {
            RedisUtils.set(ORDER_CENTER_WORKID_PREFIX + ":" + workId, workId, WORK_ID_LIVE_TIME);
        } catch (Exception e) {
            log.error("redis operation is unavailable", e);
        }
    }
}

}