/* * Copyright 1999-2019 Seata.io Group. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.seata.common.util;
/** * Start time cut (2020-05-03) */ privatefinallongtwepoch=1588435200000L;
/** * The number of bits occupied by workerId */ privatefinalintworkerIdBits=10;
/** * The number of bits occupied by timestamp */ privatefinalinttimestampBits=41;
/** * The number of bits occupied by sequence */ privatefinalintsequenceBits=12;
/** * Maximum supported machine id, the result is 1023 */ privatefinalintmaxWorkerId= ~(-1 << workerIdBits);
/** * business meaning: machine ID (0 ~ 1023) * actual layout in memory: * highest 1 bit: 0 * middle 10 bit: workerId * lowest 53 bit: all 0 */ privatelong workerId;
/** * timestamp and sequence mix in one Long * highest 11 bit: not used * middle 41 bit: timestamp * lowest 12 bit: sequence */ private AtomicLong timestampAndSequence;
/** * mask that help to extract timestamp and sequence from a long */ privatefinallongtimestampAndSequenceMask= ~(-1L << (timestampBits + sequenceBits));
/** * instantiate an IdWorker using given workerId * @param workerId if null, then will auto assign one */ publicIdWorker(Long workerId) { initTimestampAndSequence(); initWorkerId(workerId); }
/** * init workerId * @param workerId if null, then auto generate one */ privatevoidinitWorkerId(Long workerId) { if (workerId == null) { workerId = generateWorkerId(); } if (workerId > maxWorkerId || workerId < 0) { Stringmessage= String.format("worker Id can't be greater than %d or less than 0", maxWorkerId); thrownewIllegalArgumentException(message); } this.workerId = workerId << (timestampBits + sequenceBits); }
/** * get next UUID(base on snowflake algorithm), which look like: * highest 1 bit: always 0 * next 10 bit: workerId * next 41 bit: timestamp * lowest 12 bit: sequence * @return UUID */ publiclongnextId() { waitIfNecessary(); longnext= timestampAndSequence.incrementAndGet(); longtimestampWithSequence= next & timestampAndSequenceMask; return workerId | timestampWithSequence; }
/** * block current thread if the QPS of acquiring UUID is too high * that current sequence space is exhausted */ privatevoidwaitIfNecessary() { longcurrentWithSequence= timestampAndSequence.get(); longcurrent= currentWithSequence >>> sequenceBits; longnewest= getNewestTimestamp(); if (current >= newest) { try { Thread.sleep(5); } catch (InterruptedException ignore) { // don't care } } }
/** * get newest timestamp relative to twepoch */ privatelonggetNewestTimestamp() { return System.currentTimeMillis() - twepoch; }
/** * auto generate workerId, try using mac first, if failed, then randomly generate one * @return workerId */ privatelonggenerateWorkerId() { try { return generateWorkerIdBaseOnMac(); } catch (Exception e) { return generateRandomWorkerId(); } }
/** * use lowest 10 bit of available MAC as workerId * @return workerId * @throws Exception when there is no available mac found */ privatelonggenerateWorkerIdBaseOnMac()throws Exception { Enumeration<NetworkInterface> all = NetworkInterface.getNetworkInterfaces(); while (all.hasMoreElements()) { NetworkInterfacenetworkInterface= all.nextElement(); booleanisLoopback= networkInterface.isLoopback(); booleanisVirtual= networkInterface.isVirtual(); if (isLoopback || isVirtual) { continue; } byte[] mac = networkInterface.getHardwareAddress(); return ((mac[4] & 0B11) << 8) | (mac[5] & 0xFF); } thrownewRuntimeException("no available mac found"); }
/** * randomly generate one as workerId * @return workerId */ privatelonggenerateRandomWorkerId() { returnnewRandom().nextInt(maxWorkerId + 1); } }
算法优缺点
雪花算法有以下几个优点:
高并发分布式环境下生成不重复 id,每秒可生成百万个不重复 id。
基于时间戳,以及同一时间戳下序列号自增,基本保证 id 有序递增。
不依赖第三方库或者中间件。
算法简单,在内存中进行,效率高。
雪花算法有如下缺点:
依赖服务器时间,服务器时钟回拨时可能会生成重复 id。算法中可通过记录最后一个生成 id 时的时间戳来解决,每次生成 id 之前比较当前服务器时钟是否被回拨,避免生成重复 id。