分布式ID多种生成方式

分布式ID

雪花算法(时间戳41+机器编号10+自增序列号10)

作用:希望ID按照时间进行有序生成

原理:

即一台带有编号的服务器在毫秒级时间戳内生成带有自增序号的ID,这个ID保证了自增性和唯一性

雪花算法根据结构的生成ID个数的上线时,每毫秒最多能生成2^12次方个ID即4096个

总共64个Bit位,第一个位置不用,使用41个比特位作为时间戳(可以使用69年,单位时毫秒级别)、使用10个bit为作为机器ID(机器编号),最后使用12个比特位作为自增ID

特点:

1、按照时间递增

2、因为分布式ID采用了机器编号组成,因此,就算进行分布式部署,由于机器编号不同,最后生成的ID也不同,做到了不重复

3、1毫秒产生4096个id,那么1秒内就能产生400万左右个的ID

实现方式:

now:生成id的时间戳

workId:机器编号id

n:序列号

大概生成伪代码

now=System.now();//获取当前时间戳
if(now==last){last //为前一个生成id的时间戳
    n++;
  if(n>4069){//判断序列号是否超过每毫秒内的最大值   
    now=nextTime();
      n=0;;
    

}
}else{
  n=0;
}

long id=(now<<22)|(workId<<12)|n; //去或运算

实现方式:可以使用Redis来完成

@Component
public class RedisIdWorker {
    /**
     * 开始时间戳
     */
    private static final long BEGIN_TIMESTAMP = 1640995200L;
    /**
     * 序列号的位数
     */
    private static final int COUNT_BITS = 22;
    private static final int work_id = 452;//机器id

    private StringRedisTemplate stringRedisTemplate;

    public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    public long nextId(String keyPrefix) {
        // 1.生成时间戳
        LocalDateTime now = LocalDateTime.now();//需要获得毫秒级的时间戳
        long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
        long timestamp = nowSecond - BEGIN_TIMESTAMP;//得到毫秒

        // 2.生成序列号
        // 2.1.获取当前日期,精确到天
        String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
        // 2.2.自增长
        long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);

        // 3.拼接并返回
        return timestamp << COUNT_BITS | work_id<<12|count;
        //时间戳左移22位,机器id左移12位,序列号
    }
}

Redis实现全局唯一Id(时间戳+自增序号)

为了增加ID的安全性,我们可以不直接使用Redis自增的数值,而是拼接一些其它信息:

时间戳:31bit,以秒为单位,可以使用69年

序列号:32bit,秒内的计数器,支持每秒产生2^32个不同ID

将时间戳转化为日期格式,然后作为key好处

  • 1、首先就是通过redis的key不能重复来进行计算,每秒内进行自增
  • 2、便于统计每天、每月的一个id生成量

分布式ID生成方式

  • 1、UUID
  • 2、数据库自增ID
  • 3、Redis
  • 4、雪花算法

UUID:

缺点:

  • 生成不是自增的,会导致插入数据库的效率较慢
  • 2、生成所需要的空间较大

数据库自增

数据库自增ID:分库分表情况下会重复

解决方式:单独使用一张表(主要字段,id,其他字段)来生成分布式ID

表结构如下:

create table order_id(
 id bigint(20) unsigned not null auto_increment,
  stub char(1) not null default '',
  primary key (id),
  unique key sub(stub)
  )
  
begin;
replace into order_id stub values('b');//如果数据库中有字段b的值那么会先将原来字段删除,再进行插入,否则直接插入
select Last_insert_id();//通过这个查询拿到最后一个ID
commit;

缺点:

在高并发、数据量大的情况下性能比较差

对于高并发方面:比如每秒几十万的生成,那么需要同时去数据库执行这个生成ID,数据库扛不住

第二个就是,每次生成ID都需要去访问数据库,性能肯定不会太好,就算id表的数据很少基本就是用1条,但需要和数据库交互性能本就会受影响

当然解决上面方法痛点可以采用集群模式

比如可以将请求负载均衡到多个数据库,数据库A自增比如只生成奇数1,3,5,数据库只生成偶数比如2,4,6

