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…