高并发系统-分布式唯一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

相关推荐
码蜂窝编程官方17 分钟前
【含开题报告+文档+PPT+源码】基于SpringBoot+Vue的虎鲸旅游攻略网的设计与实现
java·vue.js·spring boot·后端·spring·旅游
hummhumm35 分钟前
第 25 章 - Golang 项目结构
java·开发语言·前端·后端·python·elasticsearch·golang
J老熊1 小时前
JavaFX:简介、使用场景、常见问题及对比其他框架分析
java·开发语言·后端·面试·系统架构·软件工程
猿java1 小时前
什么是 Hystrix?它的工作原理是什么?
java·微服务·面试
AuroraI'ncoding1 小时前
时间请求参数、响应
java·后端·spring
好奇的菜鸟1 小时前
Go语言中的引用类型:指针与传递机制
开发语言·后端·golang
Alive~o.01 小时前
Go语言进阶&依赖管理
开发语言·后端·golang
许苑向上1 小时前
Dubbo集成SpringBoot实现远程服务调用
spring boot·后端·dubbo
郑祎亦2 小时前
Spring Boot 项目 myblog 整理
spring boot·后端·java-ee·maven·mybatis
本当迷ya2 小时前
💖2025年不会Stream流被同事排挤了┭┮﹏┭┮(强烈建议实操)
后端·程序员