这种方式的问题:

  • 1、不是绝对的ID递增
  • 2、对于高并发情况:如果要使用数据库方式,那么就只有一直加机器,让其负载均衡
    • 如果一开始并发量不大,我们就使用两台机器来做分布式自增id,但是后面并发量变大了,两台并发量扛不住,那只有加机器,那么我们可以先拿到此时库中最大的id(比如10000),然后将所有数据库的自增起始值设置为大于这个值比如(10w)的,然后再设置对应的步长,但是这个方式设置,需要对数据库进行重启(所以说这种方式在高并发情况下并不合适)

雪花算法

public class SnowflakeIdWorker{
    /** 开始时间截 (2015-01-01) */
    private final long twepoch = 1288834974657L;
 
    /** 机器id所占的位数 */
    private final long workerIdBits = 5L;
 
    /** 数据标识id所占的位数 */
    private final long datacenterIdBits = 5L;
 
    /** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
 
    /** 支持的最大数据标识id,结果是31 */
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
 
    /** 序列在id中占的位数 */
    private final long sequenceBits = 12L;
 
    /** 机器ID向左移12位 */
    private final long workerIdShift = sequenceBits;
 
    /** 数据标识id向左移17位(12+5) */
    private final long datacenterIdShift = sequenceBits + workerIdBits;
 
    /** 时间截向左移22位(5+5+12) */
    private final long timestampLeftShift = sequenceBits + workerIdBits
            + datacenterIdBits;
 
    /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);
 
    /** 工作机器ID(0~31) */
    private long workerId;
 
    /** 数据中心ID(0~31) */
    private long datacenterId;
 
    /** 毫秒内序列(0~4095) */
    private long sequence = 0L;
 
    /** 上次生成ID的时间截 */
    private long lastTimestamp = -1L;
 
    /**
     * 构造函数
     *
     * @param workerId
     *            工作ID (0~31)
     * @param datacenterId
     *            数据中心ID (0~31)
     */
    public SnowflakeIdWorker(long workerId, long datacenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format(
                    "worker Id can't be greater than %d or less than 0",
                    maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format(
                    "datacenter Id can't be greater than %d or less than 0",
                    maxDatacenterId));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }
 
    /**
     * 获得下一个ID (该方法是线程安全的)
     *
     * @return SnowflakeId
     */
    public synchronized long nextId() {
        long timestamp = timeGen();
 
        // 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
        //这里是处理时间回波的方案:为什么会有:因为我们的ID是对当前时间具有比较高的依赖性
        //所以一半会采取将生成分布式ID的服务器的时间和时间服务器的时间进行同步,
        //如果发现本地生成ID的这台服务器的时间走快了,那么再去时间的时候就会有时间回退到之前
        //比如现在系统是12:30:05,然后准确时间是20:30:00
        // 那么时间就会回退到30:00那么如果不额外处理就会造成ID重复的问题
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(
                    String.format(
                            "Clock moved backwards.  Refusing to generate id for %d milliseconds",
                            (lastTimestamp - timestamp)));
        }
 
        // 如果是同一时间生成的,则进行毫秒内序列
        if (lastTimestamp == timestamp) {
            //mask:4095即二进制12个1(即如果序列号小于4095时候就是本身,
            //如果超过了4096了比如4096那么后面12位就是0,那么做与运算之后序列号就变为0
            sequence = (sequence + 1) & sequenceMask;
            // 毫秒内序列溢出
            if (sequence == 0) {
                //超过了4096,那么就等下一秒
                // 阻塞到下一个毫秒,获得新的时间戳
                timestamp = tilNextMillis(lastTimestamp);
            }
        }
        // 时间戳改变,毫秒内序列重置
        else {
            sequence = 0L;
        }
 
        // 上次生成ID的时间截
        lastTimestamp = timestamp;
 
        // 移位并通过或运算拼到一起组成64位的ID
        //timestamp - twepoch(twepoch一般设置为服务启动的一个初始时间,因为雪花算法最多69年
        //减去之后时间就可以长一点,比如初始时间2024年的,那么就可以用用到2024+69
        return ((timestamp - twepoch) << timestampLeftShift) //
                | (datacenterId << datacenterIdShift) //
                | (workerId << workerIdShift) //
                | sequence;
    }
 
    /**
     * 阻塞到下一个毫秒,直到获得新的时间戳
     *
     * @param lastTimestamp
     *            上次生成ID的时间截
     * @return 当前时间戳
     */
    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }
 
    /**
     * 返回以毫秒为单位的当前时间
     *
     * @return 当前时间(毫秒)
     */
    protected long timeGen() {
        return System.currentTimeMillis();
    }
 
    //测试方法
    public static void main(String[] args) {
 
        // 假设我们有一个工作机器ID为1,数据中心ID为1的环境
        long workerId = 1L;
        long datacenterId = 1L;
        
        // 创建一个SnowflakeIdWorker实例
        SnowflakeIdWorker idWorker = new SnowflakeIdWorker(workerId, datacenterId);
        
        // 生成并打印10个ID作为示例
        for (int i = 0; i < 10; i++) {
            long id = idWorker.nextId();
            System.out.println(id);
        }
    }
 
}

