分布式ID多种生成方式

news2024/10/19 9:39:50

分布式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) 。

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

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

相关文章

密码学原理

1.1 加密算法 Tags: 1、加密算法分类 2、对称算法 <原理、特征、算法> 3、非对称算法 <原理、特征、算法> 4、对称算法vs非对称算法 <结合体> 1、加密算法概述&#xff1a; 用于对用户数据进行加密&#xff0c;常用算法有DES、3DES、AES、RSA、DH算法。 根据…

循序渐进丨在 MogDB 数据库中实现 Oracle ASH能力

我们都知道&#xff0c;当 Oracle 数据库出现性能故障后&#xff0c;一般会在线上实时诊断数据库性能问题&#xff0c;特别是资源突然打高的场景&#xff0c;这个时候用到ASH的数据&#xff0c;就能很大程度上准确定位问题所在。 Oracle ASH 在 Oracle 数据库中&#xff0c;实…

E-R网络

一、ER网络的基本性质 ER网络的生成方式 定义&#xff1a;一个随机图是由N个节点构成并且每对节点之间的连接概率为p G(N,L)模型&#xff1a; 一个随机图由N个节点构成&#xff0c;并且有L条连边随机放置在L对节点之间&#xff08;不出现重边与自环&#xff09; G(N,p)模型…

利用移动式三维扫描技术创建考古文物的彩色纹理网格【上海沪敖3D】

文章来源于蔡司工业质量解决方案&#xff0c;作者蔡司工业质量 在考古环境中&#xff0c;三维扫描技术应用广泛&#xff0c;如存档、保存、复制和分享&#xff08;包括实体和虚拟形式&#xff09;。 文中&#xff0c;通过真实的扫描案例&#xff0c;您将了解到三维光学解决方案…

微信小程序绘制轨迹

1、map | uni-app官网 根据官网描述&#xff1a;通过从数据库获取POI数据&#xff0c;并通过 uni-id-common 内的路线规划API&#xff0c;计算路线、距离、时间。 2、 <map style"width:100%;height:96%;" id"myMap" :scale"scale" :longi…

打包使用pythn编写的maya插件,使用pyeal打包

1.安装python,注意版本一定要和maya上面的python解释器版本一致 2.安装pyeal使用pycharm或者maya自带的python解释器mayapy.exe 3.如果有别的库&#xff0c;下载安装到你需要的文件夹中&#xff1a; 使用mayapy: "D:\AnZhuangBao\maya2022\2022\maya2022AZ\Maya2022\bin\m…

【华为HCIP实战课程十四】OSPF网络中LSA过滤,网络工程师

一、3类LSA过滤以及汇总 我们查看SW3的路由到达R4的lo0下一跳是R1的接口IP 10.1.15.1 我们在SW3上查看3类汇总LSA: SW3的3类汇总LSA可以看到ABR R1和R5到达R4的lo0的度量值分别为48和96,因此SW3到达R4的lo0的地址为48+1=49 和 96+1=97, 因此会显示49的cost,SW3的下一跳为R1的…

word怎么清除格式,Word一键清除所有格式教程

你是否曾在编辑Word文档时遇到过复制内容时格式混乱的情况?别担心&#xff0c;这只需要清除一下格式就可以了&#xff0c;很多朋友还不知道word怎么清除格式&#xff0c;下面小编就来给大家讲一讲word一键清除所有格式的方法教程&#xff0c;操作非常简单&#xff0c;有需要的…

使用短效IP池的优势是什么?

短效IP池作为代理IP服务中一种独特的资源管理方式&#xff0c;其应用已经在数据采集、市场分析和网络安全等多个领域中展示出强大的功能。尽管“短效”听起来似乎意味着某种限制&#xff0c;然而在某些特定的应用场景下&#xff0c;短效IP池却提供了无可比拟的优势。本文将详细…

流量PID控制(开度前馈量计算+辅助PID)

