Introduction to the

Let’s take a look at the official introduction:

Snowflakes algorithm

Snowflake is an algorithm that generates distributed globally unique IDs called Snowflake IDs or snowflakes. This algorithm was created by Twitter and used for the ID of the tweet. Other companies, such as Discord and Instagram, have adopted the revised version. A Snowflake ID has 64 bits. The first 41 bits are time stamps, representing the number of milliseconds since the selected period. The next 10 digits represent computer ids to prevent collisions. The remaining 12 digits represent the serial numbers that generate ids on each machine, which allows multiple Snowflake ids to be created in the same millisecond. SnowflakeID is generated based on time, so it can be sorted by time. Furthermore, the generation time of an ID can be inferred from itself, and vice versa. This feature can be used to filter ids and objects associated with them by time.

The first bit

This bit is not used mainly to preserve the increment of ID. If the highest bit is used, int64 is represented as a negative number. In Java, the highest bit of long type is the sign bit, the positive number is 0, and the negative number is 1. Generally, the generated ID is a positive integer, so the highest bit is 0

A 41-bit timestamp

The current timestamp is not stored in the implementation, but the difference of the timestamp (the current time minus the fixed start time), so that the generated ID starts with a smaller value;

41 bits can represent as many as 2^ 41-1 milliseconds, or 69 years in adult years.

(1L << 41)/(1000L 60 60 24 365) = (2199023255552/31536000000) ≈ 69.73 years.

10-digit work machine ID

The Twitter implementation uses the first five bits as data center identifiers and the last five bits as machine identifiers to deploy 1024 (2^10) nodes. This means a maximum of 2 ^ 5 machine rooms (32 machine rooms), each machine room can represent 2 ^ 5 machines (32 machines). Specific partitions can be defined according to your needs. For example, four bits are used to identify the service number and the other six bits are used to identify the machine number.

12-digit serial number

A node can generate 4096 (2^12) ids in the same millisecond. That is, the maximum number of ids generated by a machine in the same millisecond is 4096.

In simple terms, one of your services assumes that you want to generate a globally unique ID, so you can send a request to the system that has the SnowFlake algorithm deployed, and the SnowFlake algorithm system generates the unique ID. The SnowFlake algorithm must first know the machine number it is on (let’s say 10 bits are all the work machine ids) and then once the SnowFlake algorithm receives the request, First, a 64-bit long ID is generated using a binary operation. The first of the 64 bits is meaningless. It then takes 41 bits with the current timestamp (in milliseconds), followed by 10 bits to set the machine ID. Finally, determine the number of requests in this millisecond on the machine in the current machine room, and add a sequence number to the request to generate the ID as the last 12 bits.

Advantages:

  • Theoretically, Snowflake’s QPS is about 409.6W /s (1000 * 2^12), which ensures that any machine in any IDC will generate a different ID in any milliseconds.

disadvantages

  • If the clock on the machine is dialed back, the number may be repeated or the service may be unavailable.

UidGenerator

UidGenerator is a Unique ID generator implemented in Java based on the Snowflake algorithm. The UidGenerator works in application projects as a component and supports user-defined workerId bits and initialization policies. Therefore, it is suitable for scenarios such as automatic restart and drift of instances in docker virtualization environments. In terms of implementation, UidGenerator solves the natural concurrency limitation of sequence by borrowing future time. RingBuffer is used to cache the generated UID, parallel the production and consumption of UID, and complement the CacheLine at the same time, avoiding the hardware-level “pseudo-sharing” problem caused by RingBuffer. The final single-machine QPS can reach 6 million.

The implementation of UidGenerator is not quite the same as the original SnowFlake algorithm, but the following parameters can be customized via Spring:

  • Sign (1bit) specifies the 1bit identifier. That is, the generated UID is a positive number.
  • Delta seconds (28 bits) Indicates the current time, expressed in seconds. A maximum of 8.7 years is supported
  • Worker id (22 bits) machine id, which supports a maximum of 420w machine starts. The built-in implementation is allocated by the database at startup, the default allocation policy is deprecated after use, and the subsequent reuse policy can be provided.
  • Sequence (13 bits) Indicates the sequence of concurrent requests per second. The 13-bit sequence supports 8192 concurrent requests per second.