时间回拨:

  • 如果回波时间很短比如(<=100ms)
    • 比较短可以考虑直接sleep一段时间(等时间大于原来的时间后继续生成ID)
  • 如果回拨时间适中比如(100ms<s<=1s)
    • 我们可以采用缓存机制,即缓存下每一毫秒生成的最大id,可以使用hashmap等结构
    • 然后出现时间回波后就只需要拿到最后那一毫秒的最大ID,然后再此基础上进行自增
    • 时间太长不太适合:比较占内存
  • 如果时间比较长,比如(1s<s<=5s)
    • 这台机器上出现时间回波,那我就让客户端进行重试策略,去另一个分布式ID服务上去拿ID
  • 当时间回拨非常长比如(5<s):
    • 就可以考虑将这个服务进行下线,然后将这台机器时间不同好之后又重新上线

美团Leaf分布式ID生成方案(号段模式)

提供了两种方式:

Leaf 提供了 号段模式Snowflake(雪花算法) 这两种模式来生成分布式 ID。并且,它支持双号段,还解决了雪花 ID 系统时钟回拨问题。不过,时钟问题的解决需要弱依赖于 Zookeeper(使用 Zookeeper 作为注册中心,通过在特定路径下读取和创建子节点来管理 workId) 。

Leaf 的诞生主要是为了解决美团各个业务线生成分布式 ID 的方法多种多样以及不可靠的问题。

Leaf 对原有的号段模式进行改进,比如它这里增加了双号段避免获取 DB 在获取号段的时候阻塞请求获取 ID 的线程。简单来说,就是我一个号段还没用完之前,我自己就主动提前去获取下一个号段(图片来自于美团官方文章:《Leaf——美团点评分布式 ID 生成系统》)。

号段模式

核心:数据库中一次自增一段的ID,然后本地程序拿到这一段ID之后,使用程序进行自己维护自增ID,比如一次访问数据库拿到数据范围是1000~2000,那么我们程序就在这个范围进行自增

优点:

  • 支撑高并发:
  • leaf服务可以很方便的进行线性扩展,性能完全能支撑大多数业务场景
  • ID号码是趋势递增的8byte的64位数字。满足上述数据库存储的主键要求
  • 3、容灾性高:Leaf服务内部有号段缓存,几十DB宕机了,短时间内Leaf服务任然能正常对外提供服务(因为每次leaf服务从DB中拿的是一段ID)
  • 4:灵活性强,比如这里max_id,步长step等可以自定义,直接修改
  • 5、如果后面并发量非常大,那么我们就直接分表就完事了,让每个业务类型使用一张表,一个服务器,即一个表一个业务

缺点:

  • 1、id是有规律的,别人可以根据id可以分析出每天的订单量大概在多少,可能会造成数据泄露
  • 2、数据波动比较大(即在去和数据库进行交互去获取一段的ID的那个时刻,由于各个服务都去拿取这个数据,由于每次获取id后需要进行一个数据的更行操作,所以数据库会有一个行锁,虽然也比较快,但是在高并发情况下,就会导致在取id这一时刻,用户的响应时间会增加)
  • 3、DB宕机,还是

优化段号模式(双段号模式):

上面这个方案的优化:美团Leaf方式---缓存双Buffer

  • 当leaf服务器原来的ID用了10%的时候那就取访问数据库取下一段,重复这个操作,这样处理就比较好的处理

