【分布式id生成系统——leaf源码】

news2024/12/28 23:29:54

分布式id生成系统——leaf源码

  • 号段模式
    • 双buffer优化
    • id获取

Leaf ,分布式 ID 生成系统,有两种生成 ID 的方式:

  • 号段模式
  • Snowflake模式

号段模式

由于号段模式依赖于数据库表,我们先看一下相关的数据库表:
在这里插入图片描述

biz_tag:针对不同业务需求,用biz_tag字段来隔离,如果以后需要扩容时,只需对biz_tag分库分表即可
max_id:当前业务号段的最大值,用于计算下一个号段
step:步长,也就是每次获取ID的数量
random_step: 每次getid时随机增加的长度

对应的实体类如下

import java.util.concurrent.atomic.AtomicLong;

/**
 * @author left
 */
public class Segment {

	private AtomicLong value = new AtomicLong(0); //对 long 类型的变量进行原子操作,这里就是产生的id值

	private volatile long max; //当前号段起始id

	private volatile int step;  //每次缓存数量

	private volatile int randomStep; //随机增长

	private final SegmentBuffer buffer; //双buffer
}

这样一看,就是在把数据库的自增方式放到了内存中,相当于加了一层缓存,减少了数据库的访问次数。但其实做的比这更好,程序通过一种双 Buffer 优化方式,提前缓存下一个 Segement,降低网络请求的耗时。

双buffer优化

思路如下
在这里插入图片描述

数据库表对应的实体类如下:

/**
 * @author leaf
 */
public class LeafAlloc {

	private String key;

	private long maxId;

	private int step;

	private String updateTime;

	private int randomStep;
}

这个类是用于缓存的类

public class SegmentBuffer {

	private String key;  //对应了数据库中的biz_tag

	/**
	 * 双buffer
	 */
	private final Segment[] segments;

	/**
	 * 当前的使用的segment的index
	 */
	private volatile int currentPos;

	/**
	 * 下一个segment是否处于可切换状态
	 */
	private volatile boolean nextReady;

	/**
	 * 是否初始化完成
	 */
	private volatile boolean initOk;

	/**
	 * 线程是否在运行中
	 */
	private final AtomicBoolean threadRunning;

	private final ReadWriteLock lock;

	private volatile int step;

	private volatile int minStep;

	private volatile long updateTimestamp;
}

下面从具体代码来体现:
首先是程序启动后,对SegmentService进行实例化,在通过构造方法实例化SegmentService时,首先对IDAllocDao进行了创建,这里的dao层没有直接用@Mapper注解去创建实现,而是通过实现类实现IDAllocDaoImpl方式(主要原因应该是作为被引入的包,mapper注解可能扫描不到对应的sql吧)。

@Service("SegmentService")
public class SegmentService {

	private final Logger logger = LoggerFactory.getLogger(SegmentService.class);

	private final IDGen idGen;

