ID生成方案之号段模式
号段模式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生成方案之号段模式相关推荐
- 分布式系统概念 | 分布式ID:数据库、号段模式、雪花算法(Snowflake)、Redis实现方案
文章目录 分布式ID 数据库 自增ID 多主模式 号段模式 雪花算法 Redis 总结 分布式ID ID是数据的唯一标识,传统的做法是使用数据库的自增ID,但是随着业务规模的不断发展,数据量将越来越大 ...
- 美团开源分布式ID生成系统——Leaf源码阅读笔记(Leaf的号段模式)
Leaf 最早期需求是各个业务线的订单ID生成需求.在美团早期,有的业务直接通过DB自增的方式生成ID,有的业务通过redis缓存来生成ID,也有的业务直接用UUID这种方式来生成ID.以上的方式各自 ...
- 基于号段模式、百度UID实现的分布式ID生成器kylin-id
1.简介 1.1.开源项目 kylin-id:麒麟分布式id生成器,支持号段模式.雪花算法 并未发布jar到中央仓库,需要自己本地构建 1.2.介绍 参考滴滴[tinyid] 整合百度[UID] 麒麟 ...
- 面试官:你会几种分布式 ID 生成方案???
1. 为什么需要分布式 ID 对于单体系统来说,主键 ID 常用主键自动的方式进行设置.这种 ID 生成方法在单体项目是可行的,但是对于分布式系统,分库分表之后就不适应了.比如订单表数据量太大了,分成 ...
- 深度解析leaf分布式id生成服务源码(号段模式)
原创不易,转载请注明出处 文章目录 前言 1.实现原理推演 1.1 基于mysql最简单分布式ID实现 1.2 flickr分布式id解决方案 1.3 号段+mysql 2.源码剖析 2.1初始化 2 ...
- easyui treegrid获取父节点的id_超简单的分布式ID生成方案!美团开源框架介绍
目录 阐述背景 Leaf snowflake 模式介绍 Leaf segment 模式介绍 Leaf 改造支持 RPC 阐述背景 不吹嘘,不夸张,项目中用到 ID 生成的场景确实挺多.比如业务要做幂等 ...
- 超简单的分布式ID生成方案!美团开源框架介绍
目录 阐述背景 Leaf snowflake 模式介绍 Leaf segment 模式介绍 Leaf 改造支持 RPC 阐述背景 不吹嘘,不夸张,项目中用到 ID 生成的场景确实挺多.比如业务要做幂等 ...
- 分库分表的 9种分布式主键ID 生成方案
<sharding-jdbc 分库分表的 4种分片策略> 中我们介绍了 sharding-jdbc 4种分片策略的使用场景,可以满足基础的分片功能开发,这篇我们来看看分库分表后,应该如何为 ...
- 分库分表的 9种分布式主键ID 生成方案,挺全乎的
<sharding-jdbc 分库分表的 4种分片策略> 中我们介绍了 sharding-jdbc 4种分片策略的使用场景,可以满足基础的分片功能开发,这篇我们来看看分库分表后,应该如何为 ...
最新文章
- 集合已修改;可能无法执行枚举操作。
- 我自学python的路-Python的学习路经
- 多级菜单栏展开隐藏动画
- Python之方法调用
- js 兼容event.target
- 限制nginx仅能域名访问,不可用ip访问
- 汇编语言-015(PROC伪指令定义参数方式、EXTERNDEF、INCLUDE 、EXTERN 、INVOKE、PROC、PROTO 、MOVSB 、MOVSD 、CMPSD )
- markdown改字体和背景颜色(html)
- Windows下安装python2和python3双版本
- Puppet 命令参数介绍(三)
- [cloud][sdn] neutron了解
- feign返回结果统一处理_SpringCloud异常处理统一封装我来做-使用篇
- python+pymssql+selenium 获取短信验证码登录(实战练习)
- 正交试验设计例题及答案_SPSS正交试验设计及其方差分析
- 一周信创舆情观察(11.15~11.21)
- 有哪些让人相见恨晚的记笔记方法?
- 华为SmartKit工具下载
- 安卓linux开机画面,安卓开机画面_安卓开机动画修改_安卓开机动画修改器-Guide信息网...
- kingcms php 下载,KingCMS企业版(PHP)
- 【VMware环境下Linux磁盘空间(LVM)扩容方法】