前言
各式各样的雪花算法 - Java实现
一、推特SnowFlake
/**
* 雪花算法
* @author nobody
*/
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; //上一次时间戳
/**
* 分布式环境id处理
* @param datacenterId 数据中心id
* @param machineId 机器id
*/
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;
}
/**
* 产生下一个id
* @return
*/
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; // 序列号部分
}
/**
* 比较新旧时间戳
* @return
*/
private long getNextMill() {
long mill = timeGen();
while (mill <= lastStamp) {
mill = timeGen();
}
return mill;
}
/**
* 当前的时间戳
* @return
*/
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) |
四、Mongo - ObjectId
我测试了一下单机环境的毫秒级并发还是很难吃的住这个唯一性,但是非高并发场景下的唯一id是没什么问题。
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;
/**
* MongoDB的ObjectId对象的全局唯一标识符。
* 由12个字节组成,划分如下:
* 1)4字节(8位)timeStamp:UNIX时间戳(精确到秒)。
* 2)3字节(6位)machine:所在主机的唯一标识符(这里使用随机值)。
* 3)2字节(4位)pid:同一台机器不同的进程产生objectid的进程标识符(这里使用随机值) 。
* 4)3字节(6位)increment:由一个随机数开始的计数器生成的自动增加的值,用来确保在同一秒内产生的objectid也不会发现冲突,允许256^3(16777216)条记录的唯一性。
* @mongodb.driver.manual core/object-id ObjectId
* <p>
* 2次生成的结果为:
* 5dbbd598 8dd521 6733 04ca5c
* 5dbbd5a7 5350a9 3c97 3a1692
*/
public final class NewMongoUidGeneratorUtil implements Comparable<NewMongoUidGeneratorUtil>, 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;
/**
* Gets a new object id.
* @return the new id
*/
public static NewMongoUidGeneratorUtil get() {
return new NewMongoUidGeneratorUtil();
}
/**
* Checks if a string could be an {@code ObjectId}.
* @param hexString a potential ObjectId as a String.
* @return whether the string could be an object id
* @throws IllegalArgumentException if hexString is null
*/
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;
}
/**
* Create a new object id.
*/
public NewMongoUidGeneratorUtil() {
this(new Date());
}
/**
* Constructs a new instance using the given date.
* @param date the date
*/
public NewMongoUidGeneratorUtil(final Date date) {
this(dateToTimestampSeconds(date), NEXT_COUNTER.getAndIncrement() & LOW_ORDER_THREE_BYTES, false);
}
/**
* Constructs a new instances using the given date and counter.
* @param date the date
* @param counter the counter
* @throws IllegalArgumentException if the high order byte of counter is not zero
*/
public NewMongoUidGeneratorUtil(final Date date, final int counter) {
this(dateToTimestampSeconds(date), counter, true);
}
/**
* Constructs a new instances using the given date, machine identifier, process identifier, and counter.
* @param date the date
* @param machineIdentifier the machine identifier
* @param processIdentifier the process identifier
* @param counter the counter
* @throws IllegalArgumentException if the high order byte of machineIdentifier or counter is not zero
* @deprecated Use {@link #NewMongoUidGeneratorUtil(Date, int)} instead
*/
@Deprecated
public NewMongoUidGeneratorUtil(final Date date, final int machineIdentifier, final short processIdentifier, final int counter) {
this(dateToTimestampSeconds(date), machineIdentifier, processIdentifier, counter);
}
/**
* Creates an ObjectId using the given time, machine identifier, process identifier, and counter.
* @param timestamp the time in seconds
* @param machineIdentifier the machine identifier
* @param processIdentifier the process identifier
* @param counter the counter
* @throws IllegalArgumentException if the high order byte of machineIdentifier or counter is not zero
* @deprecated Use {@link #NewMongoUidGeneratorUtil(int, int)} instead
*/
@Deprecated
public NewMongoUidGeneratorUtil(final int timestamp, final int machineIdentifier, final short processIdentifier, final int counter) {
this(timestamp, machineIdentifier, processIdentifier, counter, true);
}
/**
* Creates an ObjectId using the given time, machine identifier, process identifier, and counter.
* @param timestamp the time in seconds
* @param counter the counter
* @throws IllegalArgumentException if the high order byte of counter is not zero
*/
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;
}
/**
* Constructs a new instance from a 24-byte hexadecimal string representation.
* @param hexString the string to convert
* @throws IllegalArgumentException if the string is not a valid hex string representation of an ObjectId
*/
public NewMongoUidGeneratorUtil(final String hexString) {
this(parseHexString(hexString));
}
/**
* Constructs a new instance from the given byte array
* @param bytes the byte array
* @throws IllegalArgumentException if array is null or not of length 12
*/
public NewMongoUidGeneratorUtil(final byte[] bytes) {
this(ByteBuffer.wrap(bytes));
}
/**
* Creates an ObjectId
* @param timestamp time in seconds
* @param machineAndProcessIdentifier machine and process identifier
* @param counter incremental value
*/
NewMongoUidGeneratorUtil(final int timestamp, final int machineAndProcessIdentifier, final int counter) {
this(legacyToBytes(timestamp, machineAndProcessIdentifier, counter));
}
/**
* Constructs a new instance from the given ByteBuffer
* @param buffer the ByteBuffer
* @throws IllegalArgumentException if the buffer is null or does not have at least 12 bytes remaining
* @since 3.4
*/
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;
}
/**
* Convert to a byte array. Note that the numbers are stored in big-endian order.
* @return the byte array
*/
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
}
/**
* Convert to bytes and put those bytes to the provided ByteBuffer.
* Note that the numbers are stored in big-endian order.
* @param buffer the ByteBuffer
* @throws IllegalArgumentException if the buffer is null or does not have at least 12 bytes remaining
* @since 3.4
*/
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));
}
/**
* Gets the timestamp (number of seconds since the Unix epoch).
* @return the timestamp
*/
public int getTimestamp() {
return timestamp;
}
/**
* Gets the timestamp as a {@code Date} instance.
* @return the Date
*/
public Date getDate() {
return new Date((timestamp & 0xFFFFFFFFL) * 1000L);
}
/**
* Converts this instance into a 24-byte hexadecimal string representation.
* @return a string representation of the ObjectId in hexadecimal format
*/
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
/**
* <p>Creates an ObjectId using time, machine and inc values. The Java driver used to create all ObjectIds this way, but it does not
* match the <a href="http://docs.mongodb.org/manual/reference/object-id/">ObjectId specification</a>, which requires four values, not
* three. This major release of the Java driver conforms to the specification, but still supports clients that are relying on the
* behavior of the previous major release by providing this explicit factory method that takes three parameters instead of four.</p>
* <p>
* <p>Ordinary users of the driver will not need this method. It's only for those that have written there own BSON decoders.</p>
* <p>
* <p>NOTE: This will not break any application that use ObjectIds. The 12-byte representation will be round-trippable from old to new
* driver releases.</p>
* @param time time in seconds
* @param machine machine ID
* @param inc incremental value
* @return a new {@code ObjectId} created from the given values
* @since 2.12.0
* @deprecated Use {@link #NewMongoUidGeneratorUtil(int, int)} instead
*/
@Deprecated
public static NewMongoUidGeneratorUtil createFromLegacyFormat(final int time, final int machine, final int inc) {
return new NewMongoUidGeneratorUtil(time, machine, inc);
}
/**
* Gets the current value of the auto-incrementing counter.
* @return the current counter value.
* @deprecated
*/
@Deprecated
public static int getCurrentCounter() {
return NEXT_COUNTER.get() & LOW_ORDER_THREE_BYTES;
}
/**
* Gets the generated machine identifier.
* @return an int representing the machine identifier
* @deprecated
*/
@Deprecated
public static int getGeneratedMachineIdentifier() {
return RANDOM_VALUE1;
}
/**
* Gets the generated process identifier.
* @return the process id
* @deprecated
*/
@Deprecated
public static int getGeneratedProcessIdentifier() {
return RANDOM_VALUE2;
}
/**
* Gets the machine identifier.
* @return the machine identifier
* @deprecated
*/
@Deprecated
public int getMachineIdentifier() {
return randomValue1;
}
/**
* Gets the process identifier.
* @return the process identifier
* @deprecated
*/
@Deprecated
public short getProcessIdentifier() {
return randomValue2;
}
/**
* Gets the counter.
* @return the counter
* @deprecated
*/
@Deprecated
public int getCounter() {
return counter;
}
/**
* Gets the time of this ID, in seconds.
* @return the time component of this ID in seconds
* @deprecated Use #getTimestamp instead
*/
@Deprecated
public int getTimeSecond() {
return timestamp;
}
/**
* Gets the time of this instance, in milliseconds.
* @return the time component of this ID in milliseconds
* @deprecated Use #getDate instead
*/
@Deprecated
public long getTime() {
return (timestamp & 0xFFFFFFFFL) * 1000L;
}
/**
* @return a string representation of the ObjectId in hexadecimal format
* @see NewMongoUidGeneratorUtil#toHexString()
* @deprecated use {@link #toHexString()}
*/
@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);
}
/**
* 测试代码
* @param args
*/
public static void main(String[] args) {
String string = new NewMongoUidGeneratorUtil().toHexString();
}
}
原理回头再细细研究。
总结
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容。