全局ID生成方式

news2024/11/22 20:59:58

全局ID生成方式

目录

  • 1. 全局唯一id介绍
    • 1.1 特点
  • 2. 常见的全局唯一id生成策略
    • 2.1 利用数据库自增字段生成id
    • 2.2 UUID
    • 2.3 Redis生成id
    • 2.4 zookeeper生成ID
    • 2.5 Twitter的snowflake算法
  • 3. 面试题目:实现一个全局的ID生成器,注意线程安全
    • 3.1 单例模式分类
    • 3.2 普通懒汉模式单例(线程不安全)
    • 3.3 线程安全懒汉模式单例
    • 3.4 饿汉模式单例

本文章中前面两个章节转载自:

https://cloud.tencent.com/developer/article/1884037

1. 全局唯一id介绍

在复杂的分布式系统中,需要对大量的数据和消息进行唯一标识,在设计的初期就要考虑到日后的数据量的级别,如果需要对数据库进行分库分表,就需要有一个全局唯一id来标识一条数据或记录。

1.1 特点

全局唯一id主要有以下几个特点

  • 全局唯一性
  • 趋势递增:MySQL InnoDB默认使用的是聚簇索引,底层使用B+ tree的数据结构来存储索引数据,在主键选择上尽量使用有序的主键保证写入性能
  • 单调递增:保证下一个ID一定大于上一个ID,例如事务版本号、**IM**增量消息、排序等特殊需求
  • 信息安全:如果ID是连续的,恶意用户的爬取工作就非常容易做了,在一些应用场景下,需要ID无规则、不规则。
  • 高可用性:同时除了对ID号码自身的要求,业务还对ID号生成系统的可用性要求极高,如果ID系统瘫痪,会带来一场灾难,所以不能有单点故障。
  • 分片支持
  • 长度适中

2. 常见的全局唯一id生成策略

2.1 利用数据库自增字段生成id

优点

  • 简单:成本小,代码简单,性能可以接收
  • ID号单调递增,可以实现一些对ID有特殊要求的业务,比如分页或排序等

缺点

  • 强依赖DB。数据库迁移、多数据库版本支持、或分库分表时需要处理,比较麻烦
  • 单点故障。单个数据库或读写分离或一主多从的情况下,只有一个主库可以生成,有单点故障的风险
  • 数据一致性问题。配置主从复制可以尽可能的增加可用性,但是数据一致性在特殊情况下难以保证。
  • 难于扩展。在新能达不到要求的情况下,难以扩展

部分优化方案

对于主库单点,如果有多个Master库,则每个Master库设置的起始数字不一样,步长一样,可以是Master的个数。例如:Master 1生成的是1,4,7,10;Master 2生成的是2,5,8,11;Master 3生成的是3,6,9,12。这样就可以有效生成集群的唯一ID,也可以大大降低ID生成数据库操作的负载

2.2 UUID

常见的id生成方式,利用程序生成

UUID的目的是让分布式系统中的所有元素,都能有唯一的辨识咨询,而不需要透过中央控制端来做辨识资讯的指定。

UUID的标准形式包含32个16进制数字,以‘-’分为5段,示例:550e8400-e29b-41d4-a716-446655440000
在这里插入图片描述

C++中使用boost生成uuid的示例

#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <boost/uuid/uuid_generators.hpp>

int main() {
    boost::uuids::uuid a_uuid = boost:uuids::random_generator();
    string uuid_string = boost::uuids::to_string(a_uuid);
    return 0;
}

优点

  • 非常简单,本地生成,可以调用API
  • 性能很高,没有网络消耗,基本不会有性能问题
  • 全球唯一,在数据迁移、系统数据合并等情况下可以从容应对

缺点

  • 存储成本高。16字节128位,通常用36长度的字符串表示,海量数据场景下,需要考虑存储量的问题
  • 信息不安全。基于MAC地址生成UUID的算法可能造成MAC地址泄漏
  • 不适用作为主键
  • UUID是无序的
  • 传输数据量大
  • 不可读

部分优化方案

  • 为了解决UUID不可读,可以使用UUID to Int64方法
  • 为了解决UUID无序的问题, NHibernate在其主键生成方式中提供了Comb算法(combined guid/timestamp)。保留GUID的10个字节,用另6个字节表示GUID生成的时间(DateTime)。

2.3 Redis生成id

当使用数据库生成ID性能不够要求的时候,可以尝试使用Redis来生成ID,这主要依赖于Redis是单线程的,所以也可以用于生成全局唯一的ID。可以使用Redis的原子操作**INCR和INCRBY**来实现。

可以使用Redis集群来获取更高的吞吐量。假如一个集群中有5台Redis。可以初始化每台Redis的值分别是1,2,3,4,5,然后步长都是5。

