分布式 ID 生成策略(二)

news/2024/10/30 7:25:32/

在上一篇文章,分布式 ID 生成策略(一),我们讨论了基于数据库的 ID 池策略,今天来看另一种实现,基于雪花算法分布式 ID 生成策略。

在这里插入图片描述
如图所示,我们用 41 位时间戳 + 12 位机器 ID + 10 位序列号,来表示一个 64 位的分布式 ID。

基于这样的雪花算法来保证 ID 的唯一性

  • 时间戳是递增的,不同时刻产生的 ID 肯定是不同的;
  • 机器 ID 是不同的,同一时刻不同机器产生的 ID 肯定也是不同的;
  • 同一时刻同一机器上,可以轻易控制序列号。
CREATE TABLE `global_sequence_time` (`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',`node_name` varchar(32) NOT NULL DEFAULT '' COMMENT '机器名称,通常为内网IP',`node_id` smallint(6) NOT NULL COMMENT '机器ID,数字,最大1023',`sn` varchar(128) NOT NULL DEFAULT '' COMMENT '业务字段名称',`version` bigint(20) NOT NULL DEFAULT '1' COMMENT '乐观锁版本',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`),UNIQUE KEY `uk_name` (`sn`,`node_name`) USING BTREE,UNIQUE KEY `uk_id` (`sn`,`node_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='全局ID生成表';
  • node_name:节点名称,默认为节点的内网IP。所以,同一个机器上,部署多个应用,他们的 node_name 是一样的;

  • sn:业务名称,根据此值分类ID的生成;

  • node_id:数字类型,最大值不能超过1024,即此算法最多支持1024个节点。

还有两个唯一约束

  • 同一个业务sn值,在一个机器上不能部署2个;
  • 同一个业务sn值,node_id不能重复。
package idgenerator;/*** Description* 基于SnowFlake算法实现* 将node_id保存在数据库中,根据本机IP地址作为标识.每个机器对应一个node_id.*/
public class TimeBasedIDGenerator extends IDGenerator {protected static final int DEFAULT_RETRY = 6;public static final int NODE_SHIFT = 10;public static final int SEQ_SHIFT = 12;public static final short MAX_NODE = 1024;//最大node 个数,public static final short MAX_SEQUENCE = 4096;//每秒最大ID个数private short sequence;private long referenceTime;private DataSource dataSource;private Map<String, Integer> cachedNodeId = new HashMap<>();private String nodeName;public void setDataSource(DataSource dataSource) {this.dataSource = dataSource;}/****/public TimeBasedIDGenerator() {nodeName = getLocalAddress();}private synchronized Integer getNodeId(String sn) {Integer nodeId = cachedNodeId.get(sn);//正常,返回if (nodeId != null) {return nodeId;}int i = 0;while (i < DEFAULT_RETRY) {nodeId = fetch(sn);if (nodeId > MAX_NODE) {throw new IllegalStateException("node_id is greater than " + MAX_NODE + ",please check sn=" + sn);}if (nodeId > 0) {cachedNodeId.put(sn, nodeId);break;}i++;}return nodeId;}/*** @return The next 64-bit integer.*/public synchronized long next(String sn) {Integer nodeId = getNodeId(sn);if (nodeId == null || nodeId < 0) {throw new IllegalStateException("无法获取nodeId,sn=" + sn);}long currentTime = System.currentTimeMillis();long counter;if (currentTime < referenceTime) {throw new RuntimeException(String.format("Last referenceTime %s is after reference time %s", referenceTime, currentTime));} else if (currentTime > referenceTime) {this.sequence = 0;} else {if (this.sequence < MAX_SEQUENCE) {this.sequence++;} else {throw new RuntimeException("Sequence exhausted at " + this.sequence);}}counter = this.sequence;referenceTime = currentTime;return currentTime << NODE_SHIFT << SEQ_SHIFT | nodeId << SEQ_SHIFT | counter;}/*** 获取nodeId.* @param sn* @return*/private int fetch(String sn) {Connection connection = null;PreparedStatement ps = null;try {connection = dataSource.getConnection();connection.setAutoCommit(true);//普通操作connection.setReadOnly(false);ps = connection.prepareStatement("select node_id from `global_sequence_time` where `sn` = ? and node_name = ? limit 1");ps.setString(1, sn);ps.setString(2, nodeName);ResultSet rs = ps.executeQuery();//已有数据,则直接返回if (rs.next()) {return rs.getInt(1);}ps.close();//如果没有数据,则首先获得已有的最大node_id,然后自增//查询已知最大idps = connection.prepareStatement("select MAX(node_id) AS m_id from `global_sequence_time` where `sn` = ?");ps.setString(1, sn);rs = ps.executeQuery();int id = 1;if (rs.next()) {id = rs.getInt(1) + 1;}ps.close();//新建记录ps = connection.prepareStatement("insert into global_sequence_time(node_name,node_id,sn,create_time,update_time) VALUE (?,?,?,NOW(),NOW())");ps.setString(1, nodeName);ps.setInt(2, id);ps.setString(3, sn);int row = ps.executeUpdate();if (row > 0) {return id;}} catch (Exception e) {throw new RuntimeException(e);} finally {try {if (ps != null) {ps.close();}if (connection != null) {connection.close();}} catch (Exception ex) {//}}return -1;}private String getLocalAddress() {try {Enumeration<NetworkInterface> netInterfaces = NetworkInterface.getNetworkInterfaces();//一个主机有多个网络接口while (netInterfaces.hasMoreElements()) {NetworkInterface netInterface = netInterfaces.nextElement();Enumeration<InetAddress> addresses = netInterface.getInetAddresses();while (addresses.hasMoreElements()) {InetAddress address = addresses.nextElement();if (address.isSiteLocalAddress() && !address.isLoopbackAddress()) {return address.getHostAddress();}}}} catch (Exception e) {throw new RuntimeException("无法获取本地IP地址", e);}return null;}
}

http://www.ppmy.cn/news/1543036.html

相关文章

使用 Kibana 将地理空间数据导入 Elasticsearch 以供 ES|QL 使用

作者&#xff1a;来自 Elastic Craig Taverner 如何使用 Kibana 和 csv 采集处理器将地理空间数据采集到 Elasticsearch 中&#xff0c;以便在 Elasticsearch 查询语言 (ES|QL) 中进行搜索。Elasticsearch 具有强大的地理空间搜索功能&#xff0c;现在 ES|QL 也具备这些功能&am…

Python 网络爬虫快速入门

网络爬虫是一种自动化的程序&#xff0c;用于从互联网上抓取数据。Python 由于其简洁的语法和丰富的库支持&#xff0c;成为编写网络爬虫的理想选择。本文将带你快速入门 Python 网络爬虫&#xff0c;从安装必要的库到编写一个简单的爬虫&#xff0c;再到处理更复杂的情况。 1…

【Spring框架】Spring框架的开发方式

目录 Spring框架开发方式前言具体案例导入依赖创建数据库表结构创建实体类编写持久层接口和实现类编写业务层接口和实现类配置文件的编写 IoC注解开发注解开发入门&#xff08;半注解&#xff09;IoC常用注解Spring纯注解方式开发 Spring整合JUnit测试 Spring框架开发方式 前言…

了解elasticsearch

目录 elasticsearch的作用 ELK技术栈 elasticsearch和lucene 为什么不是其他搜索技术&#xff1f; 总结 elasticsearch的作用 elasticsearch是一款非常强大的开源搜索引擎&#xff0c;具备非常多强大功能&#xff0c;可以帮助我们从海量数据中快速找到需要的内容 例如&am…

STM32-Cube定时器TIM

一、内部时钟源 1、创建项目 File → New → STM32 project选择STM32F103C8T6单片机&#xff0c;命名TIM 2、配置单片机 1.打开USART1&#xff0c;方便我们与电脑连接查看数据 开启UART1并开启中断。 2、设置时钟源 开启外部高速晶振 将时钟频率设置为72MHz 设置调试模…

大语言模型数据流程源码解读(基于llama3模型)

文章目录 前言一、数据进入LlamaForCausalLM(LlamaPreTrainedModel)类二、数据进入LlamaModel(LlamaPreTrainedModel)类1、input_ids的embedding编码2、position_ids位置获取3、causal_mask因果mask构建1、causal_mask调用2、因果mask代码解读(_update_causal_mask)4、hidden_s…

经验总结:typescript 和 axios 项目中大量接口该如何管理和组织

引言 本文旨在介绍一种方法&#xff0c;用于在 typescript 和 axios 的项目中&#xff0c;有效的组合和管理大量的 API 接口以及 interface。 假如我们根据 API 文档对所有的接口做了初步分类&#xff0c;大体如下&#xff1a; scm(某业务模块)├── inventory(库存业务)│…

使用 Kafka 和 MinIO 实现人工智能数据工作流

MinIO Enterprise Object Store 是用于创建和执行复杂数据工作流的基础组件。此事件驱动功能的核心是使用 Kafka 的 MinIO 存储桶通知。MinIO Enterprise Object Store 为所有 HTTP 请求&#xff08;如 PUT、POST、COPY、DELETE、GET、HEAD 和 CompleteMultipartUpload&#xf…