和流体流速(瞬时流量)相关的计算请参考下面文章链接: 1、PLC通过伯努利方程近似计算水箱流量 PLC通过伯努利方程近似计算水箱流量(FC)-CSDN博客文章浏览阅读1.6k次。本文介绍了如何使用PLC通过伯努利方程近似计算水箱中的液体流量,主要涉及流量计算、模型验证、梯形图编程及…

R语言机器学习算法实战系列(六)K-邻近算法 (K-Nearest Neighbors)

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍教程下载数据加载R包导入数据数据预处理数据描述数据切割调节参数构建模型预测测试数据评估模型模型准确性混淆矩阵模型评估指标ROC CurvePRC Curve保存模型总结系统信息介绍 K-邻…

AI创新驱动教育:科技革命下的教育转型

日前&#xff0c;2024教育装备创新大会在杭州市余杭区举行&#xff0c;会上集中展示了AI技术如何赋能教学并深入探讨了其影响。AI技术正在以前所未有的力度&#xff0c;引领教育步入智能新时代&#xff0c;成为教育改革创新的催化剂。 在国家政策的积极推动下&#xff0c;AI技…

tomcat catalina log 出现乱码(SpringMvc)

如下图所示&#xff1a; 解决方法&#xff1a; 找到tomcat的conf文件夹&#xff0c;打开logging.properties&#xff0c;把最后一个UTF-8改成GBK就可以啦 改正后&#xff1a;

基于SpringBoot+Vue+uniapp微信小程序的校园反诈骗微信小程序的详细设计和实现(源码+lw+部署文档+讲解等)

项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念&#xff0c;提供了一套默认的配置&#xff0c;让开发者可以更专注于业务逻辑而不是配置文件。Spring Boot 通过自动化配置和约…

2024系统架构师---论软件维护方法及其应用

论软件维护方法及其应用 软件维护是指在软件交付使用后&#xff0c;直至软件被淘汰的整个时间范围内&#xff0c;为了改正错误或满足新的需求而修改软件的活动。在软件系统运行过程中&#xff0c;软件需要维护的原因是多种多样的。根据维护的原因不同可以将软件维护分为改正性…

C语言_指针_进阶

引言&#xff1a;在前面的c语言_指针初阶上&#xff0c;我们了解了简单的指针类型以及使用&#xff0c;下面我们将进入更深层次的指针学习&#xff0c;对指针的理解会有一个极大的提升。从此以后&#xff0c;指针将不再是难点&#xff0c;而是学习底层语言的一把利器。 本章重点…

MySQL中查询语句的执行流程

文章目录 前言流程图概述最后 前言 你好&#xff0c;我是醉墨居士&#xff0c;今天我们一起探讨一下执行一条查询的SQL语句在MySQL内部都发生了什么&#xff0c;让你对MySQL内部的架构具备一个宏观上的了解 流程图 概述 对于查询语句的SQL的执行流程&#xff0c;主要可以分为…

青少年编程能力等级测评CPA C++(二级)试卷(2)

青少年编程能力等级测评CPA C&#xff08;二级&#xff09;试卷&#xff08;2&#xff09; 一、单项选择题&#xff08;共20题&#xff0c;每题3.5分&#xff0c;共70分&#xff09; CP2_2_1&#xff0e;下列C程序段中&#xff0c;对二维数组arr的定义不正确是&#xff08; &…

(新手入门篇)2024年10 月 Java JDK 1.8版本WIN 10 系统安装教程!!!!!!!

前言 IDEA的安装是Java课程的入门&#xff0c;对于刚入门的小白来说&#xff0c;安装jdk是必不可少的一个环节&#xff0c;当然&#xff0c;很多的老同学进入新公司装环境也是不可或缺的&#xff0c;本人也是经常会碰到类似的场景&#xff0c;为避免后续忘记&#xff0c;选择记…

SHA256算法学习

SHA-256&#xff08;Secure Hash Algorithm 256-bit&#xff09;是一种常用的哈希算法&#xff0c;是SHA-2家族中的一种。它可以将任意长度的数据转换为一个固定长度的256位&#xff08;32字节&#xff09;哈希值。 算法特点 固定长度输出&#xff1a;无论输入数据有多长&…