优点

  • 不依赖数据库,灵活方便,且性能优于数据库
  • 数字ID天然排序,对分页或需要排序的结果很有帮助

缺点

  • 如果系统中没有Redis,还需要引入新的组件,增加系统的复杂度
  • 需要编码和配置的工作量比较大
  • Redis单点故障,影响序列服务的可用性

2.4 zookeeper生成ID

zookeeper主要通过其中znode数据版本来生成序列号,可以生成32位和64位的数据版本号,客户端可以使用这个版本号来作为唯一的序列号。

很少会使用zookeeper来生成唯一ID。主要是由于需要依赖zookeeper,并且是多步调用API,如果在竞争较大的情况下,需要考虑分布式锁。因此,性能在高并发的分布式环境下,也不是很理想。

2.5 Twitter的snowflake算法

snowflake(雪花算法)是Twitter开源的分布式ID生成算法,结果是一个long型的ID。这种方案把64-bit分别划分成多段,分开来标识机器、时间等。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sI2XDQmC-1692772460124)(image/image_OUDNHYGCkP.png)]

其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。具体实现的代码可以参看github

snowflake算法可以根据自身项目的需要进行一定的修改。比如估算未来的数据中心个数,每个数据中心的机器数以及统一毫秒可以能的并发数来调整在算法中所需要的bit数。

优点

  • 稳定性高,不依赖于数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的。
  • 灵活方便,可以根据自身业务特性分配bit位。
  • 单机上ID单调自增,毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。

缺点

  • 强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。
  • ID可能不是全局递增。在单机上是递增的,但是由于涉及到分布式环境,每台机器上的时钟不可能完全同步,也许有时候也会出现不是全局递增的情况。

3. 面试题目:实现一个全局的ID生成器,注意线程安全

这是我在最近面试过程中遇到的一道代码题目,个人思路是考察设计模式中的单例模式,考虑线程安全的话需要使用饿汉模式,或者双重检查锁的懒汉模式。

3.1 单例模式分类

