package io.kaif.flake; import java.io.IOException; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.google.common.base.Preconditions; import io.kaif.token.Base62; /** * FlakeId is similar to twitter's SnowFlake, which use 64bit long for unique id. * the structure of FlakeId is: * <p> * <code> * |--- 41 bits milli seconds --|-- 12 bits micro seconds --|-- 10 bits node id --| * </code> * <p> * long type is 64 bits but sign bit is not used, so meaningful bits are 63. * <p> * the timestamp part start from 2015-01-01T00:00:00Z. thus FlakeId could not use time before 2015. * <p> * for each node, FlakeId can generate 4096000 number of unique id per second * <p> * jackson serialize format is base62 string */ @JsonSerialize(using = FlakeIdSerializer.class) @JsonDeserialize(using = FlakeIdDeserializer.class) public final class FlakeId implements Comparable<FlakeId> { // 2015-01-01T00:00:00Z private static final long START_OF_SEQUENCE_TIME = 1420070400000L; private static final int SUB_MILLI_BITS = 12; private static final int NODE_ID_BITS = 10; private static final int MIN_NODE_ID = 0; /** * minimum value of FlakeId, start from 2015-01-01T00:00:00Z */ public static final FlakeId MIN = new FlakeId(0, MIN_NODE_ID); /** * maximum value of FlakeId, the epoch time is 2084-09-06T15:47:35.551Z */ public static final FlakeId MAX = new FlakeId(Long.MAX_VALUE); /** * remove fraction of milli seconds in sequence time */ static long millisOfSequenceTime(long sequenceTime) { return sequenceTime >> SUB_MILLI_BITS; } static long sequenceTimeFromEpochMilli(long epochMilli) { return (epochMilli - START_OF_SEQUENCE_TIME) << SUB_MILLI_BITS; } public static FlakeId fromString(String base62) { return new FlakeId(Base62.toBase10(base62)); } public static FlakeId valueOf(long value) { return new FlakeId(value); } /** * create a pseudo FlakeId with min value on specified epochMilli time. created FlakeId can use * to query time range in database */ public static FlakeId startOf(long epochMilli) { long sequenceTime = sequenceTimeFromEpochMilli(epochMilli); return new FlakeId(sequenceTime, MIN_NODE_ID); } /** * create a pseudo FlakeId which max value on specified epochMilli time. created FlakeId can use * to query time range in database */ public static FlakeId endOf(long epochMilli) { long sequenceTime = sequenceTimeFromEpochMilli(epochMilli); long maxSubMilli = (1L << SUB_MILLI_BITS) - 1; int maxNodeId = (1 << NODE_ID_BITS) - 1; return new FlakeId(sequenceTime + maxSubMilli, maxNodeId); } private final long value; FlakeId(long sequenceTime, int nodeId) { this((sequenceTime << NODE_ID_BITS) | nodeId); } private FlakeId(long value) { Preconditions.checkArgument(value >= 0, "invalid flakeId"); this.value = value; } long sequenceTime() { return value >> NODE_ID_BITS; } int nodeId() { return (int) (value % (1L << NODE_ID_BITS)); } /** * extract epoch milli part in this id */ public long epochMilli() { return (value >> (SUB_MILLI_BITS + NODE_ID_BITS)) + START_OF_SEQUENCE_TIME; } /** * long value of FlakeId */ public long value() { return value; } /** * Base62 string */ @Override public String toString() { return Base62.fromBase10(value); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } FlakeId flakeId = (FlakeId) o; return value == flakeId.value; } @Override public int hashCode() { return (int) (value ^ (value >>> 32)); } @Override public int compareTo(FlakeId o) { return Long.compare(value, o.value); } } class FlakeIdSerializer extends JsonSerializer<FlakeId> { @Override public void serialize(FlakeId id, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { jgen.writeString(id.toString()); } } class FlakeIdDeserializer extends JsonDeserializer<FlakeId> { @Override public FlakeId deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { String value = jp.readValueAs(String.class); return FlakeId.fromString(value); } }