分布式系统中,有一些需要使用全局唯一 ID 的场景,这种时候为了防止 ID 冲突可以使用 36 个字符的 UUID,但是 UUID 有一些缺点,首先他相对比较长,另外 UUID 一般是无序的字符串。
有些时候我们希望能使用简单一些的 ID,并且希望 ID 能够按照时间有序生成,为了解决这个问题,Twitter 发明了 SnowFlake 算法,不依赖第三方介质例如 Redis、数据库,本地程序生成分布式自增 ID,这个 ID 只能保证在工作组中的机器生成的 ID 唯一,不能像 UUID 那样保证时空唯一。
/** 上次生成 ID 的时间截 */ privatelong lastTimestamp = -1L;
/** * 创建 ID 生成器的方式一: 使用工作机器的序号,范围是 [0, 1023],优点是方便给机器编号 * * @param workerId 工作机器 ID */ publicIdWorker(long workerId){ long maxMachineId = (MAX_DATACENTER_ID +1) * (MAX_WORKER_ID +1) - 1; // 1023
if (workerId < 0 || workerId > maxMachineId) { thrownew IllegalArgumentException(String.format("Worker ID can't be greater than %d or less than 0", maxMachineId)); }
/** * 创建 ID 生成器的方式二: 使用工作机器 ID 和数据中心 ID,优点是方便分数据中心管理 * * @param datacenterId 数据中心 ID (0~31) * @param workerId 工作机器 ID (0~31) */ publicIdWorker(long datacenterId, long workerId){ if (workerId > MAX_WORKER_ID || workerId < 0) { thrownew IllegalArgumentException(String.format("Worker ID can't be greater than %d or less than 0", MAX_WORKER_ID)); } if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0) { thrownew IllegalArgumentException(String.format("Datacenter ID can't be greater than %d or less than 0", MAX_DATACENTER_ID)); }
/** * 获得下一个 ID(该方法是线程安全的),同一机器同一时间可产生 4096 个 ID,70 年内不生成重复的 ID * * @return long 类型的 ID */ publicsynchronizedlongnextId(){ long timestamp = timeGen();
// 如果当前时间小于上一次 ID 生成的时间戳,说明系统时钟回退过这个时候应当抛出异常 if (timestamp < lastTimestamp) { thrownew RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); }
publicstaticvoidmain(String[] args){ IdWorker idWorker = new IdWorker(0, 0);
for (int i = 0; i < 1000; i++) { long id = idWorker.nextId(); System.out.println(id); System.out.println(StringUtils.leftPad(Long.toBinaryString(id), 64, "0")); } } }