号段模式ID生成器组件地址 https://github.com/15928587230/os-component-idworker
开箱即用, github上面有简洁的使用说明。

一 Leaf号段模式设计

通过数据库方式生成ID之传统模式

通过数据库生成ID之号段模式

Leaf号段模式

二 Leaf号段模式解析

  • 初始化数据源和Dao层
    @Bean@Order(1)@ConditionalOnMissingBean({IdGen.class})public IdGen idGen() {DruidDataSource dataSource = new DruidDataSource();dataSource.setUrl(properties.getJdbcUrl());dataSource.setUsername(properties.getUsername());dataSource.setPassword(properties.getPassword());try {dataSource.init();} catch (SQLException exception) {exception.printStackTrace();}TransactionFactory transactionFactory = new JdbcTransactionFactory();Environment environment = new Environment("development", transactionFactory, dataSource);org.apache.ibatis.session.Configuration configuration =new org.apache.ibatis.session.Configuration(environment);configuration.addMapper(SegmentMapper.class);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);SegmentDao segmentDao = new SegmentDao();segmentDao.setSqlSessionFactory(sqlSessionFactory);SegmentIdGenImpl idGen = new SegmentIdGenImpl();idGen.setSegmentDao(segmentDao);// init bufferidGen.init();return idGen;}
  • idGen.init() 方法初始化缓存
    @Overridepublic void init() {updateIdCacheFromDB();scheduledUpdateIdCacheFromDB();idCacheInitOk = true;logger.info("IdCache initialized...");}protected void updateIdCacheFromDB() {List<String> insertTas = segmentDao.getAllTags();if (insertTas == null || insertTas.size() == 0) {return;}Set<String> curTags = cache.keySet();List<String> dbTags = new ArrayList<>(insertTas);Set<String> cacheTags = new HashSet<>(curTags);insertTas.removeAll(curTags);for (String insertTag : insertTas) {// 这里初始化SegmentBuffer、里面默认两个号段Segment
//    public SegmentBuffer() {//        segments = new Segment[] {new Segment(), new Segment()};
//        currentPos = 0;
//        initOk = false;
//        lock = new ReentrantReadWriteLock();
//        nextReady = false;
//        atomicBoolean = new AtomicBoolean(false);
//    }SegmentBuffer segmentBuffer = new SegmentBuffer();segmentBuffer.setTag(insertTag);Segment segment = segmentBuffer.getCurrent();segment.setMaxId(0);segment.setStep(0);segment.setValue(new AtomicLong(0));cache.put(insertTag, segmentBuffer);}// 删除数据库没有的TagscacheTags.removeAll(dbTags);for (String removeTag : cacheTags) {if (cache.containsKey(removeTag)) {cache.remove(removeTag);}}}protected void scheduledUpdateIdCacheFromDB() {ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(r -> {Thread t = new Thread(r);t.setName("Update-IdCache-Thread");t.setDaemon(true);return t;});service.scheduleWithFixedDelay(() -> updateIdCacheFromDB(), 60, 300, TimeUnit.SECONDS);}
  • 获取ID逻辑
  • 第一次先从数据库刷新最新的Segment号段
    @Overridepublic Long genId(String bizTag) {if (!idCacheInitOk) {throw new RuntimeException("IdCache is not ready.");}if (cache.containsKey(bizTag)) {SegmentBuffer segmentBuffer = cache.get(bizTag);if (!segmentBuffer.isInitOk()) {synchronized (segmentBuffer) {if (!segmentBuffer.isInitOk()) {updateSegmentBufferFromDB(segmentBuffer.getTag(), segmentBuffer.getCurrent());segmentBuffer.setInitOk(true);}}}return getIdFromSegmentBuffer(segmentBuffer);}throw new RuntimeException("SegmentBuffer is not ready.");}protected void updateSegmentBufferFromDB(String tag, Segment current) {// 每次更新maxId,获取一个新的号段try {// 从数据库获取最新的号段放到缓存中的segment中Segment segment = segmentDao.updateMaxIdAndGetSegment(tag);current.setBizTag(segment.getBizTag());current.setMaxId(segment.getMaxId());current.setStep(segment.getStep());current.getValue().set(current.getMaxId() - current.getStep());} catch (Exception e) {logger.warn("update segment {} from db {}", tag, current);}}
  • 获取ID以及双Segment设计
   protected Long getIdFromSegmentBuffer(final SegmentBuffer buffer) {while (true) {buffer.rLock().lock();try {final Segment segment = buffer.getCurrent();// 懒加载第二个Segment// 如果nextSegment未准备好,其它线程阻塞,只允许一个线程进来预加载segmentif (!buffer.isNextReady()&& segment.getIdle() < 0.5 * segment.getStep()&& buffer.getAtomicBoolean().compareAndSet(false, true)) {service.execute(() -> {Segment next = buffer.getSegments()[buffer.nextPos()];boolean updateOK = false;try {updateSegmentBufferFromDB(buffer.getTag(), next);updateOK = true;} catch (Exception e) {throw new RuntimeException(e.getCause());} finally {if (updateOK) {// 该代码必须和下面的buffer.switchPos互斥,因此需要单开线程池利用buffer的写锁互斥功能buffer.wLock().lock();buffer.setNextReady(true);buffer.getAtomicBoolean().set(false);buffer.wLock().unlock();} else {buffer.getAtomicBoolean().set(false);}}});}long value = segment.getValue().getAndIncrement();if (value < segment.getMaxId()) {return value;}} finally {buffer.rLock().unlock();}//假如处于segment预加载中,则等加载完毕waitAndSleep(buffer);// 同时只能有一个线程进行切换buffer.wLock().lock();try {final Segment segment = buffer.getCurrent();long value = segment.getValue().getAndIncrement();if (value < segment.getMaxId()) {return value;}if (buffer.isNextReady()) {buffer.switchPos();buffer.setNextReady(false);} else {// 如果到这里,说明双buff不够用了,需要调整buffer中Id号码个数,加大steplogger.warn("Both segment is not ready, step must be increased!");}} finally {buffer.wLock().unlock();}}}protected void waitAndSleep(SegmentBuffer buffer) {int roll = 0;while (buffer.getAtomicBoolean().get()) {roll += 1;if (roll > 10000) {try {TimeUnit.MILLISECONDS.sleep(10);break;} catch (InterruptedException e) {logger.warn("Thread {} Interrupted",Thread.currentThread().getName());break;}}}}

三 使用方式

一 创建数据库和表

CREATE DATABASE IF NOT EXISTS segment;
USE segment;
CREATE TABLE `segment`
(`biz_tag`     varchar(128) NOT NULL DEFAULT '', -- your biz unique name`max_id`      bigint(20) NOT NULL DEFAULT '1',`step`        int(11) NOT NULL,`description` varchar(256)          DEFAULT NULL,`update_time` timestamp    NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (`biz_tag`)
) ENGINE=InnoDB;insert into segment(biz_tag, max_id, step, description)
-- 初始化业务模块和号段
values ('segment-test', 1, 10, '系统测试')

二 配置yml

不会在容器中创建数据源,操作segment表的配置在IdWorkerConfigure配置类中

component:idworker:enabled: truejdbc-url: jdbc:mysql://127.0.0.1:3306/segment?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false&nullCatalogMeansCurrent=true&useInformationSchema=falseusername: rootpassword: qwer1234

三 并发测试

模拟1000个线程共获取100000个Id,最后查看数据库maxId能否对应上

@RunWith(SpringRunner.class)
@SpringBootTest
public class OsComponentIdWorkerApplicationTests {@Autowiredprivate SegmentService segmentService;@Testpublic void contextLoads() throws InterruptedException {CountDownLatch countDownLatch = new CountDownLatch(1000);for (int i = 0; i < 1000; i++) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {try {countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}for (int j = 0; j < 100; j++) {Long test = segmentService.getSegmentId("segment-test");System.out.println(test);}}});thread.setName("" + i);thread.start();countDownLatch.countDown();}Thread.sleep(30000);}
}

ID生成方案之号段模式相关推荐

  1. 分布式系统概念 | 分布式ID:数据库、号段模式、雪花算法(Snowflake)、Redis实现方案

    文章目录 分布式ID 数据库 自增ID 多主模式 号段模式 雪花算法 Redis 总结 分布式ID ID是数据的唯一标识,传统的做法是使用数据库的自增ID,但是随着业务规模的不断发展,数据量将越来越大 ...

  2. 美团开源分布式ID生成系统——Leaf源码阅读笔记(Leaf的号段模式)

    Leaf 最早期需求是各个业务线的订单ID生成需求.在美团早期,有的业务直接通过DB自增的方式生成ID,有的业务通过redis缓存来生成ID,也有的业务直接用UUID这种方式来生成ID.以上的方式各自 ...

  3. 基于号段模式、百度UID实现的分布式ID生成器kylin-id

    1.简介 1.1.开源项目 kylin-id:麒麟分布式id生成器,支持号段模式.雪花算法 并未发布jar到中央仓库,需要自己本地构建 1.2.介绍 参考滴滴[tinyid] 整合百度[UID] 麒麟 ...

  4. 面试官:你会几种分布式 ID 生成方案???

    1. 为什么需要分布式 ID 对于单体系统来说,主键 ID 常用主键自动的方式进行设置.这种 ID 生成方法在单体项目是可行的,但是对于分布式系统,分库分表之后就不适应了.比如订单表数据量太大了,分成 ...

  5. 深度解析leaf分布式id生成服务源码(号段模式)

    原创不易,转载请注明出处 文章目录 前言 1.实现原理推演 1.1 基于mysql最简单分布式ID实现 1.2 flickr分布式id解决方案 1.3 号段+mysql 2.源码剖析 2.1初始化 2 ...

  6. easyui treegrid获取父节点的id_超简单的分布式ID生成方案!美团开源框架介绍

    目录 阐述背景 Leaf snowflake 模式介绍 Leaf segment 模式介绍 Leaf 改造支持 RPC 阐述背景 不吹嘘,不夸张,项目中用到 ID 生成的场景确实挺多.比如业务要做幂等 ...

  7. 超简单的分布式ID生成方案!美团开源框架介绍

    目录 阐述背景 Leaf snowflake 模式介绍 Leaf segment 模式介绍 Leaf 改造支持 RPC 阐述背景 不吹嘘,不夸张,项目中用到 ID 生成的场景确实挺多.比如业务要做幂等 ...

  8. 分库分表的 9种分布式主键ID 生成方案

    <sharding-jdbc 分库分表的 4种分片策略> 中我们介绍了 sharding-jdbc 4种分片策略的使用场景,可以满足基础的分片功能开发,这篇我们来看看分库分表后,应该如何为 ...

  9. 分库分表的 9种分布式主键ID 生成方案,挺全乎的

    <sharding-jdbc 分库分表的 4种分片策略> 中我们介绍了 sharding-jdbc 4种分片策略的使用场景,可以满足基础的分片功能开发,这篇我们来看看分库分表后,应该如何为 ...

最新文章

  1. 集合已修改;可能无法执行枚举操作。
  2. 我自学python的路-Python的学习路经
  3. 多级菜单栏展开隐藏动画
  4. Python之方法调用
  5. js 兼容event.target
  6. 限制nginx仅能域名访问,不可用ip访问
  7. 汇编语言-015(PROC伪指令定义参数方式、EXTERNDEF、INCLUDE 、EXTERN 、INVOKE、PROC、PROTO 、MOVSB 、MOVSD 、CMPSD )
  8. markdown改字体和背景颜色(html)
  9. Windows下安装python2和python3双版本
  10. Puppet 命令参数介绍(三)
  11. [cloud][sdn] neutron了解
  12. feign返回结果统一处理_SpringCloud异常处理统一封装我来做-使用篇
  13. python+pymssql+selenium 获取短信验证码登录(实战练习)
  14. 正交试验设计例题及答案_SPSS正交试验设计及其方差分析
  15. 一周信创舆情观察(11.15~11.21)
  16. 有哪些让人相见恨晚的记笔记方法?
  17. 华为SmartKit工具下载
  18. 安卓linux开机画面,安卓开机画面_安卓开机动画修改_安卓开机动画修改器-Guide信息网...
  19. kingcms php 下载,KingCMS企业版(PHP)
  20. 【VMware环境下Linux磁盘空间(LVM)扩容方法】

热门文章

  1. 对偶理论和灵敏度分析(单纯形法矩阵形式、对偶理论及转化、影子价格、机会成本、差额成本、对偶单纯形法、灵敏度分析)
  2. 自己搭建sles的源,nginx+iso
  3. ubuntu安装rvm
  4. LPWA通信费仅需传统10% 成推动物联网重要关键
  5. 1_28_python基础学习_0427
  6. 网易雷火 2020暑期实习 面经
  7. 合纵连横:手机中国联盟在上海成立
  8. 个人MarkDown语法学习
  9. WiFi-802.11 协商速率表
  10. 人工智能-作业1:PyTorch实现反向传播