单例模式可以分为懒汉和饿汉,两者之间的区别在于创建实例的时间不同

  • 懒汉:系统运行时,实例并不存在,只有当需要使用该实例时,才会去创建并使用实例(这种方式需要考虑线程安全
  • 饿汉指系统一运行,就初始化创建实例,当需要时,直接调用即可(本身就线程安全,没有多线程的问题

3.2 普通懒汉模式单例(线程不安全)

#include <iostream>
#include <mutex>
#include <pthread.h>

using namespace std;

class IdGenerator {
public:
    // 获取单例对象 
    static IdGenerator* getInstance();

    // 释放单例,进程退出时调用
    static void deleteInstance();

    // 获取唯一ID
    int getID();
private:
    IdGenerator();
    ~IdGenerator();
    // 将拷贝构造函数设为私有,禁止外部拷贝
    IdGenerator(const IdGenerator& rhs);
    const IdGenerator& operator=(const IdGenerator& rhs);

    // 唯一单例对象指针
    static IdGenerator* m_IdGenerator;

    // 全局唯一ID
    int id;
};

// 初始化静态成员变量
IdGenerator* IdGenerator::m_IdGenerator = NULL;

int IdGenerator::getID() {
    return id ++;
}

IdGenerator* IdGenerator::getInstance() {
    if (m_IdGenerator == nullptr) {
        m_IdGenerator = new IdGenerator();
    }
    return m_IdGenerator;
}

void IdGenerator::deleteInstance() {
    if (m_IdGenerator) {
        delete m_IdGenerator;
        m_IdGenerator = nullptr;
    }
}

IdGenerator::IdGenerator() : id(0) {}
IdGenerator::~IdGenerator() {}

3.3 线程安全懒汉模式单例

双重检查锁

#include <iostream>
#include <mutex>
#include <pthread.h>

using namespace std;

// 锁对象
std::mutex g_lock;

class IdGenerator {
public:
    // 获取单例对象 
    static IdGenerator* getInstance();

    // 释放单例,进程退出时调用
    static void deleteInstance();

    // 获取唯一ID
    int getID();
private:
    IdGenerator();
    ~IdGenerator();
    // 将拷贝构造函数设为私有,禁止外部拷贝
    IdGenerator(const IdGenerator& rhs);
    const IdGenerator& operator=(const IdGenerator& rhs);

    // 唯一单例对象指针
    static IdGenerator* m_IdGenerator;

    // 全局唯一ID
    int id;
};

// 初始化静态成员变量
IdGenerator* IdGenerator::m_IdGenerator = NULL;

int IdGenerator::getID() {
    return id ++;
}

IdGenerator* IdGenerator::getInstance() {
    if (m_IdGenerator == nullptr) { // 第一重检查
        std::lock_guard<std::mutex> lock(g_lock);
        if (m_IdGenerator == nullptr) { // 第二重检查
            m_IdGenerator = new IdGenerator(); 
        }
    }
    return m_IdGenerator;
}

void IdGenerator::deleteInstance() {
    if (m_IdGenerator) {
        delete m_IdGenerator;
        m_IdGenerator = nullptr;
    }
}

IdGenerator::IdGenerator() : id(0) {}
IdGenerator::~IdGenerator() {}

3.4 饿汉模式单例

#include <iostream>
#include <pthread.h>

using namespace std;

class IdGenerator {
public:
    // 获取单例对象 
    static IdGenerator* getInstance();

    // 释放单例,进程退出时调用
    static void deleteInstance();

    // 获取唯一ID
    int getID();
private:
    IdGenerator();
    ~IdGenerator();
    // 将拷贝构造函数设为私有,禁止外部拷贝
    IdGenerator(const IdGenerator& rhs);
    const IdGenerator& operator=(const IdGenerator& rhs);

    // 唯一单例对象指针
    static IdGenerator* m_IdGenerator;

    // 全局唯一ID
    int id;
};

// 初始化静态成员变量
IdGenerator* IdGenerator::m_IdGenerator = new IdGenerator();

int IdGenerator::getID() {
    return id ++;
}

IdGenerator* IdGenerator::getInstance() {
    return m_IdGenerator;
}

void IdGenerator::deleteInstance() {
    if (m_IdGenerator) {
        delete m_IdGenerator;
        m_IdGenerator = nullptr;
    }
}

IdGenerator::IdGenerator() : id(0) {}
IdGenerator::~IdGenerator() {}

参考:

https://blog.csdn.net/fly910905/article/details/79286680

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/923668.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【PCL-9】AABB包围盒

当一个物体边与坐标轴平行时&#xff0c;生成的ABB最小外接立方体有偏差&#xff0c;故这里采用AABB算法。 示例代码&#xff1a; #include <pcl/features/moment_of_inertia_estimation.h> #include <vector> #include <pcl/io/pcd_io.h> #include <pc…

在 Pytorch 中使用 TensorBoard

机器学习的训练过程中会产生各类数据&#xff0c;包括 “标量scalar”、“图像image”、“统计图diagram”、“视频video”、“音频audio”、“文本text”、“嵌入Embedding” 等等。为了更好地追踪和分析这些数据&#xff0c;许多可视化工具应运而生&#xff0c;比如之前介绍的…

机器学习十大算法之七——随机森林

0 引言 集成学习&#xff08;ensemble learning&#xff09;是时下非常流行的机器学习算法&#xff0c;它本身不是一个单独的机器学习算法&#xff0c;而是通过在数据上构建多个横型&#xff0c;集成所有模型的建模结果&#xff0c;基本上所有的机器学习领域都可以看到集成学习…

华为OD机试 - 连续字母长度 - 字符串(Java 2023 B卷 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述1、输入2、输出3、说明4、再输入5、输出6、说明 四、解题思路五、Java算法源码六、效果展示1、输入2、输出3、说明 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08…

混币器——隐私交易的天堂,还是洗钱犯罪的聚集地?

据美国财政部官网&#xff0c;Tornado Cash 联创 Roman Storm 已被 FBI 和国税局逮捕&#xff0c;罪名是串谋洗钱、串谋经营未经许可的资金传输业务以及串谋违反制裁规定&#xff0c;另一创始人 Roman Semenov仍然在逃。 FBI局长Christopher A. Wray说&#xff1a;“今天的公告…

动物体外受精手术VR模拟仿真培训系统保证学生及标本的安全

奶牛是养殖业主要的资源&#xff0c;因此保证奶牛的健康对养殖业的成功和可持续发展具有重要已用&#xff0c;奶牛有一些常见易发病&#xff0c;一旦处理不当&#xff0c;对奶牛业都会造成较大的经济损失&#xff0c;传统的奶牛手术培训实操难度大、风险高且花费大&#xff0c;…

[管理与领导-43]:IT基层管理者 - 个人管理 - 管理中从角色定位迈步

前言&#xff1a; 管理者的“四位” &#xff1a; ‣ 定位——在什么位置做什么事情&#xff1b; ‣ 到位——全力以赴把事情做好&#xff1b; ‣ 不越位——不要把别人的工作做了&#xff1b; ‣ 补位——同事临时“缺位” &#xff0c;及时补位&#xff0c;提升效率&…

前端(十四)——DOM节点操作手册:你需要了解的一切

&#x1f642;博主&#xff1a;小猫娃来啦 &#x1f642;文章核心&#xff1a;DOM节点操作手册&#xff1a;你需要了解的一切 文章目录 前言DOM基础知识操作现有节点创建新节点遍历节点树修改节点属性和样式事件处理实践应用动态创建表格动态更新列表 前言 DOM&#xff08;文档…

性能测试工具Jmeter你所不知道的内幕

谈到性能测试&#xff0c;大家一定会联想到Jmeter和LoadRunner,这两款工具目前在国内使用的相当广泛&#xff0c;主要原因是Jmeter是开源免费&#xff0c;LoadRunner 11在现网中存在破解版本。商用型性能测试工具对于中小型企业很难承担相关的费用。国内的性能测试工具有&#…

【Linux】一张图了解系统文件

首先先认识磁盘结构 系统文件分布图 文件查找 文件删除 文件的增删改查都是围绕inode来完成的&#xff0c;所以当我们要进行文件删除的时候&#xff0c;只需要通过inode来获取到它对应的block bitmap和inode bitmap数据块容器和保存文件属性的位置置为 0即可 &#xff0c;如果想…

Visual Studio 2022的MFC框架——WinMain函数

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天我们来重新审视一下Visual Studio 2022下开发工具的MFC框架知识。 大家还记得创建Win32应用程序是怎么弄的吗&#xff1f;Win32应用程序有一条很明确的主线&#xff1a;首先进入WinMain函数&#xff0c…

RK3568 安卓源码编译

一.repo安卓编译工具 项目模块化/组件化之后各模块也作为独立的 Git 仓库从主项目里剥离了出去&#xff0c;各模块各自管理自己的版本。Android源码引用了很多开源项目&#xff0c;每一个子项目都是一个Git仓库&#xff0c;每个Git仓库都有很多分支版本&#xff0c;为了方便统…

一种特殊的NC文件转TIF——知道每个像元的坐标值怎么转为TIF

之前写过一篇文章&#xff0c;介绍了NetCDF文件格式&#xff0c;并详细讲解了如何使用Python对NetCDF文件进行读写操作&#xff0c;进而介绍了NetCDF文件的地理参考&#xff0c;最后以两个数据为例讲解了怎么将NetCDF格式的数据转GeoTIFF格式的数据。 但是上次介绍的NC文件的地…

1212页Kubernetes学习指南,几乎涵盖所有K8S核心技术点,建议收藏!

Kubernetes&#xff08;简称K8S&#xff09;是一个开源的容器编排平台&#xff0c;主要用于自动化部署、扩展和管理容器化应用程序。由于K8S在容器编排方面的优势&#xff0c;它已经成为了现代化应用程序部署和管理的事实标准 因此&#xff0c;对于运维人员来说&#xff0c;学…

数电票红利悄然而至 百望云数据驱引“供应链协同”新纪元

2023年&#xff0c;全面数字化的电子发票政策正如飞驰的列车在各地试点推广中加速前行&#xff0c;“以数治税”的全新时代即将来临&#xff0c;基于数电票赋能的企业数字化红利正在悄然释放。借着政策快速落地的东风&#xff0c;财税数字化也进入到全面建设的新周期&#xff0…

打造B2B2C商城系统成功的关键要素有哪些?

电子商务的蓬勃发展&#xff0c;B2B2C商城系统成为了越来越多企业的选择。然而&#xff0c;在激烈的市场竞争中&#xff0c;要想打造一款成功的B2B2C商城系统并不容易。下面就B2B2C商城系统成功的关键要素作一些简单介绍&#xff0c;希望对大家有帮助(仅供参考)。 一、整合供应…

VIT 论文精读 | transformer架构引入CV的开创性工作

目录 1. 背景 2. 方法 2.1 怎么把2D图像变成1D序列输入到transformer中 像素&#xff1f; 先提取特征图&#xff1f; 打成多个patch 2.2 transformer和卷积网络比较 2.3 结构 1. 背景 VIT是基于transformer的在图像分类大放异彩的变体&#xff0c;transformer是VIT的亲…

LeetCode面试经典150题(day 2)

26. 删除有序数组中的重复项 难度:简单 给你一个 升序排列 的数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。 考虑 nums 的唯…

【LeetCode-中等题】56. 合并区间

题目 题解一&#xff1a;排序 思路&#xff1a; 1、对数组按第一个元素大小进行排序&#xff0c;使得数组按大小连续排列 2、先将第一个数组提前放入list集合&#xff0c;因为肯定是从第一个开始算起的 3、接着对下一个数组的左边界和list集合最后一个元素的右边界对比&#x…

go学习之go的语法知识

文章目录 1.go语言开发注意事项2.golang常用的转义字符(escape char)3.golang开发常用的问题小结与提示&#xff1a; 4.go语言注释类型&#xff08;1&#xff09;.注释类型1&#xff09;行注释2&#xff09;块注释(多行注释) &#xff08;2&#xff09;使用细节&#xff1a;1&a…