当让也可以使用Redis来完成自增设计,和上面这个设计方式其实也类似

这两种方式都有一个共同的问题:就是自增ID需要依赖于第三方库,比如Mysql,Redis等

大厂基本就是基于雪花算法来进行封装对应的方法的

时钟回波的解决方案:默认的雪花算法时钟回波问题时就抛一个异常,但是对于每个公司来说处理方式是不同的,大多数公司是另外的处理方式,

雪花算法模式

还解决了雪花 ID 系统时钟回拨问题。不过,时钟问题的解决需要弱依赖于 Zookeeper(使用 Zookeeper 作为注册中心,通过在特定路径下读取和创建子节点来管理 workId) 。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数字图像处理:图像分割应用

数字图像处理&#xff1a;图像分割应用 图像分割是图像处理中的一个关键步骤&#xff0c;其目的是将图像分成具有不同特征的区域&#xff0c;以便进一步的分析和处理。 1.1 阈值分割法 阈值分割法&#xff08;Thresholding&#xff09;是一种基于图像灰度级或颜色的分割方法&…

PHP短视频实训平台系统小程序源码

&#x1f3a5;短视频新纪元&#xff01;短视频实训平台系统&#xff0c;解锁创作新技能&#x1f511; &#x1f680;一键入门&#xff0c;创作无界&#x1f310; 想要玩转短视频&#xff0c;却不知从何下手&#xff1f;短视频实训平台系统是你的创意启航站&#xff01;平台内…

「C/C++」C++11 之 std::bitset 二进制数据处理模板库

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「C/C」C/C程序设计&#x1f4da;全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasoli…

python爬虫-爬取蛋白晶体和分子结构

文章目录 前言一、环境准备二、爬取PDB蛋白结构1.下载指定数量的随机PDB2.下载指定靶标的PDB二、从ZINC爬取小分子mol2结构1.下载指定数量的随机分子2.下载指定分子三、从ChEMBL爬取小分子信息1.下载指定ID的SMILES(测试不成功,网站变成readonly了)四、总结爬虫1.查看对应的…

【Vue】Vue3.0(十)toRefs()和toRef()的区别及使用示例

上篇文章&#xff1a;Vue】Vue&#xff08;九&#xff09;OptionsAPI与CompositionAPI的区别 &#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;Vue专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年10月15日11点13分 文章目录 toRefs()和toRe…

生成模型初认识

生成模型初认识 参考学习资料&#xff1a;李宏毅-机器学习 以下为课程过程中的简易笔记 生成模型 为什么要用生成模型&#xff1f;——创造力&#xff1a;同一个输入&#xff0c;产生不同的输出&#xff08;distribution&#xff09;&#xff0c;有一定概率发生某种随机事件…

2024 OSCAR|《开源体系建设路径模式洞察与建议》即将发布

近年来&#xff0c;开源体系建设受到高度重视&#xff0c;国家软件发展战略和“十四五”规划纲要均对开源作出重要部署&#xff0c;为我国开源体系建设和发展指明了方向。9月25日&#xff0c;工业和信息化部党组书记、部长金壮指出要加强开源体系建设&#xff0c;助推产业高质量…

大语言模型(LLM)入门级选手初学教程

链接&#xff1a;https://llmbook-zh.github.io/ 前言&#xff1a; GPT发展&#xff1a;GPT-1 2018 -->GPT-2&GPT-3&#xff08;扩大预训练数据和模型参数规模&#xff09;–> GPT-3.5&#xff08;代码训练、人类对齐、工具使用等&#xff09;–> 2022.11 ChatG…

c++初阶--string类(使用)

大家好&#xff0c;许久不见&#xff0c;今天我们来学习c中的string类&#xff0c;在这一部分&#xff0c;我们首先应该学习一下string类的用法&#xff0c;然后再试着自己去实现一下string类。 在这里&#xff0c;我使用的是这个网站来查找的string类&#xff0c;这里面的内容…

mysql--基本查询

目录 搞定mysql--CURD操作&#xff0c;细节比较多&#xff0c;不难&#xff0c;贵在多多练 1、Create--创建 &#xff08;1&#xff09;单行插入 / 全列插入 &#xff08;2&#xff09;插入否则替换 &#xff08;3&#xff09;替换 2、Retuieve--select 1&#xff09;全…

