当前位置: 首页 > article >正文

分布式 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;
    }
}

http://www.kler.cn/a/375593.html

相关文章:

  • 从0到1搭建flink程序-WordCount(图文/详细/mac)
  • 齐次线性微分方程的解的性质与结构
  • 20.04Ubuntu搭建Vscode
  • 【C语言】宏封装的实用总结
  • STM32滴答时钟是否每次计时1ms都要中断一下,更新ms数
  • 嵌入式硬件电子电路设计(三)电源电路之负电源
  • Redis 内存回收策略小结
  • Spark中的常见算子
  • ubuntu22-安装vscode-配置shell命令环境-mac安装
  • Serverless + AI 让应用开发更简单
  • 报错:npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚本。
  • vue3-element-admin 去掉登录
  • Docker Compose入门学习——下载、授权、创建文件、定义服务
  • 创建一个基于SSM(Spring, Spring MVC, MyBatis)的教学视频点播系统
  • Sigrity Power SI Multiple Structure Simulation模式如何进行跨板级联仿真操作指导(一)
  • npm install -g @vue/cil 非常卡慢
  • linux alsa-lib snd_pcm_open函数源码分析(一)
  • 腾讯云数据库TDSQL:数据库界的“高架桥”
  • 【论文阅读】Associative Alignment for Few-shot Image Classification
  • ESP-IDF HTTP POST请求发送音频-启明云端乐鑫代理商
  • 【机器学习】21. Transformer: 最通俗易懂讲解
  • 优化低代码开发平台用户体验:功能树导航设计探讨
  • 穷举vs暴搜vs深搜vs回溯vs剪枝 算法专题
  • #渗透测试#SRC漏洞挖掘# 操作系统-windows系统
  • C++设计模式结构型模式———桥接模式
  • watch 和 computed 的区别 - 2024最新版前端秋招面试短期突击面试题【100道】