分布式 ID 生成策略(二)

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

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

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

  • 时间戳是递增的,不同时刻产生的 ID 肯定是不同的;
  • 机器 ID 是不同的,同一时刻不同机器产生的 ID 肯定也是不同的;
  • 同一时刻同一机器上,可以轻易控制序列号。
sql 复制代码
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不能重复。
java 复制代码
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;
    }
}
相关推荐
KIDAKN4 小时前
RabbitMQ 初步认识
分布式·rabbitmq
pan3035074794 小时前
Kafka 和 RabbitMQ的选择
分布式·kafka·rabbitmq
hzulwy7 小时前
Kafka基础理论
分布式·kafka
明达智控技术8 小时前
MR30分布式IO在全自动中药煎药机中的应用
分布式·物联网·自动化
jakeswang9 小时前
细说分布式ID
分布式
失散1310 小时前
分布式专题——1.2 Redis7核心数据结构
java·数据结构·redis·分布式·架构
王中阳Go11 小时前
头一次见问这么多kafka的问题
分布式·kafka
boonya12 小时前
Kafka核心原理与常见面试问题解析
分布式·面试·kafka
KIDAKN13 小时前
RabbitMQ 重试机制 和 TTL
分布式·rabbitmq
JAVA学习通14 小时前
【RabbitMQ】----初识 RabbitMQ
分布式·rabbitmq