Android系統Audio hal

一.Android系統Audio hal简介 Android系统的音频硬件抽象层(HAL)是系统与硬件之间的桥梁,允许音频应用和服务访问底层音频硬件,而无需直接与硬件交互。 主要组件: 音频 HAL 接口:定义了应用和服务如何调用音频硬件的规范。典型的音频操作包括播放、录制、音量控制等。 …

day5:权限管理

一&#xff0c;权限概述 什么是权限 合适的人干合适的事情 权限的作用 根据用户的等级&#xff0c;分配对应的权利&#xff0c;完成不同用户对不同资源的相关操作。 权限的分类 r&#xff08;4&#xff09;读w&#xff08;2&#xff09;写x&#xff08;1&#xff09;执行 …

深入理解Agent:从0实现function call

Function的调用时Agent实现很重要的一步&#xff0c;只有 理解了function call这个原理才可以更好的创建Agent。 我将不使用任何langchain等框架或者coze等平台&#xff0c;从0开始构建一个可以调用function的Agent。 源代码已经上传github&#xff1a;https://github.com/as…

美摄科技云服务解决方案,方案成熟,接入简单

美摄科技作为视频处理领域的先锋&#xff0c;凭借其强大的技术实力和深厚的行业经验&#xff0c;推出了成熟的云服务解决方案&#xff0c;为轻量化视频制作开辟了全新的道路。 一、成熟方案&#xff0c;接入无忧 美摄科技云服务解决方案的最大亮点在于其成熟度和易用性。我们…

(六) 进程控制

(六) 进程控制 ps(1) 命令可以帮助我们分析本章中的一些示例&#xff0c;所以简单介绍一些参数的组合方式&#xff0c;更详细的信息请查阅 man 手册。 ps axf 主要用于查看当前系统中进程的 PID 以及执行终端(tty)和状态等信息&#xff0c;更重要的是它能显示出进程的父子关系…

WSL2 构建Ubuntu系统-轻量级AI运行环境

环境&#xff1a;Win11 软件&#xff1a;WSL2 安装环境&#xff1a;Ubuntu 22.04 检查电脑是否开启虚拟化 打开&#xff1a;任务管理器->性能->CPU CPU 开启虚拟化&#xff08;通常默认是开启的&#xff0c;如果没有开启需要BIOS开启&#xff09; 虚拟化设置&#xff0…

uni-app 扫码插件推荐:基于支付宝 mPaaS 扫码组件开发,支持 Android 和 iOS

一. 前言 之前说到&#xff0c;我的一个 uni-app 项目遭到用户吐槽&#xff1a;“你们这个 App 扫码的正确率太低了&#xff0c;尤其是安卓的设备。经常性的扫码扫不出来&#xff0c;就算是扫出来了&#xff0c;也是错误的结果&#xff01;” 面对以上这个问题&#xff0c;我…

Vert.x,认证与授权 - HTTP基本认证

几乎所有的线上系统都离不开认证和授权&#xff0c;Vert.x auth相关组件提供了丰富(Session&#xff0c;JTW, OAuth&#xff0c;…)&#xff0c;便捷的认证和授权支持。 当前&#xff0c;使用最多是Web应用&#xff0c;所以在后续讨论中&#xff0c;都是关于Vert.x auth在Web应…

【大数据分析与挖掘模型】matlab实现——非线性回归预测模型

一、实验目的 掌握有关非线性回归的理论知识&#xff0c;通过变量代换把本来应该用非线性回归处理的问题近似转化为线性回归问题&#xff0c;并进行分析预测。 二、实验任务 对非线性回归实例进行编码计算&#xff0c;实例如下&#xff1a; 三、实验过程 1.运行非线性回归中…

【TFR-Net】基于transformer重建网络

abstract&#xff1a; 提高对数据缺失的鲁棒性已经成为多模态情感分析&#xff08;MSA&#xff09;的核心挑战之一&#xff0c;MSA旨在从语言、视觉和声学信号中判断说话者的情感。在目前的研究中&#xff0c;针对不完全模态特征的MSA&#xff0c;提出了基于平移的方法和张量正…