	public SegmentService(DataSource dataSource) throws InitException {
		// Config Dao
		IDAllocDao dao = new IDAllocDaoImpl(dataSource);

		// Config ID Gen
		idGen = new SegmentIDGenImpl();
		((SegmentIDGenImpl) idGen).setDao(dao);
		if (idGen.init()) {
			logger.info("Segment Service Init Successfully");
		}
		else {
			throw new InitException("Segment Service Init Fail");
		}

	}
public IDAllocDaoImpl(DataSource dataSource) {
    // 创建事务工厂
    TransactionFactory transactionFactory = new JdbcTransactionFactory();

    // 创建MyBatis环境
    Environment environment = new Environment("development", transactionFactory, dataSource);

    // 创建MyBatis配置对象
    Configuration configuration = new Configuration(environment);

    // 添加IDAllocMapper映射
    configuration.addMapper(IDAllocMapper.class);

    // 构建SqlSessionFactory
    sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
}

接下来这里的idGen.init(),初始化发号器,方法调用了updateCacheFromDb()和updateCacheFromDbAtEveryMinute()对数据进行缓存。updateCacheFromDb()中,SegmentBuffer buffer = new SegmentBuffer();创建了SegmentBuffer,初始化时建立一个Segment[]数组保存了当前的SegmentBuffer 。其他就是初始化需要的值
在这里插入图片描述
主要代码及注释如下:

@Override
	public boolean init() {
		logger.info("Init ...");
		// 确保加载到kv后才初始化成功
		updateCacheFromDb();
		initOk = true;
		//60s的定时更新号段
		updateCacheFromDbAtEveryMinute();
		return initOk;
	}

private void updateCacheFromDb() {
		logger.info("update cache from db");
		try {
			List<String> dbTags = dao.getAllTags();
			if (dbTags == null || dbTags.isEmpty()) {
				return;
			}
			List<String> cacheTags = new ArrayList<String>(cache.keySet());
			Set<String> insertTagsSet = new HashSet<>(dbTags);
			Set<String> removeTagsSet = new HashSet<>(cacheTags);
			// db中新加的tags灌进cache
			for (int i = 0; i < cacheTags.size(); i++) {
				String tmp = cacheTags.get(i);
				insertTagsSet.remove(tmp);
			}
			for (String tag : insertTagsSet) {
				SegmentBuffer buffer = new SegmentBuffer();
				buffer.setKey(tag);
				//取当前位置的Segment,第一次取第一个位置的
				Segment segment = buffer.getCurrent();
				//初始化为0
				segment.setValue(new AtomicLong(0));
				segment.setMax(0);
				segment.setStep(0);
				//缓存
				cache.put(tag, buffer);
				logger.info("Add tag {} from db to IdCache, SegmentBuffer {}", tag, buffer);
			}
			//遍历数据库中的tags,如果数据库中的存在,removeTagsSet就不保存
			for (int i = 0; i < dbTags.size(); i++) {
				String tmp = dbTags.get(i);
				removeTagsSet.remove(tmp);
			}
			// cache中已失效的tags从cache删除
			for (String tag : removeTagsSet) {
				cache.remove(tag);
				logger.info("Remove tag {} from IdCache", tag);
			}
		}
		catch (Exception e) {
			logger.warn("update cache from db exception", e);
		}
	}

updateCacheFromDbAtEveryMinute()做了一个定时任务,定时刷新updateCacheFromDb();

private void updateCacheFromDbAtEveryMinute() {
		ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
			@Override
			//指定线程内容
			public Thread newThread(Runnable r) {
				Thread t = new Thread(r);
				t.setName("check-idCache-thread");
				//设置为守护线程,如果主线程结束,跟着结束
				t.setDaemon(true);
				return t;
			}
		});
		//定时任务执行,60s后每60s执行一次
		service.scheduleWithFixedDelay(new Runnable() {
			@Override
			public void run() {
				updateCacheFromDb();
			}
		}, 60, 60, TimeUnit.SECONDS);
	}

id获取

这里通过请求一个用户注册接口,来获取一个用户id。通过fegin调用来调用getId服务
在这里插入图片描述
获取id的代码如下:

@Override
	public Result get(final String key) {
		if (!initOk) {
			return new Result(EXCEPTION_ID_IDCACHE_INIT_FALSE, Status.EXCEPTION);
		}
		//通过key获取缓存
		SegmentBuffer buffer = cache.get(key);
		if (buffer != null) {
			if (buffer.isInitOk()) {
			//未初始化,锁住这个buffer,其他线程不可修改
				synchronized (buffer) {
					if (buffer.isInitOk()) {
						try {
						    //从数据库中更新号段
							updateSegmentFromDb(key, buffer.getCurrent());
							logger.info("Init buffer. Update leafkey {} {} from db", key, buffer.getCurrent());
							buffer.setInitOk(true);
						}
						catch (Exception e) {
							logger.warn("Init buffer {} exception", buffer.getCurrent(), e);
						}
					}
				}
			}
			return getIdFromSegmentBuffer(cache.get(key));
		}
		return new Result(EXCEPTION_ID_KEY_NOT_EXISTS, Status.EXCEPTION);
	}

第一个核心代码:从数据库中更新号段
在这里插入图片描述
①:如果buffer是没有初始化,首先对数据库的号段最大值进行更新,更新完成后,获取结果,这里相当于是获取了一次缓存。更新方式是让max_id增加一次step。目前库中的step是100,也就是每次取100个号
在这里插入图片描述

②:第二种情况是,buffer已经初始化了,但是未发生过更新,这里就是走额外线程去获取第二层缓存了。
③④:一个segment的过期时间是15分钟,未过期,nextStep正常扩容为2倍,并且数据库进行更新
⑤:

在这里插入图片描述
⑥⑦:上诉对step操作完成后,根据key更新max_id,max_id值更新为max_id+step,相当于如果15分钟后,未使用的id将会被舍弃。
⑧:上面的三个if完成后,获取到value值,对当前的segment设置。

第二个核心代码:从缓存中获取id
在这里插入图片描述
①:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1194864.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大模型+人形机器人,用AI唤起钢筋铁骨

《经济参考报》11月8日刊发文章《多方布局人形机器人赛道,智能应用前景广》。文章称&#xff0c;工信部日前印发的《人形机器人创新发展指导意见》&#xff0c;按照谋划三年、展望五年的时间安排&#xff0c;对人形机器人创新发展作了战略部署。 从开发基于人工智能大模型的人…

SQL优化之MySQL执行计划(Explain)及索引失效详解

1、执行计划基础 1.1、执行计划&#xff08;Explain&#xff09;定义 在 MySQL 中可以通过 explain 关键字模拟优化器执行 SQL语句&#xff0c;从而解析MySQL 是如何处理 SQL 语句的。 1.2、MySQL查询过程 客户端向 MySQL 服务器发送一条查询请求服务器首先检查查询缓存&am…

为什么我一直是机器视觉调机仔,为什么一定要学一门高级语言编程?

​ 为什么我是机器视觉调机仔&#xff0c;为什么一定要学一门高级语言编程&#xff0c;以后好不好就业&#xff0c;待遇高不高&#xff0c;都是跟这项技术没关系&#xff0c;是跟这个技术背后的行业发展有关系。 你可以选择离机器视觉行业&#xff0c;也可以选择与高级语言相关…

中国电信终端产业联盟5G Inside行业子联盟正式成立!宏电股份作为副理事单位受邀加入

11月9日&#xff0c;中国电信于广州召开“2023中国电信终端生态合作暨中国电信终端产业联盟&#xff08;以下简称CTTA&#xff09;第十四次会员大会”&#xff0c;联盟成员齐聚现场。作为CTTA大会的一个重要环节&#xff0c;中国电信终端产业联盟5G Inside行业子联盟正式成立&a…

LeetCode(5)多数元素【数组/字符串】【简单】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 169. 多数元素 1.题目 给定一个大小为 n 的数组 nums &#xff0c;返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的&#xff0c;并且给定的数组总是存在多数元素。 示…

jenkins邮件告警

构建失败邮件通知 配置自己的邮箱 配置邮件服务&#xff0c;密码是授权码 添加构建后操作 扩展 配置流水线 添加扩展 钉钉通知 Jenkins安装钉钉插件 钉钉添加机器人 加签 https://oapi.dingtalk.com/robot/send?access_token98437f84ffb6cd64fa2d7698ef44191d49a11…

ios安全加固 ios 加固方案

​ 目录 一、iOS加固保护原理 1.字符串混淆 2.类名、方法名混淆 3.程序结构混淆加密 4.反调试、反注入等一些主动保护策略 二 代码混淆步骤 1. 选择要混淆保护的ipa文件 2. 选择要混淆的类名称 3. 选择要混淆保护的函数&#xff0c;方法 4. 配置签名证书 5. 混淆和测…

原型模式(创建型)

一、前言 原型模式是一种创建型设计模式&#xff0c;它允许在运行时通过克隆现有对象来创建新对象&#xff0c;而不是通过常规的构造函数创建。在原型模式中&#xff0c;一个原型对象可以克隆自身来创建新的对象&#xff0c;这个过程可以通过深度克隆或浅克隆来实现。简单说原型…

【Java SE】类和对象(上)

