- 前言
- 一、推特SnowFlake
- 二、百度UidGenerator
- 三、美团Leaf
- 四、Mongo - ObjectId
- 总结
各式各样的雪花算法 - Java实现
一、推特SnowFlake
public final class SnowFlake {
// 起始的时间戳
private final static long START_STAMP = 1577808000000L; //2020-01-01
// 每一部分占用的位数,就三个
private final static long SEQUENCE_BIT = 12; //序列号占用的位数
private final static long MACHINE_BIT = 5; //机器标识占用的位数
private final static long DATACENTER_BIT = 5; //数据中心占用的位数
// 每一部分最大值
private final static long MAX_DATACENTER_NUM = ~(-1L << DATACENTER_BIT);
private final static long MAX_MACHINE_NUM = ~(-1L << MACHINE_BIT);
private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);
// 每一部分向左的位移
private final static long MACHINE_LEFT = SEQUENCE_BIT;
private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
private final static long TIMESTAMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;
private long datacenterId; //数据中心
private long machineId; //机器标识
private long sequence = 0L; //序列号
private long lastStamp = -1L; //上一次时间戳
public SnowFlake(long datacenterId, long machineId) {
if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
}
if (machineId > MAX_MACHINE_NUM || machineId < 0) {
throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
}
this.datacenterId = datacenterId;
this.machineId = machineId;
}
public synchronized long nextId() {
long curryStamp = timeGen();
if (curryStamp < lastStamp) {
throw new RuntimeException("Clock moved backwards. Refusing to generate id");
}
if (curryStamp == lastStamp) {
// if条件里表示当前调用和上一次调用落在了相同毫秒内,只能通过第三部分,序列号自增来判断为唯一,所以+1.
sequence = (sequence + 1) & MAX_SEQUENCE;
//同一毫秒的序列数已经达到最大,只能等待下一个毫秒
if (sequence == 0L) {
curryStamp = getNextMill();
}
} else {
// 不同毫秒内,序列号置为0
// 执行到这个分支的前提是currTimestamp > lastTimestamp,说明本次调用跟上次调用对比,已经不再同一个毫秒内了,这个时候序号可以重新回置0了。
sequence = 0L;
}
lastStamp = curryStamp;
// 就是用相对毫秒数、机器ID和自增序号拼接
return (curryStamp - START_STAMP) << TIMESTAMP_LEFT // 时间戳部分
| datacenterId << DATACENTER_LEFT // 数据中心部分
| machineId << MACHINE_LEFT // 机器标识部分
| sequence; // 序列号部分
}
private long getNextMill() {
long mill = timeGen();
while (mill <= lastStamp) {
mill = timeGen();
}
return mill;
}
private long timeGen() {
return System.currentTimeMillis();
}
public static void main(String[] args) {
SnowFlake snowFlake = new SnowFlake(2, 3);
for (int i = 0; i < 10; i++) {
System.out.println(snowFlake.nextId());
}
}
}
有一个缺点,在高并发场景下如果不稍加处理还是会出现重复的id。
二、百度UidGenerator| 文章目录 |
|---|
| GitHub - baidu/uid-generator: UniqueID generator |
| 生成全局唯一ID之UidGenerator_三刻的博客-CSDN博客_uidgenerator |
基于雪花算法进行二次改进,克服了雪花算法的并发限制数问题。
三、美团Leaf| 文章目录 |
|---|
| Leaf/README_CN.md at master · Meituan-Dianping/Leaf · GitHub |
| Leaf——美团点评分布式ID生成系统 - 美团技术团队 (meituan.com) |
我测试了一下单机环境的毫秒级并发还是很难吃的住这个唯一性,但是非高并发场景下的唯一id是没什么问题。
import java.io.Serializable; import java.nio.ByteBuffer; import java.security.SecureRandom; import java.util.Date; import java.util.concurrent.atomic.AtomicInteger; public final class NewMongoUidGeneratorUtil implements Comparable, Serializable { private static final long serialVersionUID = 3670079982654483072L; private static final int OBJECT_ID_LENGTH = 12; private static final int LOW_ORDER_THREE_BYTES = 0x00ffffff; // Use primitives to represent the 5-byte random value. private static final int RANDOM_VALUE1; private static final short RANDOM_VALUE2; private static final AtomicInteger NEXT_COUNTER = new AtomicInteger(new SecureRandom().nextInt()); private static final char[] HEX_CHARS = new char[]{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; private final int timestamp; private final int counter; private final int randomValue1; private final short randomValue2; public static NewMongoUidGeneratorUtil get() { return new NewMongoUidGeneratorUtil(); } public static boolean isValid(final String hexString) { if (hexString == null) { throw new IllegalArgumentException(); } int len = hexString.length(); if (len != 24) { return false; } for (int i = 0; i < len; i++) { char c = hexString.charAt(i); if (c >= '0' && c <= '9') { continue; } if (c >= 'a' && c <= 'f') { continue; } if (c >= 'A' && c <= 'F') { continue; } return false; } return true; } public NewMongoUidGeneratorUtil() { this(new Date()); } public NewMongoUidGeneratorUtil(final Date date) { this(dateToTimestampSeconds(date), NEXT_COUNTER.getAndIncrement() & LOW_ORDER_THREE_BYTES, false); } public NewMongoUidGeneratorUtil(final Date date, final int counter) { this(dateToTimestampSeconds(date), counter, true); } @Deprecated public NewMongoUidGeneratorUtil(final Date date, final int machineIdentifier, final short processIdentifier, final int counter) { this(dateToTimestampSeconds(date), machineIdentifier, processIdentifier, counter); } @Deprecated public NewMongoUidGeneratorUtil(final int timestamp, final int machineIdentifier, final short processIdentifier, final int counter) { this(timestamp, machineIdentifier, processIdentifier, counter, true); } public NewMongoUidGeneratorUtil(final int timestamp, final int counter) { this(timestamp, counter, true); } private NewMongoUidGeneratorUtil(final int timestamp, final int counter, final boolean checkCounter) { this(timestamp, RANDOM_VALUE1, RANDOM_VALUE2, counter, checkCounter); } private NewMongoUidGeneratorUtil(final int timestamp, final int randomValue1, final short randomValue2, final int counter, final boolean checkCounter) { if ((randomValue1 & 0xff000000) != 0) { throw new IllegalArgumentException("The machine identifier must be between 0 and 16777215 (it must fit in three bytes)."); } if (checkCounter && ((counter & 0xff000000) != 0)) { throw new IllegalArgumentException("The counter must be between 0 and 16777215 (it must fit in three bytes)."); } this.timestamp = timestamp; this.counter = counter & LOW_ORDER_THREE_BYTES; this.randomValue1 = randomValue1; this.randomValue2 = randomValue2; } public NewMongoUidGeneratorUtil(final String hexString) { this(parseHexString(hexString)); } public NewMongoUidGeneratorUtil(final byte[] bytes) { this(ByteBuffer.wrap(bytes)); } NewMongoUidGeneratorUtil(final int timestamp, final int machineAndProcessIdentifier, final int counter) { this(legacyToBytes(timestamp, machineAndProcessIdentifier, counter)); } public NewMongoUidGeneratorUtil(final ByteBuffer buffer) { if (buffer == null) { throw new IllegalArgumentException("buffer can not be null"); } if (!(buffer.remaining() >= OBJECT_ID_LENGTH)) { throw new IllegalArgumentException("state should be: buffer.remaining() >=12"); } // Note: Cannot use ByteBuffer.getInt because it depends on tbe buffer's byte order // and ObjectId's are always in big-endian order. timestamp = makeInt(buffer.get(), buffer.get(), buffer.get(), buffer.get()); randomValue1 = makeInt((byte) 0, buffer.get(), buffer.get(), buffer.get()); randomValue2 = makeShort(buffer.get(), buffer.get()); counter = makeInt((byte) 0, buffer.get(), buffer.get(), buffer.get()); } private static byte[] legacyToBytes(final int timestamp, final int machineAndProcessIdentifier, final int counter) { byte[] bytes = new byte[OBJECT_ID_LENGTH]; bytes[0] = int3(timestamp); bytes[1] = int2(timestamp); bytes[2] = int1(timestamp); bytes[3] = int0(timestamp); bytes[4] = int3(machineAndProcessIdentifier); bytes[5] = int2(machineAndProcessIdentifier); bytes[6] = int1(machineAndProcessIdentifier); bytes[7] = int0(machineAndProcessIdentifier); bytes[8] = int3(counter); bytes[9] = int2(counter); bytes[10] = int1(counter); bytes[11] = int0(counter); return bytes; } public byte[] toByteArray() { ByteBuffer buffer = ByteBuffer.allocate(OBJECT_ID_LENGTH); putToByteBuffer(buffer); return buffer.array(); // using .allocate ensures there is a backing array that can be returned } public void putToByteBuffer(final ByteBuffer buffer) { if (buffer == null) { throw new IllegalArgumentException("buffer can not be null"); } if (!(buffer.remaining() >= OBJECT_ID_LENGTH)) { throw new IllegalArgumentException("state should be: buffer.remaining() >=12"); } buffer.put(int3(timestamp)); buffer.put(int2(timestamp)); buffer.put(int1(timestamp)); buffer.put(int0(timestamp)); buffer.put(int2(randomValue1)); buffer.put(int1(randomValue1)); buffer.put(int0(randomValue1)); buffer.put(short1(randomValue2)); buffer.put(short0(randomValue2)); buffer.put(int2(counter)); buffer.put(int1(counter)); buffer.put(int0(counter)); } public int getTimestamp() { return timestamp; } public Date getDate() { return new Date((timestamp & 0xFFFFFFFFL) * 1000L); } public String toHexString() { char[] chars = new char[OBJECT_ID_LENGTH * 2]; int i = 0; for (byte b : toByteArray()) { chars[i++] = HEX_CHARS[b >> 4 & 0xF]; chars[i++] = HEX_CHARS[b & 0xF]; } return new String(chars); } @Override public boolean equals(final Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } NewMongoUidGeneratorUtil objectId = (NewMongoUidGeneratorUtil) o; if (counter != objectId.counter) { return false; } if (timestamp != objectId.timestamp) { return false; } if (randomValue1 != objectId.randomValue1) { return false; } if (randomValue2 != objectId.randomValue2) { return false; } return true; } @Override public int hashCode() { int result = timestamp; result = 31 * result + counter; result = 31 * result + randomValue1; result = 31 * result + randomValue2; return result; } @Override public int compareTo(final NewMongoUidGeneratorUtil other) { if (other == null) { throw new NullPointerException(); } byte[] byteArray = toByteArray(); byte[] otherByteArray = other.toByteArray(); for (int i = 0; i < OBJECT_ID_LENGTH; i++) { if (byteArray[i] != otherByteArray[i]) { return ((byteArray[i] & 0xff) < (otherByteArray[i] & 0xff)) ? -1 : 1; } } return 0; } @Override public String toString() { return toHexString(); } // Deprecated methods @Deprecated public static NewMongoUidGeneratorUtil createFromLegacyFormat(final int time, final int machine, final int inc) { return new NewMongoUidGeneratorUtil(time, machine, inc); } @Deprecated public static int getCurrentCounter() { return NEXT_COUNTER.get() & LOW_ORDER_THREE_BYTES; } @Deprecated public static int getGeneratedMachineIdentifier() { return RANDOM_VALUE1; } @Deprecated public static int getGeneratedProcessIdentifier() { return RANDOM_VALUE2; } @Deprecated public int getMachineIdentifier() { return randomValue1; } @Deprecated public short getProcessIdentifier() { return randomValue2; } @Deprecated public int getCounter() { return counter; } @Deprecated public int getTimeSecond() { return timestamp; } @Deprecated public long getTime() { return (timestamp & 0xFFFFFFFFL) * 1000L; } @Deprecated public String toStringMongod() { return toHexString(); } static { try { SecureRandom secureRandom = new SecureRandom(); RANDOM_VALUE1 = secureRandom.nextInt(0x01000000); RANDOM_VALUE2 = (short) secureRandom.nextInt(0x00008000); } catch (Exception e) { throw new RuntimeException(e); } } private static byte[] parseHexString(final String s) { if (!isValid(s)) { throw new IllegalArgumentException("invalid hexadecimal representation of an ObjectId: [" + s + "]"); } byte[] b = new byte[OBJECT_ID_LENGTH]; for (int i = 0; i < b.length; i++) { b[i] = (byte) Integer.parseInt(s.substring(i * 2, i * 2 + 2), 16); } return b; } private static int dateToTimestampSeconds(final Date time) { return (int) (time.getTime() / 1000); } // Big-Endian helpers, in this class because all other BSON numbers are little-endian private static int makeInt(final byte b3, final byte b2, final byte b1, final byte b0) { // CHECKSTYLE:OFF return (((b3) << 24) | ((b2 & 0xff) << 16) | ((b1 & 0xff) << 8) | ((b0 & 0xff))); // CHECKSTYLE:ON } private static short makeShort(final byte b1, final byte b0) { // CHECKSTYLE:OFF return (short) (((b1 & 0xff) << 8) | ((b0 & 0xff))); // CHECKSTYLE:ON } private static byte int3(final int x) { return (byte) (x >> 24); } private static byte int2(final int x) { return (byte) (x >> 16); } private static byte int1(final int x) { return (byte) (x >> 8); } private static byte int0(final int x) { return (byte) (x); } private static byte short1(final short x) { return (byte) (x >> 8); } private static byte short0(final short x) { return (byte) (x); } public static void main(String[] args) { String string = new NewMongoUidGeneratorUtil().toHexString(); } }
原理回头再细细研究。
总结提示:这里对文章进行总结:
例如:以上就是今天要讲的内容。