RingBuffer an array of rings, each element of which becomes a slot. RingBuffer capacity. The default value is the maximum value in the Snowflake algorithm and is 2^N. You can configure boostPower to expand capacity to improve RingBuffer read/write throughput.

The Tail Cursor is used to read and write slots on a circular array:

  • The Tail pointer

Represents the maximum sequence number (starting from 0 and increasing continuously) of a Producer’s production. Tail cannot exceed Cursor, that is, producers cannot overwrite unconsumed slots. When Tail has caught up with curosr, you can specify a PutRejectPolicy with rejectedPutBufferHandler

  • The Cursor pointer

Represents the minimum sequence number that Consumer consumes (the sequence is the same as Producer sequence). The Cursor cannot exceed Tail, that is, it cannot consume unproduced slots. When the Cursor is catch up with the tail, through rejectedTakeBufferHandler TakeRejectPolicy specified at this time

The CachedUidGenerator uses a double RingBuffer, uID-ringBuffer for Uid storage and flag-ringBuffer for Uid status (fillable and consumable)

Because array elements are allocated continuously in memory, the CPU cache can be maximized to improve performance. But at the same time, FalseSharing problem will be brought, so the method of CacheLine complement is adopted in Tail, Cursor pointer and flag-ring buffer.

On the more false sharing of knowledge, can refer to: www.cnblogs.com/cyfonly/p/5…

In summary, fake sharing can cause performance problems, which can improve performance and prevent data inconsistencies if not solved.

RingBuffer filling time

  • Initialize prefill

When RingBuffer is initialized, the entire RingBuffer is prefilled.

  • Instant filling

When taking a slot, check the number of available slots immediately. If the number is smaller than the preset threshold, complete the available slots. Thresholds can be configured using paddingFactor

  • Cycle filling

Through the Schedule thread, complete the vacant slots periodically. ScheduleInterval can be configured to apply the scheduling function and specify Schedule intervals

Source code analysis

General situation of

The entire project totals 2386 lines of Java code

The internal class dependency structure looks like this:

So RingBuffer is the core class.

The directory structure

Com └ ─ ─ baidu └ ─ ─ FSG └ ─ ─ uid ├ ─ ─ BitsAllocator. Java - Bit distributor (C) ├ ─ ─ UidGenerator. Java - uid generated interface (I) ├ ─ ─ buffer │ ├ ─ ─ BufferPaddingExecutor. Java - filling RingBuffer actuators (C) │ ├ ─ ─ BufferedUidProvider. Java - UID provider (C) in the RingBuffer │ ├ ─ ─ RejectedPutBufferHandler. Java - refused to Put to the RingBuffer processor (C) │ ├ ─ ─ RejectedTakeBufferHandler. Java - from RingBuffer refused to (C) Take processor │ └ ─ ─ RingBuffer. Java - contains two annular array (C) ├ ─ ─ exception │ └ ─ ─ UidGenerateException. Java runtime exception - ├ ─ ─ impl │ ├ ─ ─ │ ├─ ├─ ├─ cacheDuidGenerator.java - UID Generator for RingBuffer (C) ├─ │ ├─ Heavy Metal Metal Metal Metal Metal Metal Metal Metal Metal Metal Metal Metal Metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal metal NetUtils. Java │ ├ ─ ─ PaddedAtomicLong. Java │ └ ─ ─ ValuedEnum. Java └ ─ ─ the worker ├ ─ ─ DisposableWorkerIdAssigner. Java - disposable WorkerId distributor (C) ├ ─ ─ WorkerIdAssigner. Java - WorkerId distributor (I) ├ ─ ─ WorkerNodeType. Java - work node type (E) ├ ─ ─ dao │ └ ─ ─ WorkerNodeDAO. Java - MyBatis Mapper └ ─ ─ the entity └ ─ ─ WorkerNodeEntity. Java - MyBatis entityCopy the code

DefaultUidGenerator

The UidGenerator is served as a Spring component in the application. DefaultUidGenerator provides the simplest Snowflake generation mode, does not use any caching to pre-store UUIds, and computes them as and when they need to be generated.

So let’s combine the source code to string together the simplest default schema generation process.

First, introduce the DefaultUidGenerator configuration

