高并发系统-分布式唯一ID生成(一)

本文主要总结市面上常见的分布式ID生成方案,并结合自身项目经历对齐总结

定义

分布式 ID 是分布式系统下的 ID,其具有唯一标识。类似系统中常见的用户ID、订单号、券码,都应该具备唯一性。

算法要求

  • 全局唯一:生成的 ID 必须全局唯一
  • 趋势递增:我们应该尽量选择有序的主键来保证索引的性能
  • 好接入:要秉着拿来即用的设计原则,在系统设计和实现上要尽可能的简单
  • 信息安全:如果是连续的 ID,攻击者很容易就猜出下一条记录的 ID,所以有些情况下尽量让 ID 无规则(可选)
  • 含时间戳:含时间戳便于追踪(可选)

可靠性要求

  • 高可用:要保证在99.999%的情况下能够生成一个唯一的分布式 ID
  • 低延迟:生成分布式 ID 的速度一定要快
  • 高QPS:假如一下子来10w个生成分布式 ID 的请求,服务器要能扛得住并且能够一下子生成10w个

生成方案

UUID

包含32个16进制的数字,以连字符分割成五段,格式是8-4-4-4-12

优点:

性能好,本地生成,无网络消耗

缺点:

存储消耗空间大(32 个字符串,128 位)

不安全(基于 MAC 地址生成 UUID 的算法会造成 MAC 地址泄露)

无序(非自增)

没有具体业务含义

需要解决重复 ID 问题(当机器时间不对的情况下,可能导致会产生重复 ID)

无序,不能生成递增的 ID,而且很长,入库性能差(MYSQL)。因为MySQL的 是 B+ 树索引,每插入一条新数据,都会对索引进行改造,因为 UUID 的无序,每次插入数据时 B+ 树的改造就会很大,也就是导致索引分裂

应用场景:

用于分布式日志追踪的traceId生成,可参照:基于LOGBACK实现的分布式日志跟踪

案例

java 复制代码
import java.util.UUID;

public class IdGenerator {
    public IdGenerator() {

    }

    public static String createID() {
        String id = UUID.randomUUID().toString();
        id = id.replace("-","");
        return id;
    }
}

数据库自增主键

直接依赖关系型数据库机制-自增主键来产生唯一ID

优点:

实现起来比较简单、ID 有序递增、存储消耗空间小

缺点:

支持的并发量不大(依赖数据库性能)

存在数据库单点问题(可以使用数据库集群解决,不过增加了复杂度)

ID 没有具体业务含义

每次获取 ID 都要访问一次数据库(增加了对数据库的压力)

应用场景

可以用于TPS不高场景的分布式唯一ID获取

不过需要注意自增键初始值和步长,不能随意更改 主要是auto_increment_incrementauto_increment_offset参数

案例

建表

sql 复制代码
drop TABLE IF EXISTS `sequence_id`;
CREATE TABLE `sequence_id` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT comment '自增ID',
  `stub` varchar(20) NOT NULL DEFAULT '' comment '随机值',
  PRIMARY KEY (`id`),
  UNIQUE KEY `stub` (`stub`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=10 comment '自增序列表';

mybatics xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.toby.dynamic.data.source.db.dao.config.SeqIdMapper">

    <insert id="getNextId">
        <selectKey keyProperty="id" resultType="java.lang.Long" order="AFTER">
            SELECT LAST_INSERT_ID()
        </selectKey>
        INSERT INTO sequence_id (stub) VALUES (#{name})
    </insert>
</mapper>

mapper文件

java 复制代码
public interface SeqIdMapper {

    long getNextId(@Param("name") String name);
}

test用例

java 复制代码
package com.toby.dynamic.data.source.db.dao.config;

import com.toby.dynamic.data.source.start.PointApplication;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = PointApplication.class)
public class SeqIdMapperTest {
    private static final Logger LOGGER = LoggerFactory.getLogger(SeqIdMapperTest.class);
    @Autowired
    private SeqIdMapper seqIdMapper;

    @Test
    public void getNextId() {
        long ret = seqIdMapper.getNextId(String.valueOf(System.currentTimeMillis()));
        LOGGER.info("getNextId ret={}", ret);
        Assert.assertTrue(ret > 0);
    }
}

运行结果

sql 复制代码
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@54be6213] was not registered for synchronization because synchronization is not active
JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@322b09da] will not be managed by Spring
==>  Preparing: INSERT INTO sequence_id (stub) VALUES (?) 
==> Parameters: 1703319042518(String)
<==    Updates: 1
==>  Preparing: SELECT LAST_INSERT_ID() 
==> Parameters: 
<==    Columns: LAST_INSERT_ID()
<==        Row: 1
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@54be6213]

具体代码参考:github.com/hongyuwen/H...

未完待续

参考
分布式 ID 详解
分布式ID

相关推荐
程序员敲代码吗5 分钟前
Spring Boot与Tomcat整合的内部机制与优化
spring boot·后端·tomcat
牛奔35 分钟前
如何理解 Go 的调度模型,以及 G / M / P 各自的职责
开发语言·后端·golang
chilavert31837 分钟前
技术演进中的开发沉思-357:重排序(下)
java·后端
Boop_wu44 分钟前
Spring生态
java·后端·spring
jzheng86101 小时前
Spring Boot(快速上手)
java·spring boot·后端
代码改善世界1 小时前
CANN深度解构:中国AI系统软件的原创性突破与架构创新
大数据·人工智能·架构
怒放吧德德1 小时前
Python3基础:基础实战巩固,从“会用”到“活用”
后端·python
晚霞的不甘1 小时前
Flutter for OpenHarmony 实现计算几何:Graham Scan 凸包算法的可视化演示
人工智能·算法·flutter·架构·开源·音视频
Tadas-Gao2 小时前
TCP粘包现象的深度解析:从协议本质到工程实践
网络·网络协议·云原生·架构·tcp
苏三说技术2 小时前
xxl-job 和 elastic-job,哪个更好?
后端