目录 一. 面向对象的初步认知 1.1 什么是面向对象 1.2 面向对象与面向过程 二. 类定义和使用 2.1 简单认识类 2.2 类的定义格式 三. 类的实例化 3.1 什么是实例化 3.2 实例化对象 四. this引用(重点&#xff09; 4.1 为什么要有this引用 4.2 this的使用 4.3 this引…

lua环境安装

文章目录 Linux 系统上安装Mac OS X 系统上安装Window 系统上安装 Lua第一个 Lua 程序 Linux 系统上安装 Linux & Mac上安装 Lua 安装非常简单&#xff0c;只需要下载源码包并在终端解压编译即可&#xff0c;本文使用了5.3.0版本进行安装&#xff1a; curl -R -O http://…

openMMLab的mmcv和mmdet、mmdet3d、mmseg版本对应关系

openmmlab提供了MIM来统一安装其多个mm功能框架包https://github.com/open-mmlab/mim&#xff0c;但是需要不借助MIM安装时&#xff0c;这里怎么确定要安装什么版本的mmcv和mmdet、mmdet3d、mmseg&#xff0c;在openmmlab网站主页上没有一个容易能找到的完整表格页面来详细记录…

Dell戴尔灵越Inspiron 7700 AIO一体机电脑原厂预装Windows10系统

链接&#xff1a;https://pan.baidu.com/s/1-slgR9t4Df_eko0Y6xaeyw?pwdmk0p 提取码&#xff1a;mk0p 灵越7700一体机原装出厂系统自带声卡驱动、无线网卡驱动、面部识别等所有驱动、出厂主题壁纸、系统属性专属LOGO标志、Office办公软件、MyDell等预装程序 由于时间关系,…

GF0-57CQD-002 测量参数:加速度、速度、位移–现场可配置

GF0-57CQD-002 测量参数:加速度、速度、位移–现场可配置 GF0-57CQD-002 是一款创新的双通道变送器&#xff0c;专为精确的振动测量而设计。它激励并读取来自加速度计的信号&#xff0c;并将整体振动值作为电流/电压信号传输。它测量加速度、速度和位移等不同参数的振动。配置…

HTTP和HTTPS详解

一)什么是HTTP协议 1)HTTP协议是倾向于相遇业务层次上面的一种协议&#xff0c;传输层协议主要考虑的是端对端之间的一个传输过程&#xff0c;TCP重点进行关注的是可靠传输&#xff1b;咱们的HTTP/1&#xff0c;HTTP/2是基于TCP的&#xff0c;但是咱们的HTTP/3是基于UDP的&…

【优选算法系列】【专题五位运算】第二节.371. 两整数之和and137. 只出现一次的数字 II

文章目录 前言一、两整数之和 1.1 题目描述 1.2 题目解析 1.2.1 算法原理 1.2.2 代码编写二、只出现一次的数字 II 2.1 题目描述 2.2 题目解析 2.2.1 算法原理 2.2.2 代码编写总结 前言 一、两整数之和 …

故障诊断模型 | Maltab实现RF随机森林的故障诊断

效果一览 文章概述 故障诊断模型 | Maltab实现RF随机森林的故障诊断 模型描述 RF善于处理高维数据,特征遗失数据,和不平衡数据 (1)训练可以并行化,速度快 (2)对高维数据集的处理能力强,它可以处理成千上万的输入变量,并确定最重要的变量,因此被认为是一个不错的降…

【overleaf参考文献引用】Citation `r51‘ on page 1 undefined on input line 46

overleaf 编辑插入参考文献出现如下问题&#xff1a; 显示如下&#xff1a;连着三个参考文献有一个显示为问号&#xff0c;latex的错误如上&#xff1a; Citation r51 on page 1 undefined on input line 46 问题原因&#xff1a; 在文档的第一页&#xff08;Page 1&#xff0…

ElasticSearch7.x - HTTP 操作 - 查询文档操作

查询索引下的所有文档 http://192.168.254.101:9200/shopping/_search 条件查询 请求路径上添加条件:http://192.168.254.101:9200/shopping/_search?q=category:小米 请求体上添加条件:http://192.168.254.101:9200/shopping/_search 请求体内容 {"query" :{&qu…

Linux 部署Sentinel控制台

Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件&#xff0c;主要以流量为切入点&#xff0c;从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。 1.版本选择 SpringCloudAlibaba SpringClo…

uniapp+uviewPlus+vue3+ts+pinia+vite+echarts 开发基础模板,开箱即用,非常顺手

github仓库地址&#xff1a;https://github.com/Sjj1024/uniapp-vue3 使用 uniapp vue3 ts pinia vite echarts 开发基础模板&#xff0c;拿来即可使用&#xff0c;不要删除 yarn.lock 文件&#xff0c;否则会启动报错&#xff0c;这个可能和 pinia 的版本有关&#xff0…