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

相关推荐
~kiss~几秒前
Rust~二刷异步逻辑
开发语言·后端·rust
菠菠萝宝4 分钟前
【Java八股文】11-分布式及场景面试篇
java·分布式·面试·k8s·系统·uuid·mq
SomeB1oody9 分钟前
【Rust中级教程】2.7. API设计原则之灵活性(flexible) Pt.3:借用 vs. 拥有、`Cow`类型、可失败和阻塞的析构函数及解决办法
开发语言·后端·性能优化·rust
larance18 分钟前
Flask 发送邮件
后端·python·flask
Aska_Lv24 分钟前
从零到一写组件库-日志组件库
后端
hxung29 分钟前
MySQL面试学习
学习·mysql·面试
ybq1951334543144 分钟前
javaEE-SpringBoot日志
java·spring boot·后端
PyAIGCMaster1 小时前
第二周补充:Go语言中&取地址符与fmt函数详解
开发语言·后端·golang
Dongwoo Jeong1 小时前
缓存基础解释与缓存友好型编程基础
后端·c·cache·cache friendly
Gy-1-__1 小时前
【springcloud】快速搭建一套分布式服务springcloudalibaba(一)
后端·spring·spring cloud