高并发系统-分布式唯一ID生成(五)-百度UidGenerator
2. 生成方案
2.7 百度-UidGenerator
UidGenerator是Java实现的, 基于Snowflake算法的唯一ID生成器。 UidGenerator以组件形式工作在应用项目中, 支持自定义workerId位数和初始化策略, 从而适用于docker等虚拟化环境下实例自动重启、漂移等场景。 在实现上, UidGenerator通过借用未来时间来解决sequence天然存在的并发限制; 采用RingBuffer来缓存已生成的UID, 并行化UID的生产和消费, 同时对CacheLine补齐,避免了由RingBuffer带来的硬件级「伪共享」问题. 最终单机QPS可达600万。
组件方式集成:下载对应源代码集成到实际使用代码中。
依赖版本:Java8及以上版本, MySQL(内置WorkerID分配器, 启动阶段通过DB进行分配; 如自定义实现
, 则DB非必选依赖)
Snowflake算法
Snowflake算法描述:指定机器 & 同一时刻 & 某一并发序列,是唯一的。据此可生成一个64 bits的唯一ID(long)。默认采用上图字节分配方式:
- 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个并发。
以上参数均可通过Spring进行自定义
1. 优点
- 高性能:uid-generator通过使用RingBuffer和高效的算法实现了高性能的分布式ID生成。它使用了预先分配的ID池来避免每次请求都需要访问数据库或网络,从而提高了生成ID的速度。
- 高可用性:uid-generator支持多实例部署,并通过使用Snowflake算法和分布式的WorkerID分配来确保生成的ID在多实例环境下的唯一性。
- 灵活的配置:uid-generator提供了多种配置选项,如时间位数、WorkerID位数、序列号位数等,可以根据需求进行调整和优化。
- 易于集成:uid-generator提供了与Spring框架和MyBatis集成的支持,可以方便地与现有的Java应用程序进行集成。
2. 缺点
- DefaultUidGenerator依赖于机器时钟:雪花算法的实现
- 需要预先分配ID范围:为了提高性能,uid-generator需要预先分配ID范围,这可能会导致一些ID浪费。如果系统的ID使用率很低,可能会浪费更多的ID。
- 依赖于数据库或配置中心:uid-generator的WorkerID分配依赖于数据库或配置中心,这可能增加了一些外部依赖性,并可能成为系统的瓶颈。
- 不适用于大规模ID生成:虽然uid-generator在一般场景下具有良好的性能,但在需要非常高并发和大规模ID生成的场景下,可能会面临一些挑战。在这种情况下,可能需要考虑更复杂的ID生成方案。
3. 应用场景
集成简单,可以应用在并发不是极高的场景下
4. 实践
以下是基于spring boot + mybatics的实现
预制建表SQL
use point_shard1;
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;
从官网下载代码,继承到项目中去,下面红框中
增加bean配置
@Configuration
public class UidConfig {
@Bean(name = "defaultUidGenerator")
public DefaultUidGenerator defaultUidGenerator(DisposableWorkerIdAssigner disposableWorkerIdAssigner) {
DefaultUidGenerator uidGenerator = new DefaultUidGenerator();
uidGenerator.setWorkerIdAssigner(disposableWorkerIdAssigner);
uidGenerator.setTimeBits(29);
uidGenerator.setWorkerBits(21);
uidGenerator.setSeqBits(13);
uidGenerator.setEpochStr("2016-09-20");
return uidGenerator;
}
@Bean(name = "cachedUidGenerator")
public CachedUidGenerator cachedUidGenerator(DisposableWorkerIdAssigner disposableWorkerIdAssigner) {
CachedUidGenerator uidGenerator = new CachedUidGenerator();
uidGenerator.setWorkerIdAssigner(disposableWorkerIdAssigner);
uidGenerator.setTimeBits(29);
uidGenerator.setWorkerBits(21);
uidGenerator.setSeqBits(13);
uidGenerator.setEpochStr("2016-09-20");
uidGenerator.setBoostPower(3);
uidGenerator.setPaddingFactor(50);
uidGenerator.setScheduleInterval(60);
// uidGenerator.setRejectedPutBufferHandler(...); // 根据需求设置拒绝策略
// uidGenerator.setRejectedTakeBufferHandler(...); // 根据需求设置拒绝策略
return uidGenerator;
}
@Bean
public DisposableWorkerIdAssigner disposableWorkerIdAssigner() {
return new DisposableWorkerIdAssigner();
}
}
defaultUidGenerator:基于模式雪花算法,发生时钟回拨直接抛异常 cachedUidGenerator:解决了时钟回拨问题,解决方法如下:
- 自增列:UidGenerator的workerId在实例每次重启时初始化,且就是数据库的自增ID,从而完美的实现每个实例获取到的workerId不会有任何冲突。
- RingBuffer:UidGenerator不再在每次取ID时都实时计算分布式ID,而是利用RingBuffer数据结构预先生成若干个分布式ID并保存。
- 时间递增:传统的雪花算法实现都是通过System.currentTimeMillis()来获取时间并与上一次时间进行比较,这样的实现严重依赖服务器的时间。而UidGenerator的时间类型是
AtomicLong,且通过incrementAndGet()方法获取下一次的时间,从而脱离了对服务器时间的依赖,也就不会有时钟回拨的问题
单侧用例如下:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = PointApplication.class)
@Slf4j
public class CachedUidGeneratorTest {
@Resource(name = "defaultUidGenerator")
private UidGenerator defaultUidGenerator;
@Resource(name = "cachedUidGenerator")
private UidGenerator cachedUidGenerator;
@Test
public void testSerialGenerate01() {
// Generate UID
long uid = defaultUidGenerator.getUID();
// Parse UID into [Timestamp, WorkerId, Sequence]
// {"UID":"180363646902239241","parsed":{ "timestamp":"2017-01-19 12:15:46", "workerId":"4", "sequence":"9" }}
System.out.println(defaultUidGenerator.parseUID(uid));
}
@Test
public void testSerialGenerate02() {
// Generate UID
long uid = cachedUidGenerator.getUID();
// Parse UID into [Timestamp, WorkerId, Sequence]
// {"UID":"180363646902239241","parsed":{ "timestamp":"2017-01-19 12:15:46", "workerId":"4", "sequence":"9" }}
System.out.println(cachedUidGenerator.parseUID(uid));
}
}
执行结果
{"UID":"3942331703401390080","timestamp":"2023-12-28 22:45:07","workerId":"1","sequence":"0"}
针对CachedUidGenerator
详细介绍可以参考官网说明,这里不在详细阐述。
转载自:https://juejin.cn/post/7317518562083029030