<! -- DefaultUidGenerator -->
<bean id="defaultUidGenerator" class="com.baidu.fsg.uid.impl.DefaultUidGenerator" lazy-init="false">
    <property name="workerIdAssigner" ref="disposableWorkerIdAssigner"/>

    <! -- The current number of bits (28 bits) -->
    <property name="timeBits" value="29"/>
    <! -- Machine ID bit, set to 21 bits -->
    <property name="workerBits" value="21"/>
    <! Number of concurrent sequences per second (13 bits)
    <property name="seqBits" value="13"/>
    <! -- Incremental value relative to the time base point "2016-09-20", in seconds -->
    <! -- Used to calculate the difference of the timestamp (the current time minus the fixed start time), so that the generated ID starts with a lower value -->
    <property name="epochStr" value="2016-09-20"/>
</bean>
 
<! WorkerIdAssigner (assigner);
<bean id="disposableWorkerIdAssigner" class="com.baidu.fsg.uid.worker.DisposableWorkerIdAssigner" />

Copy the code

I have commented out the configuration in the configuration file, focusing on two properties:

epochStr

Is given a string in the past time as a time base, such as “2016-09-20”, to calculate the difference of the timestamp (the current time minus a fixed start time), so that the resulting ID starts at a lower value. This can be seen in the following two pieces of source code:

public void setEpochStr(String epochStr) {
    if (StringUtils.isNotBlank(epochStr)) {
        this.epochStr = epochStr;
        this.epochSeconds = TimeUnit.MILLISECONDS.toSeconds(DateUtils.parseByDayPattern(epochStr).getTime()); }}/** * Get current second */
private long getCurrentSecond(a) {
    long currentSecond = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
    if (currentSecond - epochSeconds > bitsAllocator.getMaxDeltaSeconds()) {
        throw new UidGenerateException("Timestamp bits is exhausted. Refusing UID generate. Now: " + currentSecond);
    }

    return currentSecond;
}

Copy the code

disposableWorkerIdAssigner

The Worker ID allocator is used to assign a unique ID to each working machine. Currently, it is disposable. When initializing the Bean, it will automatically insert a startup information about the service into MySQL. Use this ID as the work machine ID and soften it into UID generation.

@Transactional
public long assignWorkerId(a) {
    // build worker node entity
    WorkerNodeEntity workerNodeEntity = buildWorkerNode();

    // add worker node for new (ignore the same IP + PORT)
    workerNodeDAO.addWorkerNode(workerNodeEntity);
    LOGGER.info("Add worker node:" + workerNodeEntity);

    return workerNodeEntity.getId();
}

Copy the code

BuildWorkerNode () is compatible with the Docker service to get information about the startup service. Note, however, that whether docker or K8s is used, the associated environment variable env needs to be added to the configuration file so that the application can get it.

 /** Environment param keys /
    private static final String ENV_KEY_HOST = "JPAAS_HOST";
    private static final String ENV_KEY_PORT = "JPAAS_HTTP_PORT";
Copy the code

Core method

With that out of the way, let’s look at the core method of defaultUidGenerator generating ids (note that this method is synchronous)

 protected synchronized long nextId(a) {

    long currentSecond = getCurrentSecond();

    // Move the clock backwards, reject id generation (fix clock rollback problem)
    if (currentSecond < lastSecond) {
        long refusedSeconds = lastSecond - currentSecond;
        throw new UidGenerateException("Clock moved backwards. Refusing for %d seconds", refusedSeconds);
    }

    // If the sequence is within the same second, add sequence
    if (currentSecond == lastSecond) {
        sequence = (sequence + 1) & bitsAllocator.getMaxSequence();
        // If the maximum value is exceeded, the generation needs to wait until the next second
        if (sequence == 0) {
            currentSecond = getNextSecond(lastSecond);
        }

    // In different seconds, sequence starts from 0 again
    } else {
        sequence = 0L;
    }
    lastSecond = currentSecond;
    // Allocate bits for UID
    return bitsAllocator.allocate(currentSecond - epochSeconds, workerId, sequence);
}

Copy the code

As you can see, most of the code is used to handle exceptions, such as the clock rollback problem, where it is easier to simply throw an exception.

The last line is the actual allocation of ids based on the passed or calculated parameters, and the final long ID is obtained by binary shifting and or operations.

 public long allocate(long deltaSeconds, long workerId, long sequence) {

    return (deltaSeconds << timestampShift) | (workerId << workerIdShift) | sequence;
}
Copy the code

CachedUidGenerator

CachedUidGenerator is a generator that uses RingBuffer to pre-cache uuIds, filling the entire RingBuffer when initialized, The RingBuffer is asynchronously filled again (default: 50%) when it is detected to be less than the specified filling threshold during take(), and a timer can be started to periodically detect the threshold and fill in time.

RingBuffer

RingBuffer is a pre-cached UID generator. Let’s take a look at its member variables:

    /** Constant configuration */
    private static final int START_POINT = -1;
    private static final long CAN_PUT_FLAG = 0L;
    private static final long CAN_TAKE_FLAG = 1L;
    public static final int DEFAULT_PADDING_PERCENT = 50;

    /** The slot size of RingBuffer, each slot holds a UID */
    private final int bufferSize;
    private final long indexMask;
    /** hold the UID array */
    private final long[] slots;
    /** An array of UID states (readable, writable, fillable, consumable) */
    private final PaddedAtomicLong[] flags;

    /** Tail: the last position sequence to produce */
    private final AtomicLong tail = new PaddedAtomicLong(START_POINT);
    /** Cursor: sequence of current positions to consume */
    private final AtomicLong cursor = new PaddedAtomicLong(START_POINT);

    /** Triggers the buffer fill threshold */
    private final int paddingThreshold; 
    
    /** The rejection policy for the buffer is to print logs */
    private RejectedPutBufferHandler rejectedPutHandler = this::discardPutBuffer;
    /** The rejection method of the buffer is to throw an exception and print a log */
    private RejectedTakeBufferHandler rejectedTakeHandler = this::exceptionRejectedTakeBuffer; 
    
    /** Fills the buffer with executor */
    private BufferPaddingExecutor bufferPaddingExecutor;
