What is UidGenerator

UidGenerator is a distributed high-performance unique ID generator developed by Baidu. For more details, please refer to the integration document on the official website

Uid-generator is a unique primary key generator based on Twitter’s open source Snowflake algorithm (it is important that the primary key of a database table be globally unique). Java8 or later is required.

Snowflake algorithm

Snowflake algorithm description: specifies the machine & the same time & a certain concurrent sequence, which is unique. This generates a 64-bit unique ID (long).

The 64-bit of long is divided into 3 parts, timestamp, work machine ID, and serial number. The bit allocation is as follows:

The time unit of the timestamp section is generally in milliseconds, which means that one working machine generates 4096 ids (2 ^ 12) in one millisecond.

UidGenerator algorithm

Unlike the original Snowflake algorithm, UID-Generator supports custom bits for various parts such as timestamps, work machine ids, and serial numbers to be used in different scenarios.

  • Sign (1bit) : indicates the fixed 1bit identifier. That is, the generated UID is a positive number.
  • Delta seconds (28 bits) : indicates the current time, in seconds. A maximum of 8.7 years is supported
  • Worker id (22 bits) : indicates the 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.

The length of these fields can be dynamically adjusted based on application requirements. The total length of these fields is only 64 bits.

Snowflake vs. UidGenerator

Baidu’s worker ID generation strategy is different from that of Meituan. Meituan’s Snowflake mainly determines a unique workID by using locally configured ports and IP addresses. This generation method of Meituan can cause duplicate ports due to manual configuration errors, resulting in the risk of duplicate ids. This generation method of Baidu is newly added every time. It may run out of worker ID after a period of time, and the possibility of manual configuration error is very small.

Source code analysis

DefaultUidGenerator

DefaultUidGenerator generates ids in a way that is basically the usual Snowflake algorithm implementation, with only a few differences, such as being measured in seconds rather than milliseconds. The id of DefaultUidGenerator is generated as follows.

 protected synchronized long nextId(a) {
        long currentSecond = getCurrentSecond();
        // Clock moved backwards, refuse to generate uid
        if (currentSecond < lastSecond) {
            long refusedSeconds = lastSecond - currentSecond;
            throw new UidGenerateException("Clock moved backwards. Refusing for %d seconds", refusedSeconds);
        }
        // At the same second, increase sequence
        if (currentSecond == lastSecond) {
            sequence = (sequence + 1) & bitsAllocator.getMaxSequence();
            // Exceed the max sequence, we wait the next second to generate uid
            if (sequence == 0) {
                currentSecond = getNextSecond(lastSecond);
            }
        // At the different second, sequence restart from zero
        } else {
            sequence = 0L;
        }
        lastSecond = currentSecond;
        // Allocate bits for UID
        return bitsAllocator.allocate(currentSecond - epochSeconds, workerId, sequence);
    }
Copy the code

The nextId method is mainly responsible for ID generation. This implementation is very simple. If the number of milliseconds does not change, add one to the Sequence number and reset the Sequence to 0. The sequence field will always be 0, etc., and data skew will occur.

CachedUidGenerator

CachedUidGenerator Supports cache-generated ids.

  • [Use RingBuffer to cache generated UID, parallel production and consumption of UID]
  • UidGenerator solves the natural concurrency limitation of sequence by borrowing future time.
Basic Implementation Principles

As the name implies, this is a caching ID generation method. When the remaining ID is insufficient, a batch of ids will be generated asynchronously and cached, and the ready-made ID can be returned directly when the subsequent request is made.

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.

Use RingBuffer to cache the generated ID. RingBuffer is an array of rings, with a default size of 8192, that cache the generated ids.

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.

Get the id

An ID is fetched from ringBuffer, which supports concurrent fetching

@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
RingBuffer Indicates the ID generated by the cache

RingBuffer is an array of rings. The default capacity is the maximum number (8192) that a sequence can contain. You can set the size using the boostPower parameter. Several important data structures use RingBuffer to cache UID information.

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

The Tail pointer

Refers to the last available UID position: represents the maximum serial number (starting from 0 and increasing continuously) produced by Producer. 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

