UidGenerator 是什么
UidGenerator 是百度开源的一款分布式高性能的唯一 ID 生成器,更详细的情况可以查看官网集成文档
uid-generator 是基于 Twitter 开源的 snowflake 算法实现的一款唯一主键生成器(数据库表的主键要求全局唯一是相当重要的)。要求 java8 及以上版本。
snowflake 算法
Snowflake 算法描述:指定机器 & 同一时刻 & 某一并发序列,是唯一的。据此可生成一个 64 bits 的唯一 ID(long)。
将 long 的 64 位分为 3 部分,时间戳、工作机器 id 和序列号,位数分配如下:
时间戳部分的时间单位一般为毫秒,也就是说 1 台工作机器 1 毫秒可产生 4096 个 id(2 的 12 次方)。
UidGenerator 算法
与原始的 snowflake 算法不同,uid-generator 支持自定义时间戳、工作机器 id 和序列号等各部分的位数,以应用于不同场景。
sign(1bit):固定 1bit 符号标识,即生成的 UID 为正数。
delta seconds (28 bits):当前时间,相对于时间基点"2016-05-20"的增量值,单位:秒,最多可支持约 8.7 年
worker id (22 bits):机器 id,最多可支持约 420w 次机器启动。内置实现为在启动时由数据库分配,默认分配策略为用后即弃,后续可提供复用策略。
sequence (13 bits):每秒下的并发序列,13 bits 可支持每秒 8192 个并发。
这些字段的长度可以根据具体的应用需要进行动态的调整,满足总长度为 64 位即可。
Snowflake 和 UidGenerator 的对比
百度的 worker id 的生成策略和美团的生成策略不太一样,美团的 snowflake 主要利用本地配置的 port 和 IP 来唯一确定一个 workid,美团的这种生成方式还是可以由于手工配置错误造成 port 重复,最终产生重复 ID 的风险,百度的这种生成方式每次都是新增的,可能会一段时间后 worker id 用完的情况,人工配置错误的可能性很小了。
源码分析
DefaultUidGenerator
DefaultUidGenerator 的产生 id 的方法与基本上就是常见的 snowflake 算法实现,仅有一些不同,如以秒为为单位而不是毫秒。DefaultUidGenerator 的产生 id 的方法如下。
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);
}
复制代码
nextId 方法主要负责 ID 的生成,这种实现方式很简单,如果毫秒数未发生变化,在序列号加一即可,毫秒数发生变化,重置 Sequence 为 0(Leaf 文章中讲过,重置为 0 会造成如果利用这个 ID 分表的时候,并发量不大的时候,sequence 字段会一直为 0 等,会出现数据倾斜)
CachedUidGenerator
CachedUidGenerator 支持缓存生成的 id。
基本实现原理
正如名字体现的那样,这是一种缓存型的 ID 生成方式,当剩余 ID 不足的时候,会异步的方式重新生成一批 ID 缓存起来,后续请求的时候直接的时候直接返回现成的 ID 即可。
在实现上, UidGenerator 通过借用未来时间来解决 sequence 天然存在的并发限制; 采用 RingBuffer 来缓存已生成的 UID, 并行化 UID 的生产和消费, 同时对 CacheLine 补齐,避免了由 RingBuffer 带来的硬件级「伪共享」问题. 最终单机 QPS 可达 600 万。
使用 RingBuffer 缓存生成的 id。RingBuffer 是个环形数组,默认大小为 8192 个,里面缓存着生成的 id。
CachedUidGenerator 采用了双 RingBuffer,Uid-RingBuffer 用于存储 Uid、Flag-RingBuffer 用于存储 Uid 状态(是否可填充、是否可消费)
由于数组元素在内存中是连续分配的,可最大程度利用 CPU cache 以提升性能。但同时会带来「伪共享」FalseSharing 问题,为此在 Tail、Cursor 指针、Flag-RingBuffer 中采用了 CacheLine 补齐方式。
获取 id
会从 ringbuffer 中拿一个 id,支持并发获取
@Override
public long getUID() {
try {
return ringBuffer.take();
} catch (Exception e) {
LOGGER.error("Generate unique id exception. ", e);
throw new UidGenerateException(e);
}
}
复制代码
RingBuffer 缓存已生成的 id
RingBuffer 为环形数组,默认容量为 sequence 可容纳的最大值(8192 个),可以通过 boostPower 参数设置大小。几个重要的数据结构,采用了 RingBuffer 的方式来缓存相关 UID 信息。
tail 指针、Cursor 指针用于环形数组上读写 slot:
Tail 指针
指向当前最后一个可用的 UID 位置:表示 Producer 生产的最大序号(此序号从 0 开始,持续递增)。Tail 不能超过 Cursor,即生产者不能覆盖未消费的 slot。当 Tail 已赶上 curosr,此时可通过 rejectedPutBufferHandler 指定 PutRejectPolicy
Cursor 指针
指向下一个获取 UID 的位置,其一定是小于 Tail:表示 Consumer 消费到的最小序号(序号序列与 Producer 序列相同)。Cursor 不能超过 Tail,即不能消费未生产的 slot。当 Cursor 已赶上 tail,此时可通过 rejectedTakeBufferHandler 指定 TakeRejectPolicy
Tail - Cursor 表示的是现在可用的 UID 数量,当可用 UID 数量小于一定阈值的时候会重新添加一批新的 UID 在 RingBuffer 中。
填充 id
因为 delta seconds 部分是以秒为单位的,所以 1 个 worker 1 秒内最多生成的 id 书为 8192 个(2 的 13 次方)。从上可知,支持的最大 qps 为 8192,所以通过缓存 id 来提高吞吐量。
为什么叫借助未来时间?
因为每秒最多生成 8192 个 id,当 1 秒获取 id 数多于 8192 时,RingBuffer 中的 id 很快消耗完毕,在填充 RingBuffer 时,生成的 id 的 delta seconds 部分只能使用未来的时间。(因为使用了未来的时间来生成 id,所以上面说的是,【最多】可支持约 8.7 年)
注意:这里的 RingBuffer 不是 Disruptor 框架中的 RingBuffer,但是借助了很多 Disruptor 中 RingBuffer 的设计思想,比如使用缓存行填充解决伪共享问题。
填充 RingBuffer
/**
* 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) {
//获取生成的id,放到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);
}
复制代码
生成 id(上面代码中的 uidProvider.provide 调用的就是这个方法)
/**
* 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
//这里的实现很取巧
//因为1秒内生成的id是连续的,所以利用第1个id来生成后面的id,而不用频繁调用snowflake算法
long firstSeqUid = bitsAllocator.allocate(currentSecond - epochSeconds, workerId, 0L);
for (int offset = 0; offset < listSize; offset++) {
uidList.add(firstSeqUid + offset);
}
return uidList;
}
复制代码
RingBuffer 的代码
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; //用于标记当前slot的状态,表示可以put一个id进去
private static final long CAN_TAKE_FLAG = 1L; //用于标记当前slot的状态,表示可以take一个id
public static final int DEFAULT_PADDING_PERCENT = 50; //用于控制何时填充slots的默认阈值:当剩余的可用的slot的个数,小于bufferSize的50%时,需要生成id将slots填满
/** The size of RingBuffer's slots, each slot hold a UID */
private final int bufferSize; //slots的大小,默认为sequence可容量的最大值,即8192个
private final long indexMask;
private final long[] slots; //slots用于缓存已经生成的id
private final PaddedAtomicLong[] flags; //flags用于存储id的状态(是否可填充、是否可消费)
/** Tail: last position sequence to produce */
//Tail指针
//表示Producer生产的最大序号(此序号从0开始,持续递增)。Tail不能超过Cursor,即生产者不能覆盖未消费的slot。当Tail已赶上curosr,此时可通过rejectedPutBufferHandler指定PutRejectPolicy
private final AtomicLong tail = new PaddedAtomicLong(START_POINT); //
/** Cursor: current position sequence to consume */
//表示Consumer消费到的最小序号(序号序列与Producer序列相同)。Cursor不能超过Tail,即不能消费未生产的slot。当Cursor已赶上tail,此时可通过rejectedTakeBufferHandler指定TakeRejectPolicy
private final AtomicLong cursor = new PaddedAtomicLong(START_POINT);
/** Threshold for trigger padding buffer*/
private final int paddingThreshold; //用于控制何时填充slots的阈值
/** Reject put/take buffer handle policy */
//当slots满了,无法继续put时的处理策略。默认实现:无法进行put,仅记录日志
private RejectedPutBufferHandler rejectedPutHandler = this::discardPutBuffer;
//当slots空了,无法继续take时的处理策略。默认实现:仅抛出异常
private RejectedTakeBufferHandler rejectedTakeHandler = this::exceptionRejectedTakeBuffer;
/** Executor of padding buffer */
//用于运行【生成id将slots填满】任务
private BufferPaddingExecutor bufferPaddingExecutor;
复制代码
代码层面的优化
代码中通过字节的填充,来避免伪共享的产生。
多核处理器处理相互独立的变量时,一旦这些变量处于同一个缓存行,不同变量的操作均会造成这一个缓存行失效,影响缓存的实际效果,造成很大的缓存失效的性能问题。下面图中线程处理不同的两个变量,但这两个变量的修改都会造成整个整个缓存行的失效,导致无效的加载、失效,出现了伪共享的问题
RingBuffer 中通过定义一个 PaddedAtomicLong 来独占一个缓存行,代码中的实现填充可能需要根据具体的执行系统做一些调整,保证其独占一个缓存行即可。
take 先关 id 的源码
下面我们来看下如何获取相关的 UID
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;
}
复制代码
通过 AtomicLong.updateAndGet 来避免对整个方法进行加锁,获取一个可以访问的 UID 的游标值,根据这个下标获取 slots 中相关的 uid 直接返回缓存中可用的 uid(Tail - Cursor)小于一定阈值的时候,需要启动另外一个线程来生成一批 UIDUID 的生成
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;
}
复制代码
获取 Tail 的下标值,如果缓存区满的话直接调用 RejectedPutHandler.rejectPutBuffer 方法未满的话将 UID 放置在 slots 数组相应的位置上,同时将 Flags 数组相应的位置改为 CAN_TAKE_FLAGCachedUidGenerator 通过缓存的方式预先生成一批 UID 列表,可以解决 UID 获取时候的耗时,但这种方式也有不好点,一方面需要耗费内存来缓存这部分数据,另外如果访问量不大的情况下,提前生成的 UID 中的时间戳可能是很早之前的,DefaultUidGenerator 应该在大部分的场景中就可以满足相关的需求了。
填充缓存行解决“伪共享”
关于伪共享,可以参考这篇文章《伪共享(false sharing),并发编程无声的性能杀手》
//数组在物理上是连续存储的,flags数组用来保存id的状态(是否可消费、是否可填充),在填入id和消费id时,会被频繁的修改。
//如果不进行缓存行填充,会导致频繁的缓存行失效,直接从内存中读数据。
private final PaddedAtomicLong[] flags;
//tail和cursor都使用缓存行填充,是为了避免tail和cursor落到同一个缓存行上。
/** 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)
复制代码
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() {
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;
}
}
复制代码
Spring Boot 工程集成全局唯一 ID 生成器 UidGenerator
基础工程创建
官网集成文档
创建数据表
执行如下 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;
复制代码
在使用的数据库中创建表 WORKER_NODE。(如果数据库版本较低,需要将 TIMESTAMP 类型换成 datetime(3),一劳永逸的做法就是直接将 TIMESTAMP 换成 datetime(3))
引入 Maven 依赖
<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>
<!--必须放在最后-->
<dependency>
<groupId>com.baidu.fsg</groupId>
<artifactId>uid-generator</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
复制代码
互联网 jar 包引入(本文用的是此方式)
在 maven 仓库只找到了一个 jar 包。
<dependency>
<groupId>com.xfvape.uid</groupId>
<artifactId>uid-generator</artifactId>
<version>0.0.4-RELEASE</version>
</dependency>
复制代码
排除冲突的依赖
uid-generator 中依赖了 logback 和 mybatis。一般在项目搭建过程中,springboot 中已经有了 logback 依赖,mybatis 会作为单独的依赖引入。如果版本和 uid-generator 中的依赖不一致的话,就会导致冲突。为了防止出现这些问题,直接排除一劳永逸。
<dependency>
<groupId>com.baidu.fsg</groupId>
<artifactId>uid-generator</artifactId>
<version>1.0.0-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
复制代码
排除冲突的依赖如下:(使用本地项目引入的方式也需要排除以下依赖)
<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>
复制代码
我这里用的是 mybatis-plus,mybatis-plus 官方要求的是,如果要使用 mybatis-plus,就不能再单独引入 mybatis 了,所以我这里也是必须排除 mybatis 的。
配置 SpringBoot 核心配置
修改配置文件 application.properties(注意 MySQL 地址、数据库名称账户等于之前建表的保持一致)
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=true
复制代码
@MapperScan 的 dao 层接口扫描:
@MapperScan({"com.xxx.xx.dao"})
复制代码
核心对象装配为 spring 的 bean。
uid-generator 提供了两种生成器: DefaultUidGenerator、CachedUidGenerator。
如对 UID 生成性能有要求, 请使用 CachedUidGenerator。这里装配 CachedUidGenerator,DefaultUidGenerator 装配方式是一样的。
自定义 DisposableWorkerIdAssigner
将源码 DisposableWorkerIdAssigner 类加入到自己的项目中,并将其中的 mapper 方法修改成自己项目中的方法与启动类同级目录新建 DisposableWorkerIdAssigner 内容如下
/**
* 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() {
// 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() {
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;
}
}
复制代码
/**
* <p> 百度 Uid-Generator配置
* @author zepal
* */
@Configuration
public class UidGeneratorConfig {
@Bean("disposableWorkerIdAssigner")
public DisposableWorkerIdAssigner disposableWorkerIdAssigner(){
DisposableWorkerIdAssigner disposableWorkerIdAssigner = new DisposableWorkerIdAssigner();
return disposableWorkerIdAssigner;
}
@Bean("cachedUidGenerator")
public CachedUidGenerator initCachedUidGenerator(WorkerIdAssigner workerIdAssigner) {
CachedUidGenerator cachedUidGenerator = new CachedUidGenerator();
cachedUidGenerator.setWorkerIdAssigner(workerIdAssigner);
// 属性参考链接 https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md
// 以下为可选配置, 如未指定将采用默认值
// cachedUidGenerator.setTimeBits(28);
// cachedUidGenerator.setWorkerBits(22);
// cachedUidGenerator.setSeqBits(13);
// cachedUidGenerator.setEpochStr("2016-09-20");
cachedUidGenerator.setBoostPower(3);
cachedUidGenerator.setPaddingFactor(50);
cachedUidGenerator.setScheduleInterval(60L);
// // 拒绝策略: 当环已满, 无法继续填充时
// 默认无需指定, 将丢弃Put操作, 仅日志记录. 如有特殊需求, 请实现RejectedPutBufferHandler接口(支持Lambda表达式)
// 拒绝策略: 当环已空, 无法继续获取时
// 默认无需指定, 将记录日志, 并抛出UidGenerateException异常. 如有特殊需求, 请实现RejectedTakeBufferHandler接口(支持Lambda表达式)
return cachedUidGenerator;
}
}
复制代码
详细配置信息控制
/**
* disposableWorkerIdAssigner的入参对象类型最好使用 WorkerIdAssigner,
* 否则其他地方引入CGLib动态代理的时候可能会导致代理混用的问题
*
* @param disposableWorkerIdAssigner
* @return
*/
@Bean
public DefaultUidGenerator defaultUidGenerator(WorkerIdAssigner disposableWorkerIdAssigner) {
DefaultUidGenerator defaultUidGenerator = new DefaultUidGenerator();
defaultUidGenerator.setWorkerIdAssigner(disposableWorkerIdAssigner);
/**
* 关于UID比特分配的建议:
* 对于并发数要求不高、期望长期使用的应用, 可增加timeBits位数, 减少seqBits位数.
* 例如节点采取用完即弃的WorkerIdAssigner策略, 重启频率为12次/天, 那么配置成:
* {"workerBits":23,"timeBits":31,"seqBits":9}时, 可支持28个节点以整体并发量14400 UID/s的速度持续运行68年.
*
* 对于节点重启频率频繁、期望长期使用的应用, 可增加workerBits和timeBits位数, 减少seqBits位数.
* 例如节点采取用完即弃的WorkerIdAssigner策略, 重启频率为24*12次/天, 那么配置成:
* {"workerBits":27,"timeBits":30,"seqBits":6}时, 可支持37个节点以整体并发量2400 UID/s的速度持续运行34年.
*/
//以下为可选配置, 如未指定将采用默认值
defaultUidGenerator.setTimeBits(32);
// 机器id,最多可支持2^22约420w次机器启动。内置实现为在启动时由数据库分配,默认分配策略为用后即弃,后续可提供复用策略。
defaultUidGenerator.setWorkerBits(22);
// 每秒下的并发序列,9 bits可支持每台服务器每秒512个并发。
defaultUidGenerator.setSeqBits(9);
defaultUidGenerator.setEpochStr("2020-01-01");
return defaultUidGenerator;
}
/**
* disposableWorkerIdAssigner的入参对象类型最好使用 WorkerIdAssigner,
* 否则其他地方引入CGLib动态代理的时候可能会导致代理混用的问题
*
* @param disposableWorkerIdAssigner
* @return
*/
@Bean
public CachedUidGenerator cachedUidGenerator(WorkerIdAssigner disposableWorkerIdAssigner) {
CachedUidGenerator cachedUidGenerator = new CachedUidGenerator();
cachedUidGenerator.setWorkerIdAssigner(disposableWorkerIdAssigner);
/**
* 关于UID比特分配的建议:
* 对于并发数要求不高、期望长期使用的应用, 可增加timeBits位数, 减少seqBits位数.
* 例如节点采取用完即弃的WorkerIdAssigner策略, 重启频率为12次/天, 那么配置成:
* {"workerBits":23,"timeBits":31,"seqBits":9}时, 可支持28个节点以整体并发量14400 UID/s的速度持续运行68年.
*
* 对于节点重启频率频繁、期望长期使用的应用, 可增加workerBits和timeBits位数, 减少seqBits位数.
* 例如节点采取用完即弃的WorkerIdAssigner策略, 重启频率为24*12次/天, 那么配置成:
* {"workerBits":27,"timeBits":30,"seqBits":6}时, 可支持37个节点以整体并发量2400 UID/s的速度持续运行34年.
*/
//以下为可选配置, 如未指定将采用默认值
cachedUidGenerator.setTimeBits(32);
// 机器id,最多可支持2^22约420w次机器启动。内置实现为在启动时由数据库分配,默认分配策略为用后即弃,后续可提供复用策略。
cachedUidGenerator.setWorkerBits(22);
// 每秒下的并发序列,9 bits可支持每台服务器每秒512个并发。
cachedUidGenerator.setSeqBits(9);
cachedUidGenerator.setEpochStr("2020-01-01");
//RingBuffer size扩容参数, 可提高UID生成的吞吐量
//默认:3, 原bufferSize=8192, 扩容后bufferSize= 8192 << 3 = 65536
cachedUidGenerator.setBoostPower(3);
// 指定何时向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线程时间间隔, 单位:秒
cachedUidGenerator.setScheduleInterval(60L);
//拒绝策略: 当环已满, 无法继续填充时
//默认无需指定, 将丢弃Put操作, 仅日志记录. 如有特殊需求, 请实现RejectedPutBufferHandler接口(支持Lambda表达式)
//<property name="rejectedPutBufferHandler" ref="XxxxYourPutRejectPolicy"></property>
//cachedUidGenerator.setRejectedPutBufferHandler();
//拒绝策略: 当环已空, 无法继续获取时 -->
//默认无需指定, 将记录日志, 并抛出UidGenerateException异常. 如有特殊需求, 请实现RejectedTakeBufferHandler接口(支持Lambda表达式) -->
//<property name="rejectedTakeBufferHandler" ref="XxxxYourTakeRejectPolicy"></property>
return cachedUidGenerator;
}
复制代码
mapper 服务接口
与启动类同级目录新建 WorkerNodeMapper 内容如下
@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);
}
复制代码
WorkerNodeMapper
<?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="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>
复制代码
创建 UidGenService 逻辑类
@Service
public class UidGenService {
@Resource
private UidGenerator uidGenerator;
public long getUid() {
return uidGenerator.getUID();
}
}
复制代码
参考资料
一个Java对象到底占用多大内存?
写Java也得了解CPU--伪共享https://www.cnblogs.com/techyc/p/3625701.html
https://www.jianshu.com/p/947bff7be2da
https://blog.csdn.net/qq_43578870/article/details/105495740
评论