Copy the code

As you can see, the RingBuffer contains two circular arrays, one for the UID and one for the state of the UID. Both arrays have the same size, namely bufferSize.

Slots holds the long array for UID and flags holds the PaddedAtomicLong array for read and write flags. What’s with PaddedAtomicLong? The concept of fake sharing has been mentioned above, and here is to solve this problem. If you do not understand fake sharing, you can see the reference link above to understand it.

In short, because slots is essentially a read-less variable, using native types is more profitable. Flags, on the other hand, writes frequently and completes manually to avoid false sharing.

RingBuffer constructor


   /** * constructor with buffer size, paddingFactor defaults to {@value #DEFAULT_PADDING_PERCENT}
     * 
     * @paramBufferSize must be a positive number and a power of 2 */
    public RingBuffer(int bufferSize) {
        this(bufferSize, DEFAULT_PADDING_PERCENT);
    }
    
    /** * constructor ** with buffer size and fill factor@paramBufferSize must be a positive number and a power of 2 *@paramPercentage in paddingFactor (0-100). When the number of uIds remaining available reaches a threshold, populating the buffer * Sample is triggered: PaddingFactor =20, bufferSize=1000 -> threshold=1000 * 20/100, * When tail-cursor<threshold the buffer will be filled */
    public RingBuffer(int bufferSize, int paddingFactor) {
        // check buffer size is positive & a power of 2; padding factor in (0, 100)
        Assert.isTrue(bufferSize > 0L."RingBuffer size must be positive");
        Assert.isTrue(Integer.bitCount(bufferSize) == 1."RingBuffer size must be a power of 2");
        Assert.isTrue(paddingFactor > 0 && paddingFactor < 100."RingBuffer size must be positive");

        this.bufferSize = bufferSize;
        this.indexMask = bufferSize - 1;
        this.slots = new long[bufferSize];
        this.flags = initFlags(bufferSize);
        
        this.paddingThreshold = bufferSize * paddingFactor / 100;
    }

Copy the code

BufferSize Default value. If the sequence is 13 bits, the default maximum value is 8192, and capacity expansion is supported.

The threshold that triggers filling the buffer is also configurable,

<! -- RingBuffer Size Capacity expansion parameter, which improves the throughput of UID generation. --> 
<! BufferSize =8192 << 3 = 65536 -->
<property name="boostPower" value="3"></property>

<! -- Specifies when to fill the RingBuffer with UID. The value is percentage (0, 100), default is 50 -->
<! BufferSize =1024, paddingFactor=50 -> threshold=1024 * 50/100 = 512.
<! -- RingBuffer will be filled automatically when the number of UID available on the ring < 512
<property name="paddingFactor" value="50"></property>