Pointing to the next location where the UID is obtained, which must be less than Tail: represents the minimum sequence number that the Consumer can consume (the sequence is the same as the 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

Tail-cursor represents the number of available uids. When the number of available uids is less than a certain threshold, a new set of uids are added to the RingBuffer.

Fill the id
  • RingBuffer filling time

    • When the program starts, RingBuffer is filled with 8192 ids

    • When getUID() is called to obtain the ID, it detects that the number of remaining ids in the RingBuffer is less than 50% of the total number, and fills the RingBuffer to make it cache 8192 ids.

    • Scheduled filling (configurable whether to use and the period of scheduled tasks)

Because delta seconds is partly in seconds, a worker can generate up to 8192 ID books in one second (2 ^ 13). As you can see from the above, the maximum QPS supported is 8192, so cache ids are used to improve throughput.

Why is it called future time?

Because a maximum of 8192 ids are generated per second, when the number of ids obtained in a second is more than 8192, the ids in the RingBuffer are quickly consumed. When filling the RingBuffer, only the future time can be used for the delta seconds part of the generated ID. (Since future time is used to generate the ID, it is said that about 8.7 years will be supported.)

Note: RingBuffer is not RingBuffer in the Disruptor framework, but leverages many of the design ideas of RingBuffer in Disruptor, such as using cache row padding to address pseudo-sharing issues.

Fill the RingBuffer
/** * Padding buffer fill the slots until to catch the cursor */
    public void paddingBuffer(a) {
        LOGGER.info("Ready to padding buffer lastSecond:{}. {}", lastSecond.get(), ringBuffer);

        // is still running
        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) {// Get the generated ID and put it in RingBuffer.
            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

Generate the ID (which is what uidprovider.provide calls in the above code)

/**
     * 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
        // The implementation here is tricky
        // Since the ids generated within 1 second are consecutive, use the first ID to generate the following ids instead of calling the Snowflake algorithm frequently
        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 RingBuffer code

public class RingBuffer {
    private static final Logger LOGGER = LoggerFactory.getLogger(RingBuffer.class);

    /** Constants */
    private static final int START_POINT = -1; 
    private static final long CAN_PUT_FLAG = 0L; // Indicates the status of the current slot, indicating that an ID can be put into the slot
    private static final long CAN_TAKE_FLAG = 1L; // Indicates the status of the current slot, indicating that an ID can be taken
    public static final int DEFAULT_PADDING_PERCENT = 50; // The default threshold for controlling when slots is filled: When the number of available slots is less than 50% of bufferSize, the slots need to be filled with ids

    /** The size of RingBuffer's slots, each slot hold a UID */
    private final int bufferSize; // The size of slots. By default, the maximum number of slots available for sequence is 8192
    private final long indexMask; 
  
    private final long[] slots;  //slots is used to cache generated ids
    private final PaddedAtomicLong[] flags; //flags is used to store the status of ids (fillable, consumable)

    /** Tail: last position sequence to produce */
    / / Tail pointer
    // Indicates the maximum number of Producer production (starting from 0 and increasing). 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
    private final AtomicLong tail = new PaddedAtomicLong(START_POINT); //

    /** Cursor: current position sequence to consume */
    // Represents the minimum sequence number that the Consumer consumes (the sequence is the same as the 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
    private final AtomicLong cursor = new PaddedAtomicLong(START_POINT);

    /** Threshold for trigger padding buffer*/
    private final int paddingThreshold; // The threshold that controls when slots is filled
    
    /** Reject put/take buffer handle policy */
    // When slots is full and cannot continue to put. Default implementation: Do not put, only log
    private RejectedPutBufferHandler rejectedPutHandler = this::discardPutBuffer;
    // When slots is empty and cannot continue to take. Default implementation: only throws exceptions
    private RejectedTakeBufferHandler rejectedTakeHandler = this::exceptionRejectedTakeBuffer; 
    
    /** Executor of padding buffer */
    // Run the generate ID to fill slots task
    private BufferPaddingExecutor bufferPaddingExecutor;
Copy the code

Optimization at the code level

The code is padded with bytes to avoid false sharing.

When a multi-core processor processes variables that are independent of each other, once these variables are in the same cache line, the operation of different variables will cause the invalidation of the cache line, affecting the actual effect of the cache and causing a great performance problem of cache invalidation. The thread in the figure below handles two different variables, but changes to both variables invalidate the entire cache row, resulting in invalid loads, invalidation, and pseudo-sharing

RingBuffer defines a PaddedAtomicLong to monopolize a cache line. The implementation padding in the code may need to be adjusted depending on the execution system to ensure that it monopolizes a cache line.

Take first close the id source

Now let’s look at how to get the associated UID

public long take(a) {
        // spin get next available cursor
        long currentCursor = cursor.get();
        long nextCursor = cursor.updateAndGet(old -> old == tail.get() ? old : old + 1);

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

        // trigger padding in an async-mode if reach the threshold
        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 catch the tail, means that there is no more available UID to take
        if (nextCursor == currentCursor) {
            rejectedTakeHandler.rejectTakeBuffer(this);
        }

        // 1. check next slot flag is CAN_TAKE_FLAG
        int nextCursorIndex = calSlotIndex(nextCursor);
        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];
        flags[nextCursorIndex].set(CAN_PUT_FLAG);

        // Note that: Step 2,3 can not swap. If we set flag before get value of slot, the producer may overwrite the
        // slot with a new UID, and this may cause the consumer take the UID twice after walk a round the ring
        return uid;
    }
Copy the code

AtomicLong. UpdateAndGet to avoid locking the entire method, get a Cursor value for an accessible UID, and use this subscript to get the associated Uids from slots to return the available UIds in the cache. Another thread needs to be started to generate a batch of UID UID generation

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

// tail catches the cursor, means that you can't put any cause of RingBuffer is full long distance = currentTail - (currentCursor == START_POINT ? 0 : currentCursor); if (distance == bufferSize - 1) { rejectedPutHandler.rejectPutBuffer(this, uid); return false; } // 1. pre-check whether the flag is CAN_PUT_FLAG 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 // 3. update next slot' flag to CAN_TAKE_FLAG // 4. publish tail with sequence increase  by one slots[nextTailIndex] = uid; flags[nextTailIndex].set(CAN_TAKE_FLAG); tail.incrementAndGet(); // The atomicity of operations above, guarantees by 'synchronized'. In another word, // the take operation can't consume the UID we just put, until the tail is published(tail.incrementAndGet()) return true; }Copy the code

Get values under the Tail, if cache area with direct call RejectedPutHandler. Place the UID rejectPutBuffer method under word in slots array of the corresponding position, Change the location of the Flags array to CAN_TAKE_FLAG CachedUidGenerator. The CachedUidGenerator is used to generate a batch of UID lists in advance, which is time-consuming to obtain UUIds. However, this method also has its disadvantages. In addition, if there is not much traffic, the timestamp in the pre-generated UID may be very old, and DefaultUidGenerator should suffice in most scenarios.

Populating cache rows solves “fake sharing”

For pseudo-sharing, see this article “False Sharing, Silent Performance Killer of Concurrent Programming.”

// The array is physically contiguous. The flags array is used to store the state of the ID (consumable, populatable). The state of the ID and the consumption ID are frequently changed as they are filled in.
    // If the cache rows are not populated, frequent cache row invalidation will result in data being read directly from memory.
    private final PaddedAtomicLong[] flags;

    // Tail and CURSOR are both populated with cache rows to avoid tail and cursor falling on the same cache row.
    /** Tail: last position sequence to produce */
    private final AtomicLong tail = new PaddedAtomicLong(START_POINT);

    /** Cursor: current position sequence to consume */
    private final AtomicLong cursor = new PaddedAtomicLong(START_POINT)
Copy the code
The design of the PaddedAtomicLong
/**
 * Represents a padded {@link AtomicLong} to prevent the FalseSharing problem<p>
 * 
 * The CPU cache line commonly be 64 bytes, here is a sample of cache line after padding:<br>
 * 64 bytes = 8 bytes (object reference) + 6 * 8 bytes (padded long) + 8 bytes (a long value)
 * @author yutianbao
 */
public class PaddedAtomicLong extends AtomicLong {
    private static final long serialVersionUID = -3415778863941386253L;

    /** Padded 6 long (48 bytes) */
    public volatile long p1, p2, p3, p4, p5, p6 = 7L;

    /**
     * Constructors from {@link AtomicLong}
     */
    public PaddedAtomicLong(a) {
        super(a); }public PaddedAtomicLong(long initialValue) {
        super(initialValue);
    }

    /** * To prevent GC optimizations for cleaning unused padded references */
    public long sumPaddingToPreventOptimization(a) {
        returnp1 + p2 + p3 + p4 + p5 + p6; }}Copy the code

Spring Boot project integrates globally unique ID generator UidGenerator

Foundation engineering creation

Official website Integration document

Create table

Execute the following SQL

DROP TABLE IF EXISTS WORKER_NODE;
CREATE TABLE WORKER_NODE
(
ID BIGINT NOT NULL AUTO_INCREMENT COMMENT 'auto increment id',
HOST_NAME VARCHAR(64) NOT NULL COMMENT 'host name',
PORT VARCHAR(64) NOT NULL COMMENT 'port',
TYPE INT NOT NULL COMMENT 'node type: ACTUAL or CONTAINER',
LAUNCH_DATE DATE NOT NULL COMMENT 'launch date',
MODIFIED TIMESTAMP NOT NULL COMMENT 'modified time',
CREATED TIMESTAMP NOT NULL COMMENT 'created time'.PRIMARY KEY(ID)
)
 COMMENT='DB WorkerID Assigner for UID Generator',ENGINE = INNODB;
Copy the code

Create the table WORKER_NODE in the database you are using. (If the database version is older, you need to change TIMESTAMP type to datetime(3). The best way to do this is to change TIMESTAMP type to datetime(3).)

Introducing Maven dependencies

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.mybatis.spring.boot</groupId>
		<artifactId>mybatis-spring-boot-starter</artifactId>
		<version>2.1.0</version>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
	<! --for Mysql-->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<scope>runtime</scope>
		<version>8.0.12</version>
	</dependency>
	<! -- druid -->
	<dependency>
		<groupId>com.alibaba</groupId>
		<artifactId>druid-spring-boot-starter</artifactId>
		<version>1.1.9</version>
	</dependency>
	<! -- Must come last -->
	<dependency>
		<groupId>com.baidu.fsg</groupId>
		<artifactId>uid-generator</artifactId>
		<version>1.0.0 - the SNAPSHOT</version>
	</dependency>
</dependencies>

Copy the code

Internet JAR package introduction (this is the approach used in this article)

Only one JAR package was found in the Maven repository.

<dependency>
    <groupId>com.xfvape.uid</groupId>
    <artifactId>uid-generator</artifactId>
    <version>0.0.4 - RELEASE</version>
</dependency>
Copy the code
Eliminate conflicting dependencies

Uid-generator relies on logback and mybatis. Mybatis will be introduced as a separate dependency when springBoot already has a logback dependency during project setup. If the version does not match the dependencies in the UID-generator, conflicts can result. In order to prevent these problems, directly eliminate once and for all.

<dependency>
    <groupId>com.baidu.fsg</groupId>
    <artifactId>uid-generator</artifactId>
    <version>1.0.0 - the SNAPSHOT</version>
    <exclusions>
        <exclusion>
            <groupId>org.mybatis</groupId>
            <artifactId>*</artifactId>
        </exclusion>
    </exclusions>
</dependency>
Copy the code

Conflicting dependencies are excluded as follows :(the following dependencies also need to be excluded using the local project introduction approach)

< the dependency > < groupId > com. Xfvape. Uid < / groupId > < artifactId > uid - generator < / artifactId > < version > 0.0.4 - RELEASE < / version > <exclusions> <exclusion> <groupId>org.mybatis</groupId> <artifactId>*</artifactId> </exclusion> </exclusions> </dependency>Copy the code

I used Mybatis – Plus here. The official requirement of MyBatis – Plus is that if mybatis- Plus is to be used, myBatis cannot be introduced separately, so I must exclude MyBatis here.

Configure the SpringBoot core configuration

SQL > alter table application.properties;

server.port=9999 spring.datasource.url=jdbc:mysql://*.*.*.*:3306/baiduUidGenerator? useUnicode=true&characterEncoding=utf-8&useSSL=false spring.datasource.username=root spring.datasource.password=* spring.datasource.driver-class-name=com.mysql.jdbc.Driver mybatis.mapper-locations=classpath:mapper/*.xml mybatis.configuration.map-underscore-to-camel-case=trueCopy the code

MapperScan DAO layer interface scanning:

@MapperScan({"com.xxx.xx.dao"})
Copy the code

The core object is assembled as spring’s bean.

Uid-generator provides two types of generators: DefaultUidGenerator and CachedUidGenerator.

If you need UID generation performance, use CachedUidGenerator. The CachedUidGenerator is assembled in the same way as DefaultUidGenerator.

Custom DisposableWorkerIdAssigner

Will source DisposableWorkerIdAssigner class added to their own projects, and will the mapper method modified into their projects and start new DisposableWorkerIdAssigner directory content at the same level as follows


/**
 * Represents an implementation of {@link WorkerIdAssigner},
 * the worker id will be discarded after assigned to the UidGenerator
 * 
 * @author yutianbao
 */
public class DisposableWorkerIdAssigner implements WorkerIdAssigner {
    private static final Logger LOGGER = LoggerFactory.getLogger(DisposableWorkerIdAssigner.class);

    @Autowired
    private WorkerNodeMapper workerNodeMapper;

    /**
     * Assign worker id base on database.<p>
     * If there is host name & port in the environment, we considered that the node runs in Docker container<br>
     * Otherwise, the node runs on an actual machine.
     * 
     * @return assigned worker id
     */
    @Override
    @Transactional
    public long assignWorkerId(a) {
        // build worker node entity
        WorkerNodeEntity workerNodeEntity = buildWorkerNode();

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

        return workerNodeEntity.getId();
    }

    /** * Build worker node entity by IP and PORT */
    private WorkerNodeEntity buildWorkerNode(a) {
        WorkerNodeEntity workerNodeEntity = new WorkerNodeEntity();
        if (DockerUtils.isDocker()) {
            workerNodeEntity.setType(WorkerNodeType.CONTAINER.value());
            workerNodeEntity.setHostName(DockerUtils.getDockerHost());
            workerNodeEntity.setPort(DockerUtils.getDockerPort());
        } else {
            workerNodeEntity.setType(WorkerNodeType.ACTUAL.value());
            workerNodeEntity.setHostName(NetUtils.getLocalAddress());
            workerNodeEntity.setPort(System.currentTimeMillis() + "-" + RandomUtils.nextInt(100000));
        }
        returnworkerNodeEntity; }}Copy the code
/** * <p> Baidu uID-generator configuration *@author zepal
 * */
@Configuration
public class UidGeneratorConfig {


@Bean("disposableWorkerIdAssigner")
public DisposableWorkerIdAssigner disposableWorkerIdAssigner(a){
	DisposableWorkerIdAssigner disposableWorkerIdAssigner = new DisposableWorkerIdAssigner();
	return  disposableWorkerIdAssigner;
}

@Bean("cachedUidGenerator")
	public CachedUidGenerator initCachedUidGenerator(WorkerIdAssigner workerIdAssigner) {
		CachedUidGenerator cachedUidGenerator = new CachedUidGenerator();
		cachedUidGenerator.setWorkerIdAssigner(workerIdAssigner);
		/ / attribute reference links to https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md
        // The following configuration is optional. If this parameter is not specified, the default value is used
		// cachedUidGenerator.setTimeBits(28);
		// cachedUidGenerator.setWorkerBits(22);
		// cachedUidGenerator.setSeqBits(13);
		// cachedUidGenerator.setEpochStr("2016-09-20");
		cachedUidGenerator.setBoostPower(3);
		cachedUidGenerator.setPaddingFactor(50);
		cachedUidGenerator.setScheduleInterval(60L);
// // Reject policy: When the ring is full and cannot be filled
		// This parameter is not required by default. The Put operation is discarded and only logs are recorded. For special requirements, implement the RejectedPutBufferHandler interface (support Lambda expressions)
		// Reject policy: when the ring is empty and cannot be retrieved
		// No need to specify by default, it will be logged and UidGenerateException will be thrown. If you have special needs, please implement RejectedTakeBufferHandler interface Lambda expressions (support)
		returncachedUidGenerator; }}Copy the code
Detailed configuration information control
 / * * * disposableWorkerIdAssigner into object types had better use WorkerIdAssigner, * or other places to introduce additional dynamic proxy issue may cause agent mixed with * *@param disposableWorkerIdAssigner
     * @return* /
    @Bean
    public DefaultUidGenerator defaultUidGenerator(WorkerIdAssigner disposableWorkerIdAssigner) {
        DefaultUidGenerator defaultUidGenerator = new DefaultUidGenerator();
     defaultUidGenerator.setWorkerIdAssigner(disposableWorkerIdAssigner);
        /** * * For applications with low concurrency requirements and long-term usage, the number of timeBits can be increased and the number of seqBits can be decreased. * For example, if the node uses the WorkerIdAssigner policy and the restart frequency is 12 times/day, the following configuration can be configured: * {"workerBits":23,"timeBits":31,"seqBits":9} Supports 28 nodes to run for 68 years at a total concurrency rate of 14400 UID/s. * For example, if the node uses the WorkerIdAssigner policy and the restart frequency is 24*12 times/day, set the following parameters: * {"workerBits":27,"timeBits":30,"seqBits":6} supports 37 nodes running for 34 years at 2400 UID/s concurrency. */
        // The following configuration is optional. If this parameter is not specified, the default value is used
        defaultUidGenerator.setTimeBits(32);
        // Machine ID, can support up to 2^22 about 420W machine startup. 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.
        defaultUidGenerator.setWorkerBits(22);
        // Concurrency sequence per second, 9 bits support 512 concurrency per second per server.
        defaultUidGenerator.setSeqBits(9);
        defaultUidGenerator.setEpochStr("2020-01-01");

        return defaultUidGenerator;
    }
  / * * * disposableWorkerIdAssigner into object types had better use WorkerIdAssigner, * or other places to introduce additional dynamic proxy issue may cause agent mixed with * *@param disposableWorkerIdAssigner
     * @return* /
    @Bean
    public CachedUidGenerator cachedUidGenerator(WorkerIdAssigner disposableWorkerIdAssigner) {
        CachedUidGenerator cachedUidGenerator = new CachedUidGenerator();
        cachedUidGenerator.setWorkerIdAssigner(disposableWorkerIdAssigner);
        /** * * For applications with low concurrency requirements and long-term usage, the number of timeBits can be increased and the number of seqBits can be decreased. * For example, if the node uses the WorkerIdAssigner policy and the restart frequency is 12 times/day, the following configuration can be configured: * {"workerBits":23,"timeBits":31,"seqBits":9} Supports 28 nodes to run for 68 years at a total concurrency rate of 14400 UID/s. * For example, if the node uses the WorkerIdAssigner policy and the restart frequency is 24*12 times/day, set the following parameters: * {"workerBits":27,"timeBits":30,"seqBits":6} supports 37 nodes running for 34 years at 2400 UID/s concurrency. */
        // The following configuration is optional. If this parameter is not specified, the default value is used
        cachedUidGenerator.setTimeBits(32);
        // Machine ID, can support up to 2^22 about 420W machine startup. 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.
        cachedUidGenerator.setWorkerBits(22);
        // Concurrency sequence per second, 9 bits support 512 concurrency per second per server.
        cachedUidGenerator.setSeqBits(9);
        cachedUidGenerator.setEpochStr("2020-01-01");

        //RingBuffer size Capacity expansion parameter to improve the throughput of UID generation
        // Default :3, original bufferSize=8192, bufferSize=8192 << 3 = 65536
        cachedUidGenerator.setBoostPower(3);
        // Specifies when to fill the RingBuffer with UID. The value is a percentage (0, 100), and the default is 50
        // Example: bufferSize=1024, paddingFactor=50 -> threshold=1024 * 50/100 = 512.
        // When the number of available UUIds on the ring is less than 512, RingBuffer will be filled automatically
        //<property name="paddingFactor" value="50"></property>

        // Another RingBuffer fill timing, in the Schedule thread, periodically checks the fill
        // Default: This item is not configured, that is, the Schedule thread is not available. If necessary, specify Schedule thread interval, in seconds
        cachedUidGenerator.setScheduleInterval(60L);

        // Reject policy: when the ring is too full to continue filling
        // This parameter is not required by default. The Put operation is discarded and only logs are recorded. For special requirements, implement the RejectedPutBufferHandler interface (support Lambda expressions)
        //<property name="rejectedPutBufferHandler" ref="XxxxYourPutRejectPolicy"></property>
        //cachedUidGenerator.setRejectedPutBufferHandler();
        // Reject policy: when the ring is empty, it cannot continue to fetch -->
        // No need to specify by default, it will be logged and UidGenerateException will be thrown. If you have special needs, please implement RejectedTakeBufferHandler interface Lambda expressions (support) -- - >
        //<property name="rejectedTakeBufferHandler" ref="XxxxYourTakeRejectPolicy"></property>

        return cachedUidGenerator;
    }
Copy the code

Mapper service interface

Create a WorkerNodeMapper in the same directory as the startup class

@Repository
public interface WorkerNodeMapper {
    /**
     * Get {@link WorkerNodeEntity} by node host
     * 
     * @param host
     * @param port
     * @return* /
    WorkerNodeEntity getWorkerNodeByHostPort(@Param("host") String host, @Param("port") String port);

    /**
     * Add {@link WorkerNodeEntity}
     * 
     * @param workerNodeEntity
     */
    void addWorkerNode(WorkerNodeEntity workerNodeEntity);
}
Copy the code

WorkerNodeMapper


      
<! DOCTYPEmapper PUBLIC "- / / mybatis.org//DTD Mapper / 3.0 / EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.zxp.uidgeneratortest.WorkerNodeMapper">
    <resultMap id="workerNodeRes"
               type="com.baidu.fsg.uid.worker.entity.WorkerNodeEntity">
        <id column="ID" jdbcType="BIGINT" property="id"/>
        <result column="HOST_NAME" jdbcType="VARCHAR" property="hostName"/>
        <result column="PORT" jdbcType="VARCHAR" property="port"/>
        <result column="TYPE" jdbcType="INTEGER" property="type"/>
        <result column="LAUNCH_DATE" jdbcType="DATE" property="launchDate"/>
        <result column="MODIFIED" jdbcType="TIMESTAMP" property="modified"/>
        <result column="CREATED" jdbcType="TIMESTAMP" property="created"/>
    </resultMap>

    <insert id="addWorkerNode" useGeneratedKeys="true" keyProperty="id"
            parameterType="com.baidu.fsg.uid.worker.entity.WorkerNodeEntity">
		INSERT INTO WORKER_NODE
		(HOST_NAME,
		PORT,
		TYPE,
		LAUNCH_DATE,
		MODIFIED,
		CREATED)
		VALUES (
		#{hostName},
		#{port},
		#{type},
		#{launchDate},
		NOW(),
		NOW())
	</insert>

    <select id="getWorkerNodeByHostPort" resultMap="workerNodeRes">
		SELECT
		ID,
		HOST_NAME,
		PORT,
		TYPE,
		LAUNCH_DATE,
		MODIFIED,
		CREATED
		FROM
		WORKER_NODE
		WHERE
		HOST_NAME = #{host} AND PORT = #{port}
	</select>
</mapper>

Copy the code

Create the UidGenService logical class

@Service
public class UidGenService {
    @Resource
    private UidGenerator uidGenerator;
    public long getUid(a) {
        returnuidGenerator.getUID(); }}Copy the code

The resources

How much memory does a Java object take up?

Write a Java also must understand the CPU – false sharing www.cnblogs.com/techyc/p/36…

www.jianshu.com/p/947bff7be…

Blog.csdn.net/qq_43578870…