分布式 ID 生成策略(二)

在上一篇文章,分布式 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,然后自增
            //查询已知最大id
            ps = 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;
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大数据新视界 -- 大数据大厂之大数据重塑影视娱乐产业的未来(4 - 3)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

【数据库设计】规范设计理论之范式

学习完函数依赖之后我们就可以以函数依赖的定义为依据来规范设计数据库了。范式&#xff08;normal form&#xff09;的意思是规范的程度&#xff0c;如第一范式第二范式第三范式&#xff0c;数字越大说明规范程度越高。让数据库满足某一范式实际上是在消除函数依赖的过程&…

VisualStudio2022配置2D图形库SFML

文章目录 1. 下载安装SFML库2. 创建C项目并配置SFML配置include目录和库目录链接SFML库配置动态链接库 3. 测试 1. 下载安装SFML库 SFML&#xff08;Simple and Fast Multimedia Library&#xff09;C库&#xff0c;适合2D游戏和图形界面&#xff0c;提供了以下模块&#xff1…

Python(pandas库3)

函数 随机抽样 语法&#xff1a; n&#xff1a;要抽取的行数 frac&#xff1a;抽取的比例&#xff0c;比如 frac0.5&#xff0c;代表抽取总体数据的50% axis&#xff1a;示在哪个方向上抽取数据(axis1 表示列/axis0 表示行) 案例&#xff1a; 输出结果都为随机抽取。 空…

MATLAB锂电概率分布模型

&#x1f3af;要点 概率分布等效电路模型结合了路径相关速率能力及状态估计中滞后效应。纠正了充电状态中时间误差累积及避免开路电压中电压滞后现象。使用电流方向和电池容量相关函数描述开路电压&#xff0c;并使用微分方程描述电压滞后现象。模型结构基于一级相变的材料机制…

2024.10.9华为留学生笔试题解

第一题无线基站名字相似度 动态规划 考虑用动态规划解决 char1=input().strip() char2=input().strip() n,m=len(char1),len(char2) dp=[[0]*(m+1) for _ in range(n+1)] #dp[i][j]定义为以i-1为结尾的char1 和以 j-1为结尾的char2 的最短编辑距离 setA = set(wirel@com) set…

力扣21 : 合并两个有序链表

链表style 描述&#xff1a; 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例&#xff1a; 节点大小相同时&#xff0c;l1的节点在前 何解&#xff1f; 1&#xff0c;遍历两个链表&#xff0c;挨个比较节点大小 同时遍…

【北京迅为】《STM32MP157开发板嵌入式开发指南》-第六十七章 Trusted Firmware-A 移植

iTOP-STM32MP157开发板采用ST推出的双核cortex-A7单核cortex-M4异构处理器&#xff0c;既可用Linux、又可以用于STM32单片机开发。开发板采用核心板底板结构&#xff0c;主频650M、1G内存、8G存储&#xff0c;核心板采用工业级板对板连接器&#xff0c;高可靠&#xff0c;牢固耐…

基于海思soc的智能产品开发(音视频处理的三个方向)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 音视频处理这个需求一直都有&#xff0c;那我们为什么需要soc来处理音视频。或者说&#xff0c;用soc来处理音视频有什么好处&#xff1f;传统的pc…

msvcr100.dll丢失怎样修复,介绍6个简单靠谱的方法

msvcr100.dll是Microsoft Visual C 2010 Redistributable Package中的一个关键动态链接库文件。该文件包含了运行基于Visual Studio 2010编译的应用程序所必需的一系列函数和资源。具体来说&#xff0c;它提供了C运行时库的支持&#xff0c;使得许多使用Visual C库编译的应用程…

雷池社区版compose配置文件解析-mgt

在现代网络安全中&#xff0c;选择合适的 Web 应用防火墙至关重要。雷池&#xff08;SafeLine&#xff09;社区版免费切好用。为网站提供全面的保护&#xff0c;帮助网站抵御各种网络攻击。 compose.yml 文件是 Docker Compose 的核心文件&#xff0c;用于定义和管理多个 Dock…

并发编程的深入探索(3/5)

目录 1. Java线程模型 示例代码&#xff1a;创建线程的两种方式 2. 同步机制与锁 示例代码&#xff1a;synchronized的使用 显式锁&#xff08;ReentrantLock&#xff09; 3. Java并发工具包&#xff08;java.util.concurrent&#xff09; 线程池&#xff08;Executor F…

gcc与mingw64版本介绍

三类编译器 GCC&#xff0c;全称为GNU Compiler Collection&#xff0c;是一个强大的编译器集合&#xff0c;它不仅支持C和C语言&#xff0c;还支持Fortran、Ada、Java等多种编程语言的编译。在GCC工具链中&#xff0c;gcc和g是两个核心的编译器工具。gcc是专门用于编译C语言程…

PostgreSQL使用clickhouse_fdw访问ClickHouse

Postgres postgres版本&#xff1a;16&#xff08;测试可用&#xff09;docker 安装 插件安装 clickhouse_fdw: https://github.com/ildus/clickhouse_fdw 安装命令 git clone gitgithub.com:ildus/clickhouse_fdw.git cd clickhouse_fdw mkdir build && cd build…

【高级IO】IO多路转接之epoll

epoll接口 epoll_create 创建一个epoll模型 自从linux2.6.8之后&#xff0c;size参数是被忽略的返回值是一个文件描述符用完之后, 必须调用close()关闭 epoll_ctl epoll_ctl用于添加、修改或删除关注的文件描述符&#xff0c;并设置感兴趣的事件类型&#xff08;如读事件、写…

windows 11 mpksldrv.sys 导致蓝屏

#mpksldrv.sys 导致蓝屏 windows 11 在运行Twincat 3进行仿真时&#xff0c;就会蓝屏 尝试了各种其他的办法修改什么注册表&#xff0c;各种办法都尝试了没有用&#xff0c; 后面尝试了下面的办法竟然解决了&#xff0c;请参考下面链接&#xff1a; 链接: 两条命令可以帮助你…

如何使用的是github提供的Azure OpenAI服务

使用的是github提供的Azure OpenAI的服务gpt-4o 说明&#xff1a;使用的是github提供的Azure OpenAI的服务&#xff0c;可以无限薅羊毛。开源地址 进入&#xff1a; 地址 进入后点击 右上角“Get API key”按钮 点击“Get developer key” 选择Beta版本“Generate new to…

WPF+MVVM案例实战(九)- 霓虹灯字效果控件封装实现

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 1、运行效果2、主菜单与界面实现1、主菜单2、霓虹灯字界面实现3、字体资源获取3、控件封装1.创建自定义控件2、依赖属性实现3、封装控件使用4、运行效果4、源代码获取1、运行效果 2、主菜单与界面实…

Vulkan 开发(五):Vulkan 逻辑设备

图片来自《Vulkan 应用开发指南》 Vulkan 开发系列文章&#xff1a; 1. 开篇&#xff0c;Vulkan 概述 2. Vulkan 实例 3. Vulkan 物理设备 4. Vulkan 设备队列 在 Vulkan 中&#xff0c;逻辑设备&#xff08;Logical Device&#xff09;是与物理设备&#xff08;Physical D…

Hue3.9.0-cdh5.14.0安装

hue的安装需要准备如下的前置条件 1、Java-jdk8 2、python2.6 3、安装时非root用户 4、另行安装maven和ant&#xff0c;并配置HOME 5、hue安装节点需要hadoop、hive的配置文件 6、准备一个mysql库来做hue的元数据库 第一步&#xff1a;前置准备中的Javajdk和python准备本简单就…