Copy the code

RingBuffer filling and obtaining

The fill and fetch operations of RingBuffer are thread-safe, but the performance of the fill and fetch operations is affected by the size of RingBuffer. Let’s take a look at the put operations:

 public synchronized boolean put(long uid) {
        long currentTail = tail.get();
        long currentCursor = cursor.get();

        // When tail catches up with cursor, RingBuffer is full and cannot be placed any more
        // Tail cannot exceed Cursor, that is, producers cannot overwrite unconsumed slots. When Tail has caught up with curosr, you can specify a PutRejectPolicy with rejectedPutBufferHandler
        long distance = currentTail - (currentCursor == START_POINT ? 0 : currentCursor);
        if (distance == bufferSize - 1) {
            rejectedPutHandler.rejectPutBuffer(this, uid);
            return false;
        }

        Check whether flag is CAN_PUT_FLAG. For the first put, currentTail is -1
        int nextTailIndex = calSlotIndex(currentTail + 1);
        if(flags[nextTailIndex].get() ! = CAN_PUT_FLAG) { rejectedPutHandler.rejectPutBuffer(this, uid);
            return false;
        }
        // 2. put UID in the next slot
        slots[nextTailIndex] = uid;
        // 3. update next slot' flag to CAN_TAKE_FLAG
        flags[nextTailIndex].set(CAN_TAKE_FLAG);
        // 4. Publish tail with sequence increase by one
        tail.incrementAndGet();

        
        // The atomicity of the above operations is guaranteed by "synchronized". In other words,
        // Take cannot consume the UID we just put until tail moves (tail.incrementandGet ())
        return true;
    }
Copy the code

Note that the put method is synchronized. Take a look at the take method:

Reading the UID is a lockless operation. Before obtaining the UID, also check if the padding threshold, in another thread will trigger the padding buffer operation, if there is no more available UID can obtain, the application of the specified RejectedTakeBufferHandler

 public long take(a) {
    // spin get next available cursor
    long currentCursor = cursor.get();
    // Cursor is initialized to -1. Now cursor equals tail, so nextCursor is initialized to -1
    long nextCursor = cursor.updateAndGet(old -> old == tail.get() ? old : old + 1);

    // check for safety consideration, it never occurs
    NextCursor == currentCursor when all UID runs out
    Assert.isTrue(nextCursor >= currentCursor, "Curosr can't move back");

    // If the threshold is reached, the fill is triggered in asynchronous mode
    long currentTail = tail.get();
    if (currentTail - nextCursor < paddingThreshold) {
        LOGGER.info("Reach the padding threshold:{}. tail:{}, cursor:{}, rest:{}", paddingThreshold, currentTail,
                nextCursor, currentTail - nextCursor);
        bufferPaddingExecutor.asyncPadding();
    }

    // cursor catches up with tail, meaning there are no more UID available to retrieve
    if (nextCursor == currentCursor) {
        rejectedTakeHandler.rejectTakeBuffer(this);
    }

    // 1. check next slot flag is CAN_TAKE_FLAG
    int nextCursorIndex = calSlotIndex(nextCursor);
    // This position must be able to TAKE
    Assert.isTrue(flags[nextCursorIndex].get() == CAN_TAKE_FLAG, "Curosr not in can take status");

    // 2. get UID from next slot
    // 3. set next slot flag as CAN_PUT_FLAG.
    long uid = slots[nextCursorIndex];
    // Indicate that the position in the flags array is reusable
    flags[nextCursorIndex].set(CAN_PUT_FLAG);

    // note: steps 2,3 are not interchangeable. If we set the flag before getting the slot's value, the producer might overwrite the slot with a new UID, which might cause the consumer to get the UID twice in a ring
    return uid;
 }
Copy the code

BufferPaddingExecutor

By default, slots is asynchronously populated when the slots consumption is greater than 50%. This padding is performed by BufferPaddingExecutor. Let’s look at the main code for this executor in a moment.

 /** * Padding buffer fill the slots until to catch the cursor * this method is called by both instant and periodic Padding */
