likes
comments
collection
share

高并发系统-分布式唯一ID生成(五)-百度UidGenerator

作者站长头像
站长
· 阅读数 8

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算法

高并发系统-分布式唯一ID生成(五)-百度UidGenerator

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. 优点

  1. 高性能:uid-generator通过使用RingBuffer和高效的算法实现了高性能的分布式ID生成。它使用了预先分配的ID池来避免每次请求都需要访问数据库或网络,从而提高了生成ID的速度。
  2. 高可用性:uid-generator支持多实例部署,并通过使用Snowflake算法和分布式的WorkerID分配来确保生成的ID在多实例环境下的唯一性。
  3. 灵活的配置:uid-generator提供了多种配置选项,如时间位数、WorkerID位数、序列号位数等,可以根据需求进行调整和优化。
  4. 易于集成:uid-generator提供了与Spring框架和MyBatis集成的支持,可以方便地与现有的Java应用程序进行集成。

2. 缺点

  1. DefaultUidGenerator依赖于机器时钟:雪花算法的实现
  2. 需要预先分配ID范围:为了提高性能,uid-generator需要预先分配ID范围,这可能会导致一些ID浪费。如果系统的ID使用率很低,可能会浪费更多的ID。
  3. 依赖于数据库或配置中心:uid-generator的WorkerID分配依赖于数据库或配置中心,这可能增加了一些外部依赖性,并可能成为系统的瓶颈。
  4. 不适用于大规模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;

从官网下载代码,继承到项目中去,下面红框中

高并发系统-分布式唯一ID生成(五)-百度UidGenerator

增加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详细介绍可以参考官网说明,这里不在详细阐述。

参考: uid-generator UidGenerator:百度开源的分布式ID服务(解决了时钟回拨问题)