提交 79cf3c53 编写于 作者: S System Administrator

封装一个流水号ID生成器

上级 db966d1b
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.codesheep</groupId>
<artifactId>id-spring-boot-starter</artifactId>
<version>1.0.0</version>
<!-- Properties -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jdk.version>1.8</jdk.version>
<spring.version>5.0.9.RELEASE</spring.version>
<slf4j-version>1.7.7</slf4j-version>
</properties>
<!-- Dependencies -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.3</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.4</version>
</dependency>
<!-- Apache Commons -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<!-- Logger -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j-version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>${slf4j-version}</version>
</dependency>
<!-- Test. Scope test only -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.18</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.19</version>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${jdk.version}</source>
<target>${jdk.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
<version>3.5.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
/*
* Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baidu.fsg.uid;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.springframework.util.Assert;
/**
* Allocate 64 bits for the UID(long)<br>
* sign (fixed 1bit) -> deltaSecond -> workerId -> sequence(within the same second)
*
* @author yutianbao
*/
public class BitsAllocator {
/**
* Total 64 bits
*/
public static final int TOTAL_BITS = 1 << 6;
/**
* Bits for [sign-> second-> workId-> sequence]
*/
private int signBits = 1;
private final int timestampBits;
private final int workerIdBits;
private final int sequenceBits;
/**
* Max value for workId & sequence
*/
private final long maxDeltaSeconds;
private final long maxWorkerId;
private final long maxSequence;
/**
* Shift for timestamp & workerId
*/
private final int timestampShift;
private final int workerIdShift;
/**
* Constructor with timestampBits, workerIdBits, sequenceBits<br>
* The highest bit used for sign, so <code>63</code> bits for timestampBits, workerIdBits, sequenceBits
*/
public BitsAllocator(int timestampBits, int workerIdBits, int sequenceBits) {
// make sure allocated 64 bits
int allocateTotalBits = signBits + timestampBits + workerIdBits + sequenceBits;
Assert.isTrue(allocateTotalBits == TOTAL_BITS, "allocate not enough 64 bits");
// initialize bits
this.timestampBits = timestampBits;
this.workerIdBits = workerIdBits;
this.sequenceBits = sequenceBits;
// initialize max value
this.maxDeltaSeconds = ~(-1L << timestampBits);
this.maxWorkerId = ~(-1L << workerIdBits);
this.maxSequence = ~(-1L << sequenceBits);
// initialize shift
this.timestampShift = workerIdBits + sequenceBits;
this.workerIdShift = sequenceBits;
}
/**
* Allocate bits for UID according to delta seconds & workerId & sequence<br>
* <b>Note that: </b>The highest bit will always be 0 for sign
*
* @param deltaSeconds
* @param workerId
* @param sequence
* @return
*/
public long allocate(long deltaSeconds, long workerId, long sequence) {
return (deltaSeconds << timestampShift) | (workerId << workerIdShift) | sequence;
}
/**
* Getters
*/
public int getSignBits() {
return signBits;
}
public int getTimestampBits() {
return timestampBits;
}
public int getWorkerIdBits() {
return workerIdBits;
}
public int getSequenceBits() {
return sequenceBits;
}
public long getMaxDeltaSeconds() {
return maxDeltaSeconds;
}
public long getMaxWorkerId() {
return maxWorkerId;
}
public long getMaxSequence() {
return maxSequence;
}
public int getTimestampShift() {
return timestampShift;
}
public int getWorkerIdShift() {
return workerIdShift;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}
\ No newline at end of file
/*
* Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baidu.fsg.uid;
import com.baidu.fsg.uid.exception.UidGenerateException;
/**
* Represents a unique id generator.
*
* @author yutianbao
*/
public interface UidGenerator {
/**
* Get a unique ID
*
* @return UID
* @throws UidGenerateException
*/
long getUID() throws UidGenerateException;
/**
* Parse the UID into elements which are used to generate the UID. <br>
* Such as timestamp & workerId & sequence...
*
* @param uid
* @return Parsed info
*/
String parseUID(long uid);
}
/*
* Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baidu.fsg.uid.buffer;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import com.baidu.fsg.uid.utils.NamingThreadFactory;
import com.baidu.fsg.uid.utils.PaddedAtomicLong;
/**
* Represents an executor for padding {@link RingBuffer}<br>
* There are two kinds of executors: one for scheduled padding, the other for padding immediately.
*
* @author yutianbao
*/
public class BufferPaddingExecutor {
private static final Logger LOGGER = LoggerFactory.getLogger(RingBuffer.class);
/** Constants */
private static final String WORKER_NAME = "RingBuffer-Padding-Worker";
private static final String SCHEDULE_NAME = "RingBuffer-Padding-Schedule";
private static final long DEFAULT_SCHEDULE_INTERVAL = 5 * 60L; // 5 minutes
/** Whether buffer padding is running */
private final AtomicBoolean running;
/** We can borrow UIDs from the future, here store the last second we have consumed */
private final PaddedAtomicLong lastSecond;
/** RingBuffer & BufferUidProvider */
private final RingBuffer ringBuffer;
private final BufferedUidProvider uidProvider;
/** Padding immediately by the thread pool */
private final ExecutorService bufferPadExecutors;
/** Padding schedule thread */
private final ScheduledExecutorService bufferPadSchedule;
/** Schedule interval Unit as seconds */
private long scheduleInterval = DEFAULT_SCHEDULE_INTERVAL;
/**
* Constructor with {@link RingBuffer} and {@link BufferedUidProvider}, default use schedule
*
* @param ringBuffer {@link RingBuffer}
* @param uidProvider {@link BufferedUidProvider}
*/
public BufferPaddingExecutor(RingBuffer ringBuffer, BufferedUidProvider uidProvider) {
this(ringBuffer, uidProvider, true);
}
/**
* Constructor with {@link RingBuffer}, {@link BufferedUidProvider}, and whether use schedule padding
*
* @param ringBuffer {@link RingBuffer}
* @param uidProvider {@link BufferedUidProvider}
* @param usingSchedule
*/
public BufferPaddingExecutor(RingBuffer ringBuffer, BufferedUidProvider uidProvider, boolean usingSchedule) {
this.running = new AtomicBoolean(false);
this.lastSecond = new PaddedAtomicLong(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()));
this.ringBuffer = ringBuffer;
this.uidProvider = uidProvider;
// initialize thread pool
int cores = Runtime.getRuntime().availableProcessors();
bufferPadExecutors = Executors.newFixedThreadPool(cores * 2, new NamingThreadFactory(WORKER_NAME));
// initialize schedule thread
if (usingSchedule) {
bufferPadSchedule = Executors.newSingleThreadScheduledExecutor(new NamingThreadFactory(SCHEDULE_NAME));
} else {
bufferPadSchedule = null;
}
}
/**
* Start executors such as schedule
*/
public void start() {
if (bufferPadSchedule != null) {
bufferPadSchedule.scheduleWithFixedDelay(() -> paddingBuffer(), scheduleInterval, scheduleInterval, TimeUnit.SECONDS);
}
}
/**
* Shutdown executors
*/
public void shutdown() {
if (!bufferPadExecutors.isShutdown()) {
bufferPadExecutors.shutdownNow();
}
if (bufferPadSchedule != null && !bufferPadSchedule.isShutdown()) {
bufferPadSchedule.shutdownNow();
}
}
/**
* Whether is padding
*
* @return
*/
public boolean isRunning() {
return running.get();
}
/**
* Padding buffer in the thread pool
*/
public void asyncPadding() {
bufferPadExecutors.submit(this::paddingBuffer);
}
/**
* Padding buffer fill the slots until to catch the cursor
*/
public void paddingBuffer() {
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) {
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);
}
/**
* Setters
*/
public void setScheduleInterval(long scheduleInterval) {
Assert.isTrue(scheduleInterval > 0, "Schedule interval must positive!");
this.scheduleInterval = scheduleInterval;
}
}
/*
* Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baidu.fsg.uid.buffer;
import java.util.List;
/**
* Buffered UID provider(Lambda supported), which provides UID in the same one second
*
* @author yutianbao
*/
@FunctionalInterface
public interface BufferedUidProvider {
/**
* Provides UID in one second
*
* @param momentInSecond
* @return
*/
List<Long> provide(long momentInSecond);
}
/*
* Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baidu.fsg.uid.buffer;
/**
* If tail catches the cursor it means that the ring buffer is full, any more buffer put request will be rejected.
* Specify the policy to handle the reject. This is a Lambda supported interface
*
* @author yutianbao
*/
@FunctionalInterface
public interface RejectedPutBufferHandler {
/**
* Reject put buffer request
*
* @param ringBuffer
* @param uid
*/
void rejectPutBuffer(RingBuffer ringBuffer, long uid);
}
/*
* Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baidu.fsg.uid.buffer;
/**
* If cursor catches the tail it means that the ring buffer is empty, any more buffer take request will be rejected.
* Specify the policy to handle the reject. This is a Lambda supported interface
*
* @author yutianbao
*/
@FunctionalInterface
public interface RejectedTakeBufferHandler {
/**
* Reject take buffer request
*
* @param ringBuffer
*/
void rejectTakeBuffer(RingBuffer ringBuffer);
}
/*
* Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baidu.fsg.uid.buffer;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import com.baidu.fsg.uid.utils.PaddedAtomicLong;
/**
* Represents a ring buffer based on array.<br>
* Using array could improve read element performance due to the CUP cache line. To prevent
* the side effect of False Sharing, {@link PaddedAtomicLong} is using on 'tail' and 'cursor'<p>
*
* A ring buffer is consisted of:
* <li><b>slots:</b> each element of the array is a slot, which is be set with a UID
* <li><b>flags:</b> flag array corresponding the same index with the slots, indicates whether can take or put slot
* <li><b>tail:</b> a sequence of the max slot position to produce
* <li><b>cursor:</b> a sequence of the min slot position to consume
*
* @author yutianbao
*/
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;
private static final long CAN_TAKE_FLAG = 1L;
public static final int DEFAULT_PADDING_PERCENT = 50;
/** The size of RingBuffer's slots, each slot hold a UID */
private final int bufferSize;
private final long indexMask;
private final long[] slots;
private final PaddedAtomicLong[] flags;
/** 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);
/** Threshold for trigger padding buffer*/
private final int paddingThreshold;
/** Reject put/take buffer handle policy */
private RejectedPutBufferHandler rejectedPutHandler = this::discardPutBuffer;
private RejectedTakeBufferHandler rejectedTakeHandler = this::exceptionRejectedTakeBuffer;
/** Executor of padding buffer */
private BufferPaddingExecutor bufferPaddingExecutor;
/**
* Constructor with buffer size, paddingFactor default as {@value #DEFAULT_PADDING_PERCENT}
*
* @param bufferSize must be positive & a power of 2
*/
public RingBuffer(int bufferSize) {
this(bufferSize, DEFAULT_PADDING_PERCENT);
}
/**
* Constructor with buffer size & padding factor
*
* @param bufferSize must be positive & a power of 2
* @param paddingFactor percent in (0 - 100). When the count of rest available UIDs reach the threshold, it will trigger padding buffer<br>
* Sample: paddingFactor=20, bufferSize=1000 -> threshold=1000 * 20 /100,
* padding buffer will be triggered when tail-cursor<threshold
*/
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;
}
/**
* Put an UID in the ring & tail moved<br>
* We use 'synchronized' to guarantee the UID fill in slot & publish new tail sequence as atomic operations<br>
*
* <b>Note that: </b> It is recommended to put UID in a serialize way, cause we once batch generate a series UIDs and put
* the one by one into the buffer, so it is unnecessary put in multi-threads
*
* @param uid
* @return false means that the buffer is full, apply {@link RejectedPutBufferHandler}
*/
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;
}
/**
* Take an UID of the ring at the next cursor, this is a lock free operation by using atomic cursor<p>
*
* Before getting the UID, we also check whether reach the padding threshold,
* the padding buffer operation will be triggered in another thread<br>
* If there is no more available UID to be taken, the specified {@link RejectedTakeBufferHandler} will be applied<br>
*
* @return UID
* @throws IllegalStateException if the cursor moved back
*/
public long take() {
// 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;
}
/**
* Calculate slot index with the slot sequence (sequence % bufferSize)
*/
protected int calSlotIndex(long sequence) {
return (int) (sequence & indexMask);
}
/**
* Discard policy for {@link RejectedPutBufferHandler}, we just do logging
*/
protected void discardPutBuffer(RingBuffer ringBuffer, long uid) {
LOGGER.warn("Rejected putting buffer for uid:{}. {}", uid, ringBuffer);
}
/**
* Policy for {@link RejectedTakeBufferHandler}, throws {@link RuntimeException} after logging
*/
protected void exceptionRejectedTakeBuffer(RingBuffer ringBuffer) {
LOGGER.warn("Rejected take buffer. {}", ringBuffer);
throw new RuntimeException("Rejected take buffer. " + ringBuffer);
}
/**
* Initialize flags as CAN_PUT_FLAG
*/
private PaddedAtomicLong[] initFlags(int bufferSize) {
PaddedAtomicLong[] flags = new PaddedAtomicLong[bufferSize];
for (int i = 0; i < bufferSize; i++) {
flags[i] = new PaddedAtomicLong(CAN_PUT_FLAG);
}
return flags;
}
/**
* Getters
*/
public long getTail() {
return tail.get();
}
public long getCursor() {
return cursor.get();
}
public int getBufferSize() {
return bufferSize;
}
/**
* Setters
*/
public void setBufferPaddingExecutor(BufferPaddingExecutor bufferPaddingExecutor) {
this.bufferPaddingExecutor = bufferPaddingExecutor;
}
public void setRejectedPutHandler(RejectedPutBufferHandler rejectedPutHandler) {
this.rejectedPutHandler = rejectedPutHandler;
}
public void setRejectedTakeHandler(RejectedTakeBufferHandler rejectedTakeHandler) {
this.rejectedTakeHandler = rejectedTakeHandler;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("RingBuffer [bufferSize=").append(bufferSize)
.append(", tail=").append(tail)
.append(", cursor=").append(cursor)
.append(", paddingThreshold=").append(paddingThreshold).append("]");
return builder.toString();
}
}
package com.baidu.fsg.uid.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
@Configuration
@ImportResource(locations = { "classpath:config/cached-uid-spring.xml" })
public class UIDConfig {
}
/*
* Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baidu.fsg.uid.exception;
/**
* UidGenerateException
*
* @author yutianbao
*/
public class UidGenerateException extends RuntimeException {
/**
* Serial Version UID
*/
private static final long serialVersionUID = -27048199131316992L;
/**
* Default constructor
*/
public UidGenerateException() {
super();
}
/**
* Constructor with message & cause
*
* @param message
* @param cause
*/
public UidGenerateException(String message, Throwable cause) {
super(message, cause);
}
/**
* Constructor with message
*
* @param message
*/
public UidGenerateException(String message) {
super(message);
}
/**
* Constructor with message format
*
* @param msgFormat
* @param args
*/
public UidGenerateException(String msgFormat, Object... args) {
super(String.format(msgFormat, args));
}
/**
* Constructor with cause
*
* @param cause
*/
public UidGenerateException(Throwable cause) {
super(cause);
}
}
/*
* Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baidu.fsg.uid.impl;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import com.baidu.fsg.uid.BitsAllocator;
import com.baidu.fsg.uid.UidGenerator;
import com.baidu.fsg.uid.buffer.BufferPaddingExecutor;
import com.baidu.fsg.uid.buffer.RejectedPutBufferHandler;
import com.baidu.fsg.uid.buffer.RejectedTakeBufferHandler;
import com.baidu.fsg.uid.buffer.RingBuffer;
import com.baidu.fsg.uid.exception.UidGenerateException;
/**
* Represents a cached implementation of {@link UidGenerator} extends
* from {@link DefaultUidGenerator}, based on a lock free {@link RingBuffer}<p>
*
* The spring properties you can specified as below:<br>
* <li><b>boostPower:</b> RingBuffer size boost for a power of 2, Sample: boostPower is 3, it means the buffer size
* will be <code>({@link BitsAllocator#getMaxSequence()} + 1) &lt;&lt;
* {@link #boostPower}</code>, Default as {@value #DEFAULT_BOOST_POWER}
* <li><b>paddingFactor:</b> Represents a percent value of (0 - 100). When the count of rest available UIDs reach the
* threshold, it will trigger padding buffer. Default as{@link RingBuffer#DEFAULT_PADDING_PERCENT}
* Sample: paddingFactor=20, bufferSize=1000 -> threshold=1000 * 20 /100, padding buffer will be triggered when tail-cursor<threshold
* <li><b>scheduleInterval:</b> Padding buffer in a schedule, specify padding buffer interval, Unit as second
* <li><b>rejectedPutBufferHandler:</b> Policy for rejected put buffer. Default as discard put request, just do logging
* <li><b>rejectedTakeBufferHandler:</b> Policy for rejected take buffer. Default as throwing up an exception
*
* @author yutianbao
*/
public class CachedUidGenerator extends DefaultUidGenerator implements DisposableBean {
private static final Logger LOGGER = LoggerFactory.getLogger(CachedUidGenerator.class);
private static final int DEFAULT_BOOST_POWER = 3;
/** Spring properties */
private int boostPower = DEFAULT_BOOST_POWER;
private int paddingFactor = RingBuffer.DEFAULT_PADDING_PERCENT;
private Long scheduleInterval;
private RejectedPutBufferHandler rejectedPutBufferHandler;
private RejectedTakeBufferHandler rejectedTakeBufferHandler;
/** RingBuffer */
private RingBuffer ringBuffer;
private BufferPaddingExecutor bufferPaddingExecutor;
@Override
public void afterPropertiesSet() throws Exception {
// initialize workerId & bitsAllocator
super.afterPropertiesSet();
// initialize RingBuffer & RingBufferPaddingExecutor
this.initRingBuffer();
LOGGER.info("Initialized RingBuffer successfully.");
}
@Override
public long getUID() {
try {
return ringBuffer.take();
} catch (Exception e) {
LOGGER.error("Generate unique id exception. ", e);
throw new UidGenerateException(e);
}
}
@Override
public String parseUID(long uid) {
return super.parseUID(uid);
}
@Override
public void destroy() throws Exception {
bufferPaddingExecutor.shutdown();
}
/**
* 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;
}
/**
* Initialize RingBuffer & RingBufferPaddingExecutor
*/
private void initRingBuffer() {
// initialize RingBuffer
int bufferSize = ((int) bitsAllocator.getMaxSequence() + 1) << boostPower;
this.ringBuffer = new RingBuffer(bufferSize, paddingFactor);
LOGGER.info("Initialized ring buffer size:{}, paddingFactor:{}", bufferSize, paddingFactor);
// initialize RingBufferPaddingExecutor
boolean usingSchedule = (scheduleInterval != null);
this.bufferPaddingExecutor = new BufferPaddingExecutor(ringBuffer, this::nextIdsForOneSecond, usingSchedule);
if (usingSchedule) {
bufferPaddingExecutor.setScheduleInterval(scheduleInterval);
}
LOGGER.info("Initialized BufferPaddingExecutor. Using schdule:{}, interval:{}", usingSchedule, scheduleInterval);
// set rejected put/take handle policy
this.ringBuffer.setBufferPaddingExecutor(bufferPaddingExecutor);
if (rejectedPutBufferHandler != null) {
this.ringBuffer.setRejectedPutHandler(rejectedPutBufferHandler);
}
if (rejectedTakeBufferHandler != null) {
this.ringBuffer.setRejectedTakeHandler(rejectedTakeBufferHandler);
}
// fill in all slots of the RingBuffer
bufferPaddingExecutor.paddingBuffer();
// start buffer padding threads
bufferPaddingExecutor.start();
}
/**
* Setters for spring property
*/
public void setBoostPower(int boostPower) {
Assert.isTrue(boostPower > 0, "Boost power must be positive!");
this.boostPower = boostPower;
}
public void setRejectedPutBufferHandler(RejectedPutBufferHandler rejectedPutBufferHandler) {
Assert.notNull(rejectedPutBufferHandler, "RejectedPutBufferHandler can't be null!");
this.rejectedPutBufferHandler = rejectedPutBufferHandler;
}
public void setRejectedTakeBufferHandler(RejectedTakeBufferHandler rejectedTakeBufferHandler) {
Assert.notNull(rejectedTakeBufferHandler, "RejectedTakeBufferHandler can't be null!");
this.rejectedTakeBufferHandler = rejectedTakeBufferHandler;
}
public void setScheduleInterval(long scheduleInterval) {
Assert.isTrue(scheduleInterval > 0, "Schedule interval must positive!");
this.scheduleInterval = scheduleInterval;
}
}
/*
* Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baidu.fsg.uid.impl;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import com.baidu.fsg.uid.BitsAllocator;
import com.baidu.fsg.uid.UidGenerator;
import com.baidu.fsg.uid.exception.UidGenerateException;
import com.baidu.fsg.uid.utils.DateUtils;
import com.baidu.fsg.uid.worker.WorkerIdAssigner;
/**
* Represents an implementation of {@link UidGenerator}
*
* The unique id has 64bits (long), default allocated as blow:<br>
* <li>sign: The highest bit is 0
* <li>delta seconds: The next 28 bits, represents delta seconds since a customer epoch(2016-05-20 00:00:00.000).
* Supports about 8.7 years until to 2024-11-20 21:24:16
* <li>worker id: The next 22 bits, represents the worker's id which assigns based on database, max id is about 420W
* <li>sequence: The next 13 bits, represents a sequence within the same second, max for 8192/s<br><br>
*
* The {@link DefaultUidGenerator#parseUID(long)} is a tool method to parse the bits
*
* <pre>{@code
* +------+----------------------+----------------+-----------+
* | sign | delta seconds | worker node id | sequence |
* +------+----------------------+----------------+-----------+
* 1bit 28bits 22bits 13bits
* }</pre>
*
* You can also specified the bits by Spring property setting.
* <li>timeBits: default as 28
* <li>workerBits: default as 22
* <li>seqBits: default as 13
* <li>epochStr: Epoch date string format 'yyyy-MM-dd'. Default as '2016-05-20'<p>
*
* <b>Note that:</b> The total bits must be 64 -1
*
* @author yutianbao
*/
public class DefaultUidGenerator implements UidGenerator, InitializingBean {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultUidGenerator.class);
/** Bits allocate */
protected int timeBits = 28;
protected int workerBits = 22;
protected int seqBits = 13;
/** Customer epoch, unit as second. For example 2016-05-20 (ms: 1463673600000)*/
protected String epochStr = "2016-05-20";
protected long epochSeconds = TimeUnit.MILLISECONDS.toSeconds(1463673600000L);
/** Stable fields after spring bean initializing */
protected BitsAllocator bitsAllocator;
protected long workerId;
/** Volatile fields caused by nextId() */
protected long sequence = 0L;
protected long lastSecond = -1L;
/** Spring property */
protected WorkerIdAssigner workerIdAssigner;
@Override
public void afterPropertiesSet() throws Exception {
// initialize bits allocator
bitsAllocator = new BitsAllocator(timeBits, workerBits, seqBits);
// initialize worker id
workerId = workerIdAssigner.assignWorkerId();
if (workerId > bitsAllocator.getMaxWorkerId()) {
throw new RuntimeException("Worker id " + workerId + " exceeds the max " + bitsAllocator.getMaxWorkerId());
}
LOGGER.info("Initialized bits(1, {}, {}, {}) for workerID:{}", timeBits, workerBits, seqBits, workerId);
}
@Override
public long getUID() throws UidGenerateException {
try {
return nextId();
} catch (Exception e) {
LOGGER.error("Generate unique id exception. ", e);
throw new UidGenerateException(e);
}
}
@Override
public String parseUID(long uid) {
long totalBits = BitsAllocator.TOTAL_BITS;
long signBits = bitsAllocator.getSignBits();
long timestampBits = bitsAllocator.getTimestampBits();
long workerIdBits = bitsAllocator.getWorkerIdBits();
long sequenceBits = bitsAllocator.getSequenceBits();
// parse UID
long sequence = (uid << (totalBits - sequenceBits)) >>> (totalBits - sequenceBits);
long workerId = (uid << (timestampBits + signBits)) >>> (totalBits - workerIdBits);
long deltaSeconds = uid >>> (workerIdBits + sequenceBits);
Date thatTime = new Date(TimeUnit.SECONDS.toMillis(epochSeconds + deltaSeconds));
String thatTimeStr = DateUtils.formatByDateTimePattern(thatTime);
// format as string
return String.format("{\"UID\":\"%d\",\"timestamp\":\"%s\",\"workerId\":\"%d\",\"sequence\":\"%d\"}",
uid, thatTimeStr, workerId, sequence);
}
/**
* Get UID
*
* @return UID
* @throws UidGenerateException in the case: Clock moved backwards; Exceeds the max timestamp
*/
protected synchronized long nextId() {
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);
}
/**
* Get next millisecond
*/
private long getNextSecond(long lastTimestamp) {
long timestamp = getCurrentSecond();
while (timestamp <= lastTimestamp) {
timestamp = getCurrentSecond();
}
return timestamp;
}
/**
* Get current second
*/
private long getCurrentSecond() {
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;
}
/**
* Setters for spring property
*/
public void setWorkerIdAssigner(WorkerIdAssigner workerIdAssigner) {
this.workerIdAssigner = workerIdAssigner;
}
public void setTimeBits(int timeBits) {
if (timeBits > 0) {
this.timeBits = timeBits;
}
}
public void setWorkerBits(int workerBits) {
if (workerBits > 0) {
this.workerBits = workerBits;
}
}
public void setSeqBits(int seqBits) {
if (seqBits > 0) {
this.seqBits = seqBits;
}
}
public void setEpochStr(String epochStr) {
if (StringUtils.isNotBlank(epochStr)) {
this.epochStr = epochStr;
this.epochSeconds = TimeUnit.MILLISECONDS.toSeconds(DateUtils.parseByDayPattern(epochStr).getTime());
}
}
}
package com.baidu.fsg.uid.service;
import com.baidu.fsg.uid.UidGenerator;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class UidGenService {
@Resource
private UidGenerator uidGenerator;
public long getUid() {
return uidGenerator.getUID();
}
}
/*
* Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baidu.fsg.uid.utils;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import org.apache.commons.lang.time.DateFormatUtils;
/**
* DateUtils provides date formatting, parsing
*
* @author yutianbao
*/
public abstract class DateUtils extends org.apache.commons.lang.time.DateUtils {
/**
* Patterns
*/
public static final String DAY_PATTERN = "yyyy-MM-dd";
public static final String DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
public static final String DATETIME_MS_PATTERN = "yyyy-MM-dd HH:mm:ss.SSS";
public static final Date DEFAULT_DATE = DateUtils.parseByDayPattern("1970-01-01");
/**
* Parse date by 'yyyy-MM-dd' pattern
*
* @param str
* @return
*/
public static Date parseByDayPattern(String str) {
return parseDate(str, DAY_PATTERN);
}
/**
* Parse date by 'yyyy-MM-dd HH:mm:ss' pattern
*
* @param str
* @return
*/
public static Date parseByDateTimePattern(String str) {
return parseDate(str, DATETIME_PATTERN);
}
/**
* Parse date without Checked exception
*
* @param str
* @param pattern
* @return
* @throws RuntimeException when ParseException occurred
*/
public static Date parseDate(String str, String pattern) {
try {
return parseDate(str, new String[]{pattern});
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
/**
* Format date into string
*
* @param date
* @param pattern
* @return
*/
public static String formatDate(Date date, String pattern) {
return DateFormatUtils.format(date, pattern);
}
/**
* Format date by 'yyyy-MM-dd' pattern
*
* @param date
* @return
*/
public static String formatByDayPattern(Date date) {
if (date != null) {
return DateFormatUtils.format(date, DAY_PATTERN);
} else {
return null;
}
}
/**
* Format date by 'yyyy-MM-dd HH:mm:ss' pattern
*
* @param date
* @return
*/
public static String formatByDateTimePattern(Date date) {
return DateFormatUtils.format(date, DATETIME_PATTERN);
}
/**
* Get current day using format date by 'yyyy-MM-dd HH:mm:ss' pattern
*
* @return
* @author yebo
*/
public static String getCurrentDayByDayPattern() {
Calendar cal = Calendar.getInstance();
return formatByDayPattern(cal.getTime());
}
}
/*
* Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baidu.fsg.uid.utils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* DockerUtils
*
* @author yutianbao
*/
public abstract class DockerUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(DockerUtils.class);
/** Environment param keys */
private static final String ENV_KEY_HOST = "JPAAS_HOST";
private static final String ENV_KEY_PORT = "JPAAS_HTTP_PORT";
private static final String ENV_KEY_PORT_ORIGINAL = "JPAAS_HOST_PORT_8080";
/** Docker host & port */
private static String DOCKER_HOST = "";
private static String DOCKER_PORT = "";
/** Whether is docker */
private static boolean IS_DOCKER;
static {
retrieveFromEnv();
}
/**
* Retrieve docker host
*
* @return empty string if not a docker
*/
public static String getDockerHost() {
return DOCKER_HOST;
}
/**
* Retrieve docker port
*
* @return empty string if not a docker
*/
public static String getDockerPort() {
return DOCKER_PORT;
}
/**
* Whether a docker
*
* @return
*/
public static boolean isDocker() {
return IS_DOCKER;
}
/**
* Retrieve host & port from environment
*/
private static void retrieveFromEnv() {
// retrieve host & port from environment
DOCKER_HOST = System.getenv(ENV_KEY_HOST);
DOCKER_PORT = System.getenv(ENV_KEY_PORT);
// not found from 'JPAAS_HTTP_PORT', then try to find from 'JPAAS_HOST_PORT_8080'
if (StringUtils.isBlank(DOCKER_PORT)) {
DOCKER_PORT = System.getenv(ENV_KEY_PORT_ORIGINAL);
}
boolean hasEnvHost = StringUtils.isNotBlank(DOCKER_HOST);
boolean hasEnvPort = StringUtils.isNotBlank(DOCKER_PORT);
// docker can find both host & port from environment
if (hasEnvHost && hasEnvPort) {
IS_DOCKER = true;
// found nothing means not a docker, maybe an actual machine
} else if (!hasEnvHost && !hasEnvPort) {
IS_DOCKER = false;
} else {
LOGGER.error("Missing host or port from env for Docker. host:{}, port:{}", DOCKER_HOST, DOCKER_PORT);
throw new RuntimeException(
"Missing host or port from env for Docker. host:" + DOCKER_HOST + ", port:" + DOCKER_PORT);
}
}
}
/*
* Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baidu.fsg.uid.utils;
import org.springframework.util.Assert;
/**
* EnumUtils provides the operations for {@link ValuedEnum} such as Parse, value of...
*
* @author yutianbao
*/
public abstract class EnumUtils {
/**
* Parse the bounded value into ValuedEnum
*
* @param clz
* @param value
* @return
*/
public static <T extends ValuedEnum<V>, V> T parse(Class<T> clz, V value) {
Assert.notNull(clz, "clz can not be null");
if (value == null) {
return null;
}
for (T t : clz.getEnumConstants()) {
if (value.equals(t.value())) {
return t;
}
}
return null;
}
/**
* Null-safe valueOf function
*
* @param <T>
* @param enumType
* @param name
* @return
*/
public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) {
if (name == null) {
return null;
}
return Enum.valueOf(enumType, name);
}
}
/*
* Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baidu.fsg.uid.utils;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.lang.ClassUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Named thread in ThreadFactory. If there is no specified name for thread, it
* will auto detect using the invoker classname instead.
*
* @author yutianbao
*/
public class NamingThreadFactory implements ThreadFactory {
private static final Logger LOGGER = LoggerFactory.getLogger(NamingThreadFactory.class);
/**
* Thread name pre
*/
private String name;
/**
* Is daemon thread
*/
private boolean daemon;
/**
* UncaughtExceptionHandler
*/
private UncaughtExceptionHandler uncaughtExceptionHandler;
/**
* Sequences for multi thread name prefix
*/
private final ConcurrentHashMap<String, AtomicLong> sequences;
/**
* Constructors
*/
public NamingThreadFactory() {
this(null, false, null);
}
public NamingThreadFactory(String name) {
this(name, false, null);
}
public NamingThreadFactory(String name, boolean daemon) {
this(name, daemon, null);
}
public NamingThreadFactory(String name, boolean daemon, UncaughtExceptionHandler handler) {
this.name = name;
this.daemon = daemon;
this.uncaughtExceptionHandler = handler;
this.sequences = new ConcurrentHashMap<String, AtomicLong>();
}
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setDaemon(this.daemon);
// If there is no specified name for thread, it will auto detect using the invoker classname instead.
// Notice that auto detect may cause some performance overhead
String prefix = this.name;
if (StringUtils.isBlank(prefix)) {
prefix = getInvoker(2);
}
thread.setName(prefix + "-" + getSequence(prefix));
// no specified uncaughtExceptionHandler, just do logging.
if (this.uncaughtExceptionHandler != null) {
thread.setUncaughtExceptionHandler(this.uncaughtExceptionHandler);
} else {
thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
public void uncaughtException(Thread t, Throwable e) {
LOGGER.error("unhandled exception in thread: " + t.getId() + ":" + t.getName(), e);
}
});
}
return thread;
}
/**
* Get the method invoker's class name
*
* @param depth
* @return
*/
private String getInvoker(int depth) {
Exception e = new Exception();
StackTraceElement[] stes = e.getStackTrace();
if (stes.length > depth) {
return ClassUtils.getShortClassName(stes[depth].getClassName());
}
return getClass().getSimpleName();
}
/**
* Get sequence for different naming prefix
*
* @param invoker
* @return
*/
private long getSequence(String invoker) {
AtomicLong r = this.sequences.get(invoker);
if (r == null) {
r = new AtomicLong(0);
AtomicLong previous = this.sequences.putIfAbsent(invoker, r);
if (previous != null) {
r = previous;
}
}
return r.incrementAndGet();
}
/**
* Getters & Setters
*/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isDaemon() {
return daemon;
}
public void setDaemon(boolean daemon) {
this.daemon = daemon;
}
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
return uncaughtExceptionHandler;
}
public void setUncaughtExceptionHandler(UncaughtExceptionHandler handler) {
this.uncaughtExceptionHandler = handler;
}
}
/*
* Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baidu.fsg.uid.utils;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
/**
* NetUtils
*
* @author yutianbao
*/
public abstract class NetUtils {
/**
* Pre-loaded local address
*/
public static InetAddress localAddress;
static {
try {
localAddress = getLocalInetAddress();
} catch (SocketException e) {
throw new RuntimeException("fail to get local ip.");
}
}
/**
* Retrieve the first validated local ip address(the Public and LAN ip addresses are validated).
*
* @return the local address
* @throws SocketException the socket exception
*/
public static InetAddress getLocalInetAddress() throws SocketException {
// enumerates all network interfaces
Enumeration<NetworkInterface> enu = NetworkInterface.getNetworkInterfaces();
while (enu.hasMoreElements()) {
NetworkInterface ni = enu.nextElement();
if (ni.isLoopback()) {
continue;
}
Enumeration<InetAddress> addressEnumeration = ni.getInetAddresses();
while (addressEnumeration.hasMoreElements()) {
InetAddress address = addressEnumeration.nextElement();
// ignores all invalidated addresses
if (address.isLinkLocalAddress() || address.isLoopbackAddress() || address.isAnyLocalAddress()) {
continue;
}
return address;
}
}
throw new RuntimeException("No validated local address!");
}
/**
* Retrieve local address
*
* @return the string local address
*/
public static String getLocalAddress() {
return localAddress.getHostAddress();
}
}
/*
* Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baidu.fsg.uid.utils;
import java.util.concurrent.atomic.AtomicLong;
/**
* 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() {
super();
}
public PaddedAtomicLong(long initialValue) {
super(initialValue);
}
/**
* To prevent GC optimizations for cleaning unused padded references
*/
public long sumPaddingToPreventOptimization() {
return p1 + p2 + p3 + p4 + p5 + p6;
}
}
\ No newline at end of file
/*
* Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baidu.fsg.uid.utils;
/**
* {@code ValuedEnum} defines an enumeration which is bounded to a value, you
* may implements this interface when you defines such kind of enumeration, that
* you can use {@link EnumUtils} to simplify parse and valueOf operation.
*
* @author yutianbao
*/
public interface ValuedEnum<T> {
T value();
}
/*
* Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baidu.fsg.uid.worker;
import com.baidu.fsg.uid.utils.DockerUtils;
import com.baidu.fsg.uid.utils.NetUtils;
import com.baidu.fsg.uid.worker.dao.WorkerNodeDAO;
import com.baidu.fsg.uid.worker.entity.WorkerNodeEntity;
import org.apache.commons.lang.math.RandomUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
/**
* 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);
@Resource
private WorkerNodeDAO workerNodeDAO;
/**
* 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
*/
@Transactional
public long assignWorkerId() {
// 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();
}
/**
* Build worker node entity by IP and PORT
*/
private WorkerNodeEntity buildWorkerNode() {
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));
}
return workerNodeEntity;
}
}
/*
* Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baidu.fsg.uid.worker;
/**
* Represents a worker id assigner for {@link com.baidu.fsg.uid.impl.DefaultUidGenerator}
*
* @author yutianbao
*/
public interface WorkerIdAssigner {
/**
* Assign worker id for {@link com.baidu.fsg.uid.impl.DefaultUidGenerator}
*
* @return assigned worker id
*/
long assignWorkerId();
}
/*
* Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baidu.fsg.uid.worker;
import com.baidu.fsg.uid.utils.ValuedEnum;
/**
* WorkerNodeType
* <li>CONTAINER: Such as Docker
* <li>ACTUAL: Actual machine
*
* @author yutianbao
*/
public enum WorkerNodeType implements ValuedEnum<Integer> {
CONTAINER(1), ACTUAL(2);
/**
* Lock type
*/
private final Integer type;
/**
* Constructor with field of type
*/
private WorkerNodeType(Integer type) {
this.type = type;
}
@Override
public Integer value() {
return type;
}
}
/*
* Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baidu.fsg.uid.worker.dao;
import com.baidu.fsg.uid.worker.entity.WorkerNodeEntity;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
/**
* DAO for M_WORKER_NODE
*
* @author yutianbao
*/
@Repository
public interface WorkerNodeDAO {
/**
* 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);
}
/*
* Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baidu.fsg.uid.worker.entity;
import java.util.Date;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import com.baidu.fsg.uid.worker.WorkerNodeType;
/**
* Entity for M_WORKER_NODE
*
* @author yutianbao
*/
public class WorkerNodeEntity {
/**
* Entity unique id (table unique)
*/
private long id;
/**
* Type of CONTAINER: HostName, ACTUAL : IP.
*/
private String hostName;
/**
* Type of CONTAINER: Port, ACTUAL : Timestamp + Random(0-10000)
*/
private String port;
/**
* type of {@link WorkerNodeType}
*/
private int type;
/**
* Worker launch date, default now
*/
private Date launchDate = new Date();
/**
* Created time
*/
private Date created;
/**
* Last modified
*/
private Date modified;
/**
* Getters & Setters
*/
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getHostName() {
return hostName;
}
public void setHostName(String hostName) {
this.hostName = hostName;
}
public String getPort() {
return port;
}
public void setPort(String port) {
this.port = port;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public Date getLaunchDate() {
return launchDate;
}
public void setLaunchDateDate(Date launchDate) {
this.launchDate = launchDate;
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
public Date getModified() {
return modified;
}
public void setModified(Date modified) {
this.modified = modified;
}
@Override
public String toString() {
return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.baidu.fsg.uid.config.UIDConfig,\
com.baidu.fsg.uid.service.UidGenService
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
<!-- UID generator -->
<bean id="disposableWorkerIdAssigner" class="com.baidu.fsg.uid.worker.DisposableWorkerIdAssigner" />
<bean id="cachedUidGenerator" class="com.baidu.fsg.uid.impl.CachedUidGenerator">
<property name="workerIdAssigner" ref="disposableWorkerIdAssigner" />
<!-- 以下为可选配置, 如未指定将采用默认值 -->
<!-- Specified bits & epoch as your demand. No specified the default value will be used -->
<!-- 2^28 /(365*24*60*60) = 8年多 -->
<property name="timeBits" value="28"/>
<!-- 最多支持2^22 4194304 次机器启动 -->
<property name="workerBits" value="22"/>
<!-- 每秒支持2^13 8192个并发 -->
<property name="seqBits" value="13"/>
<property name="epochStr" value="2019-08-01"/>
<!-- 以下为可选配置, 如未指定将采用默认值 -->
<!-- RingBuffer size扩容参数, 可提高UID生成的吞吐量. -->
<!-- 默认:3, 原bufferSize=8192, 扩容后bufferSize= 8192 << 3 = 65536 -->
<!--<property name="boostPower" value="3"></property>-->
<!-- 指定何时向RingBuffer中填充UID, 取值为百分比(0, 100), 默认为50 -->
<!-- 举例: bufferSize=1024, paddingFactor=50 -> threshold=1024 * 50 / 100 = 512. -->
<!-- 当环上可用UID数量 < 512时, 将自动对RingBuffer进行填充补全 -->
<!--<property name="paddingFactor" value="50"></property> -->
<!-- 另外一种RingBuffer填充时机, 在Schedule线程中, 周期性检查填充 -->
<!-- 默认:不配置此项, 即不实用Schedule线程. 如需使用, 请指定Schedule线程时间间隔, 单位:秒 -->
<!--<property name="scheduleInterval" value="60"></property>-->
<!-- 拒绝策略: 当环已满, 无法继续填充时 -->
<!-- 默认无需指定, 将丢弃Put操作, 仅日志记录. 如有特殊需求, 请实现RejectedPutBufferHandler接口(支持Lambda表达式) -->
<!--<property name="rejectedPutBufferHandler" ref="XxxxYourPutRejectPolicy"></property>-->
<!-- 拒绝策略: 当环已空, 无法继续获取时 -->
<!-- 默认无需指定, 将记录日志, 并抛出UidGenerateException异常. 如有特殊需求, 请实现RejectedTakeBufferHandler接口(支持Lambda表达式) -->
<!--<property name="rejectedPutBufferHandler" ref="XxxxYourPutRejectPolicy"></property>-->
</bean>
</beans>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.baidu.fsg.uid.worker.dao.WorkerNodeDAO">
<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>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.codesheep</groupId>
<artifactId>test-id-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>test-id-spring-boot-starter</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.9</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Apache Commons -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>cn.codesheep</groupId>
<artifactId>id-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
package cn.codesheep.testidspringbootstarter;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan({"org.zjs.cms","com.baidu.fsg.uid.worker.dao"})
public class TestIdSpringBootStarterApplication {
public static void main(String[] args) {
SpringApplication.run(TestIdSpringBootStarterApplication.class, args);
}
}
package cn.codesheep.testidspringbootstarter.controller;
import com.baidu.fsg.uid.service.UidGenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@Autowired
private UidGenService uidGenService;
@GetMapping("/uid")
public String genUid() {
return String.valueOf(uidGenService.getUid());
}
}
server:
port: 8088
spring:
datasource:
name: dataSource
type: com.alibaba.druid.pool.DruidDataSource
druid:
name: dataSource
url: jdbc:mysql://XXX.XXX.XXX.XXX:3306/demo?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useOldAliasMetadataBehavior=true&connectionCollation=utf8mb4_unicode_ci&rewriteBatchedStatements=true&allowMultiQueries=true
username: XXXXXX
password: XXXXXX
driver-class-name: com.mysql.jdbc.Driver
filters: stat,wall
maxActive: 20
initialSize: 1
maxWait: 60000
minIdle: 1
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: select 'x'
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxOpenPreparedStatements: 20
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000;druid.stat.logSlowSql=true
filter:
stat:
enabled: true
db-type: mysql
merge-sql: true
log-slow-sql: true
slow-sql-millis: 2000
mybatis:
mapper-locations: classpath:mapper/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
\ No newline at end of file
package cn.codesheep.testidspringbootstarter;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestIdSpringBootStarterApplicationTests {
@Test
public void contextLoads() {
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册