public void paddingBuffer(a) {
    LOGGER.info("Ready to padding buffer lastSecond:{}. {}", lastSecond.get(), ringBuffer);

    // is still running
    // This represents the padding executor executing, not the RingBuffer executing. Avoid simultaneous expansion of multiple threads.
    if(! running.compareAndSet(false.true)) {
        LOGGER.info("Padding buffer is still running. {}", ringBuffer);
        return;
    }

    // fill the rest slots until to catch the cursor
    boolean isFullRingBuffer = false;
    while(! isFullRingBuffer) {// Fill all the UID in the specified SECOND until it is full
        List<Long> uidList = uidProvider.provide(lastSecond.incrementAndGet());
        for(Long uid : uidList) { isFullRingBuffer = ! ringBuffer.put(uid);if (isFullRingBuffer) {
                break; }}}// not running now
    running.compareAndSet(true.false);
    LOGGER.info("End to padding buffer lastSecond:{}. {}", lastSecond.get(), ringBuffer);
}
Copy the code
  • When the thread pool dispatches multiple threads to perform the filling task, the thread that successfully snatches the running state will actually fill the RingBuffer until it is full, and the other thread that failed will return directly.

  • This class also provides the periodic fill function. The function takes effect if the switch is set. Periodic fill is not enabled by default

  • RIngBuffer can be filled at three times: initialization of RIngBuffer at CachedUidGenerator, detection of a threshold at RIngBuffer#take(), and periodic filling (if turned on)

Use the UID generator for RingBuffer

Finally, take a look at the code that generates UID using CachedUidGenerator, which inherits DefaultUidGenerator and implements the UidGenerator interface.

This class is injected into each component in the application as a Spring Bean and is used to initialize RingBuffer and BufferPaddingExecutor. The most important method is the provider of the BufferedUidProvider, the nextIdsForOneSecond(Long) method in the lambda expression.

 /**
     * Get the UIDs in the same specified second under the max sequence
     * 
     * @param currentSecond
     * @return UID list, size of {@link BitsAllocator#getMaxSequence()} + 1
     */
    protected List<Long> nextIdsForOneSecond(long currentSecond) {
        // Initialize result list size of (max sequence + 1)
        int listSize = (int) bitsAllocator.getMaxSequence() + 1;
        List<Long> uidList = new ArrayList<>(listSize);

        // Allocate the first sequence of the second, the others can be calculated with the offset
        long firstSeqUid = bitsAllocator.allocate(currentSecond - epochSeconds, workerId, 0L);
        for (int offset = 0; offset < listSize; offset++) {
            uidList.add(firstSeqUid + offset);
        }

        return uidList;
    }
Copy the code

The ID is obtained by delegating the Take () method of RingBuffer

    @Override
    public long getUID(a) {
        try {
            return ringBuffer.take();
        } catch (Exception e) {
            LOGGER.error("Generate unique id exception. ", e);
            throw newUidGenerateException(e); }}Copy the code

Here is a sequence of the process of obtaining the ID through the sequence diagram.

Bits of arithmetic code

Check if it’s a power of two

Using the bitCount function, the principle is to calculate the number of 1’s in the binary value of the int passed by the parameter, if there is only one, it is a power of 2, otherwise it is not.

Integer.bitCount(bufferSize) == 1
Copy the code

Take the maximum in terms of bits

// initialize max value
this.maxDeltaSeconds = ~(-1L << timestampBits);
this.maxWorkerId = ~(-1L << workerIdBits);
this.maxSequence = ~(-1L << sequenceBits);
Copy the code

The binary of negative 1 is shifted to the left and then reversed, for example by looking at the binary values of 13 and -1:

System.out.println(Long.toBinaryString(-1L));
System.out.println(Long.toBinaryString(-1L << 13 ));
System.out.println(Long.toBinaryString( ~(-1L << 13))); The output1111111111111111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111110000000000000
1111111111111
Copy the code

parse UID

It has some strange similarities with the one above.

 // parse UID
long sequence = (uid << (totalBits - sequenceBits)) >>> (totalBits - sequenceBits);
long workerId = (uid << (timestampBits + signBits)) >>> (totalBits - workerIdBits);
long deltaSeconds = uid >>> (workerIdBits + sequenceBits);
Copy the code

reference

  • www.cnblogs.com/cyfonly/p/5…
  • www.semlinker.com/uuid-snowfl…
  • Programmer. Group/snowflake – a…
  • Github.com/baidu/uid-g…
  • Blog.chriscs.com/2017/08/02/…