被面试官问到分布式ID,别再傻乎乎只会答雪花算法了...

news2024/11/26 8:47:49

文章目录

  • 1. 分布式ID
  • 2. 数据库主键自增
  • 3. 数据库号段模式
  • 4. Redis自增
  • 5. UUID
  • 6. Snowflake (雪花算法)
  • 7. Leaf (美团分布式ID生成系统)
    • 7.1 Leaf-segment 号段方案
      • 7.1.2 双buffer优化
    • 7.2 Leaf-snowflake方案
    • 7.3 Leaf-snowflake Demo

1. 分布式ID

在分布式系统中,通常都需要对大量数据和消息进行唯一标识,这个表示通常被称为分布式ID。

分布式ID是用于识别不同实体或数据对象的,这就要求分布式ID必须具有全局唯一性,不能出现重复的ID

并且,由于在复杂的分布式系统下,分布式ID使用的场景很多,这就要求分布式ID的生成速度应该足够快,并且对本地资源消耗小。除此之外,生成分布式ID必须是高可用的,因为分布式ID关联着众多系统,必须要求分布式ID的生成的服务可用性无限趋向于100%

在业务方面,ID通常有以下两个需求,但是这两个需求是互斥的:

  1. 单调递增:在某些业务场景,ID必须是单调递增的,也就是上一个ID要比下一个ID要小。例如MySQL事务的版本号。
  2. 无规则:上述的这种ID生成策略,如果遇到恶意的人就能从ID号得到信息。比如存储图片进MySQL的时候,图片的重名是ID自增的,那么别人就可以恶意猜测到下一张图片的URL是啥;再比如,我们所熟悉的订单号,如果订单号是递增的,那么别人就可以知道一天的单量。

现如今,分布式ID的生成方案有很多

  1. 数据库主键自增
  2. Redis自增
  3. UUID
  4. Snowflake (雪花算法)
  5. Leaf (美团分布式ID生成系统)
  6. uid-generator (百度分布式ID生成系统)
  7. Tinyid (滴滴分布式ID生成系统)

本文重点介绍前五种,后两种以后有机会再做介绍~


2. 数据库主键自增

在MySQL数据库,我们可以通过创建一个具有主键ID自增字段的表。

每次向数据库中插入一条数据,那么数据库插入的记录的ID就会自增ID。

接着,将这个操作抽象成一个服务,那么这就是一个简单的分布式ID生成服务了。

这样的方式实现的分布式ID系统,显而易见的简单,优点也是简单方便。

但是,这样的实现方式会存在以下缺点

  1. 并发性能并不好,受限于MySQL的性能
  2. 当数据量上来的时候,需要进行分库分表,操作起来复杂

img


3. 数据库号段模式

在前面这种通过数据库自增的方式生成ID,每次都需要访问一次数据库,很容易受到数据库上限导致并发低。

因此可以改造为批量获取然后存进内存里,需要用到的时候直接在内存拿即可。

这就是数据库的号段模式,每次请求分配一个号段,号段模式相比主键自增,性能有一定提升。


4. Redis自增

Redis可以通过自增命令INCR将某一个Key进行自增。

并且这一过程是线程安全的,利用这些特性,我们就可以实现一个分布式ID生成系统,也可以在分布式系统中使用。

这样的分布式ID生成系统,可用性依赖于Redis

虽然Redis有AOF与RDB持久化,但是依然会存在数据丢失问题。一旦数据发送丢失,那么就可以出现ID重复问题,但是系统出现异常。

img


5. UUID

UUID是通用唯一标识符(Universally Unique Identifier)的缩写。它是由数字和字母组成的32位字符串,用于在计算机系统中唯一地标识实体或资源。UUID的生成算法能够保证在正常情况下几乎不会生成重复的标识符。

UUID中包含了网卡MAC地址、时间戳、名字空间(Namespace)、随机或伪随机数、时序等元素,其就是利用这些元素生成UUID的。

UUID的优点在于UUID的生成性能非常高,因为UUID是本地生成的,没有任何网络消耗。

不过,UUID也存在以下缺点:

  1. 不易存储:UUID包含了32个16进制的数字,形成8-4-4-4-12的36个字符,生成的ID太长在很多场景不适用
  2. 信息不安全:UUID看似无规则,实际其是基于MAC地址生成的,因此有可能泄漏MAC地址,MAC地址是可以被用到定位位置的
  3. 不适用于MySQL主键:在MySQL官方文档中提到,主键的长度应该越短越好

img


6. Snowflake (雪花算法)

Snowflake,也称雪花算法,是一种用于生成分布式系统中唯一ID的算法。它是Twitter公司开发的,目的是为了解决分布式系统中高并发场景下生成ID的问题

Snowflake算法生成的ID是一个64位整数,其中包含41位的时间戳、10位的机器ID和12位的序列号。

image-20231012223959527

41-bit的时间可以表示(1L<<41)/(1000L360024*365)=69年的时间,10-bit机器可以分别表示1024台机器。如果我们对IDC划分有需求,还可以将10-bit分5-bit给IDC,分5-bit给工作机器。这样就可以表示32个IDC,每个IDC下可以有32台机器,可以根据自身需求定义。12个自增序列号可以表示2^12个ID,理论上snowflake方案的QPS约为409.6w/s,这种分配方式可以保证在任何一个IDC的任何一台机器在任意毫秒内生成的ID都是不同的。

由于前41为是时间戳,也就是毫秒数,因此雪花算法生成的ID是趋势自增的

雪花算法不依赖与第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的

另外,可以根据业务特性分配bit位,非常灵活

当然,雪花算法也存在缺点,那就是雪花算法强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。

这是一个雪花算法生成的工具类,是基于hutool工具类生成的

@Component
public class SnowFlake{

    private Snowflake snowflake;

    @PostConstruct
    public void init() {
        // 0 ~ 31 位,可以采用配置的方式使用
        long workerId;
        try {
            workerId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr());
        } catch (Exception e) {
            workerId = NetUtil.getLocalhostStr().hashCode();
        }

        workerId = workerId >> 16 & 31;

        long dataCenterId = 1L;
        snowflake = IdUtil.createSnowflake(workerId, dataCenterId);
    }

    public synchronized long nextId() {
        return snowflake.nextId();
    }

}

7. Leaf (美团分布式ID生成系统)

img

Leaf 是美团开源的一个分布式 ID 解决方案。提供了号段模式 和 Snowflake这两种模式来生成分布式 ID。

Leaf 具有高可靠、低延迟、全局唯一的特点。


7.1 Leaf-segment 号段方案

Leaf-segment 号段方案是基于数据库自增方案做的改进。

在数据库自增方案中,每次获取新的ID都需要请求一次数据库,会造成数据库压力很大。

而在Leaf-segment 号段方案中,改为利用proxy server批量获取,每次获取一个segment(step决定大小)号段的值。用完之后再去数据库获取新的号段,可以大大的减轻数据库的压力。

在使用 Leaf-segment 号段方案需要建立DB表

CREATE DATABASE leaf
CREATE TABLE `leaf_alloc` (
  `biz_tag` varchar(128)  NOT NULL DEFAULT '',
  `max_id` bigint(20) NOT NULL DEFAULT '1',
  `step` int(11) NOT NULL,
  `description` varchar(256)  DEFAULT NULL,
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`biz_tag`)
) ENGINE=InnoDB;

insert into leaf_alloc(biz_tag, max_id, step, description) values('leaf-segment-test', 1, 2000, 'Test leaf Segment Mode Get Id')

biz_tag是用于区分业务的

max_id表示该biz_tag目前所被分配的ID号段的最大值

step表示每次分配的号段长度

原来获取ID每次都需要写数据库,现在只需要把step设置得足够大,比如1000。那么只有当1000个号被消耗完了之后才会去重新读写一次数据库。读写数据库的频率从1减小到了1/step

image-20231012230626604

test_tag在第一台Leaf机器上是11000的号段,当这个号段用完时,会去加载另一个长度为`step`=1000的号段,假设另外两台号段都没有更新,这个时候第一台机器新加载的号段就应该是30014000。同时数据库对应的biz_tag这条数据的max_id会从3000被更新成4000,更新号段的SQL语句如下:

Begin
UPDATE table SET max_id=max_id+step WHERE biz_tag=xxx
SELECT tag, max_id, step FROM table WHERE biz_tag=xxx
Commit

并配置leaf.jdbc.url, leaf.jdbc.username, leaf.jdbc.password

如果不想使用该模式配置leaf.segment.enable=false即可。

image-20231012230417166

Leaf-segment 号段方案有以下优缺点

优点:

  • Leaf服务可以很方便的线性扩展,性能完全能够支撑大多数业务场景。
  • ID号码是趋势递增的8byte的64位数字,满足上述数据库存储的主键要求
  • 容灾性高:Leaf服务内部有号段缓存,即使DB宕机,短时间内Leaf仍能正常对外提供服务
  • 可以自定义max_id的大小,非常方便业务从原有的ID方式上迁移过来。

缺点:

  • ID号码不够随机,能够泄露发号数量的信息,不太安全
  • TP999数据波动大,当号段使用完之后还是会hang在更新数据库的I/O上,tg999数据会出现偶尔的尖刺。
  • DB宕机会造成整个系统不可用。

7.1.2 双buffer优化

针对第二个缺点TP999数据波动大,当号段使用完之后还是会hang在更新数据库的I/O上,tg999数据会出现偶尔的尖刺。

这其中的耗时主要体现在Leaf取号时机是在号段消耗完进行的,也就意味着号段临界点的ID下发时间取决于下一次从DB取回号段的时间,也就是这段时间内会导致线程阻塞,倘若请求DB的网络和性能不稳定,会导致整体响应时间变慢

优化思路也很简单,就是希望DB取号段的过程做到无阻塞,也就是在号段消费到某个程度的时候就异步将下一个号段加载进内存中,而不是等待号段消耗完成才去更新号段。

什么是TP999?

TP90就是满足百分之九十的网络请求所需要的最低耗时。

TP99就是满足百分之九十九的网络请求所需要的最低耗时。

同理TP999就是满足千分之九百九十九的网络请求所需要的最低耗时。

image-20231013201119082

美团Leaf对此的优化是采用双Buffer的方式,Leaf服务内部有两个号段缓存区segment。当前号段已下发10%时,如果下一个号段未更新,则另启一个更新线程去更新下一个号段。当前号段全部下发完后,如果下个号段准备好了则切换到下个号段为当前segment接着下发,循环往复。

  1. 每个biz-tag都有消费速度监控,通常推荐segment长度设置为服务高峰期发号QPS的600倍(10分钟),这样即使DB宕机,Leaf仍能持续发号10-20分钟不受影响。
  2. 每次请求来临时都会判断下个号段的状态,从而更新此号段,所以偶尔的网络抖动不会影响下个号段的更新。

7.2 Leaf-snowflake方案

Leaf-segment 号段方案生成的ID是递增的,并不适用与订单场景。

于是Leaf还提供了Leaf-snowflake方案。

Leaf-snowflake方案沿用了Snowflake 的设计思想,也就1+41+10+12的方式装订ID。

image-20231013201625617

对于workerID的分配,当服务集群数量较小的情况下,完全可以手动配置。Leaf服务规模较大,动手配置成本太高。所以使用Zookeeper持久顺序节点的特性自动对snowflake节点配置wokerIDLeaf-snowflake是按照下面几个步骤启动的:

  1. 启动Leaf-snowflake服务,连接Zookeeper,在leaf_forever父节点下检查自己是否已经注册过(是否有该顺序子节点)。
  2. 如果有注册过直接取回自己的workerID(zk顺序节点生成的int类型ID号),启动服务。
  3. 如果没有注册过,就在该父节点下面创建一个持久顺序节点,创建成功后取回顺序号当做自己的workerID号,启动服务。

image-20231013201734419

该方式除了每次会去ZK拿数据以外,也会在本机文件系统上缓存一个workerID文件。这样的好处在于ZK出现问题的时候,能确保服务能够正常启动,这样使得Leaf-snowflake弱依赖于第三方组件。

那么Leaf-snowflake是如何解决时钟问题呢?

snowflake算法当时钟回拨的时候,有可能出现重复ID的情况,在Leaf-snowflake是这也解决时钟问题的。

  1. 服务会先检查自己是否写过ZooKeeper leaf_forever节点
  2. 如果写过,则用自身系统时间与leaf_forever/${self}节点记录时间做比较,如果小于则认为机器时间发生回拨,服务启动失败并报警
  3. 如果没写过,证明是新服务节点,直接创建持久节点leaf_forever/${self}并写入自身系统时间,接下来综合对比其余Leaf节点的系统时间来判断自身系统时间是否准确,具体做法是取leaf_temporary下的所有临时节点(所有运行中的Leaf-snowflake节点)的服务IP:Port,然后通过RPC请求得到所有节点的系统时间,计算sum(time)/nodeSize。
  4. abs( 系统时间-sum(time)/nodeSize ) < 阈值,认为当前系统时间准确,正常启动服务,同时写临时节点leaf_temporary/${self} 维持租约。
  5. 否则认为本机系统时间发生大步长偏移,启动失败并报警。
  6. 每隔一段时间(3s)上报自身系统时间写入leaf_forever/${self}。

由于强依赖时钟,对时间的要求比较敏感,在机器工作时NTP同步也会造成秒级别的回退,建议可以直接关闭NTP同步。要么在时钟回拨的时候直接不提供服务直接返回ERROR_CODE,等时钟追上即可。或者做一层重试,然后上报报警系统,更或者是发现有时钟回拨之后自动摘除本身节点并报警,如下:

 //发生了回拨,此刻时间小于上次发号时间
 if (timestamp < lastTimestamp) {
  			  
            long offset = lastTimestamp - timestamp;
            if (offset <= 5) {
                try {
                	//时间偏差大小小于5ms,则等待两倍时间
                    wait(offset << 1);//wait
                    timestamp = timeGen();
                    if (timestamp < lastTimestamp) {
                       //还是小于,抛异常并上报
                        throwClockBackwardsEx(timestamp);
                      }    
                } catch (InterruptedException e) {  
                   throw  e;
                }
            } else {
                //throw
                throwClockBackwardsEx(timestamp);
            }
        }
 //分配ID       

7.3 Leaf-snowflake Demo

想要使用Leaf-snowflake方案,首先需要上GitHub将项目clone下来

美团Leaf:下载,

然后安装并启动Zookeeper,这里可以参考这个博客:Zookeeper 安装(Windows)_zookeeper windows安装_coder i++的博客-CSDN博客

image-20231013205035860

完成后,配置leaf.properties,因为这里使用的snowflake 方案,所以leaf.segment.enable设置为falseleaf.snowflake.enable设置为true,并配置Zookeeper的地址和端口

leaf.name=com.sankuai.leaf.opensource.test
leaf.segment.enable=false
#leaf.jdbc.url=
#leaf.jdbc.username=
#leaf.jdbc.password=

leaf.snowflake.enable=true
leaf.snowflake.zk.address=127.0.0.1
leaf.snowflake.port=2181

启动leaf-server服务,默认端口为8080。但是由于这里我冲突了,所以我将端口设置为8081。

image-20231013205752630

浏览器访问(http://127.0.0.1:8081/api/snowflake/get/leaf-test),就会得到一个ID

image-20231013205913861

对应的调用就是LeafControllergetSnowflakeId方法,这里需要传入一个Key,这个Key可以是用到这个分布式ID生成的服务表示,这样可以做到不同模块之间的分布式ID隔离。

image-20231013210006358

Leaf的简单使用就到这里了,在项目中可以将该项目单独为一个服务,使用OpenFeign或是Dubbo等RPC框架远程调用获取接口。


参考:

  1. Leaf——美团点评分布式ID生成系统 - 美团技术团队 (meituan.com)
  2. 面试总被问分布式ID? 美团(Leaf)了解一下 - 掘金 (juejin.cn)
  3. 不能错过的分布式ID生成器(Leaf ),好用的一批! - 掘金 (juejin.cn)
  4. 分布式ID生成服务的技术原理和项目实战 - 掘金 (juejin.cn)
  5. 【分布式系统】10种分布式唯一ID生成方案总结 - 掘金 (juejin.cn)
  6. 常见分布式ID解决方案总结:数据库、算法、开源组件 - 掘金 (juejin.cn)

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

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

相关文章

GEE:使用中文做变量和函数名写GEE代码

作者&#xff1a;CSDN _养乐多_ 啊&#xff1f;最近在编写GEE代码的时候&#xff0c;无意中发现 JavaScript 已经能够支持中文字符作为变量名和函数名&#xff0c;这个发现让我感到非常兴奋。这意味着以后在编程过程中&#xff0c;我可以更自由地融入中文元素&#xff0c;不再…

ubuntu20.04 vins-fusion 运行记录

过程记录 环境&#xff1a; ubuntu20.04 opencv4.2.0(此次使用) 3.3.1(其他程序在使用) vins-fusion vision_opencv 1.下载VINS-Fusion和cv_bridge&#xff0c;并进行修改&#xff0c;方便使用opencv4.2.0和对应的cv_bridge。 mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src…

jetty嵌入式开发及spring整合quartz(内存模式)

1 依赖jar包 2 jetty嵌入式开发 2.1 jetty服务启动类 package com.primal.server;import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.AbstractHandler;…

[资源推荐] 复旦大学张奇老师科研分享

刷B站的时候首页给我推了这个&#xff1a;【直播回放】复旦大学张奇教授亲授&#xff1a;人工智能领域顶会论文的发表指南先前也散漫地读了些许论文&#xff0c;但没有在一些宏观的方法论下去训练&#xff0c;读的时候能感觉出一些科研的套路&#xff0c;论文写作的套路&#x…

leetCode 583.两个字符串的删除操作 动态规划 + 优化空间复杂度(二维dp、一维dp)

583. 两个字符串的删除操作 - 力扣&#xff08;LeetCode&#xff09; 给定两个单词 word1 和 word2 &#xff0c;返回使得 word1 和 word2 相同所需的最小步数。 每步 可以删除任意一个字符串中的一个字符。 示例 1&#xff1a; 输入: word1 "sea", word2 &qu…

3.2.5:VBA对单元格操作的引申

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的劳动效率&#xff0c;而且可以提高数据处理的准确度。我推出的VBA系列教程共九套和一部VBA汉英手册&#xff0c;现在已经全部完成&#xff0c;希望大家利用、学习。 如果…

【Leetcode刷题(数据结构)】:三路划分与三数随机取中的思想实现快速排序的再优化

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法&#xff0c;其基本思想为&#xff1a;任取待排序元素序列中 的某元素作为基准值&#xff0c;按照该排序码将待排序集合分割成两子序列&#xff0c;左子序列中所有元素均小于基准值&#xff0c;右 子序列中所有元素均…

JWT前后端分离在项目中的应用

14天阅读挑战赛当你累了&#xff0c;要学会休息&#xff0c;而不是放弃&#xff01; 目录 一、JWT简介 1.1 什么是JWT 1.2 为什么要使用JWT&#xff0c;与session的区别 1.3 JWT组成及工作原理和流程 二、JWT工具类解析 2.1 生成JWT 2.2 解析oldJwt 2.3 复制JWT并延时…

4.04 用户中心-我的订单评价

内容实现效果&#xff1a; 代码实现内容查看&#xff1a; http://www.gxcode.top/code

Unity中Shader光照模型Blinn-Phong原理及实现

文章目录 前言一、Blinn-Phong原理二、Blinn-Phong实现最终代码 前言 Unity中Shader光照模型Blinn-Phong原理及实现&#xff0c;也是经验型光照模型。和Phong模型一样&#xff0c;都是用于实现高光效果 一、Blinn-Phong原理 可以看出&#xff1a;Blinn-Phong模型和Phong模型不…

排序:如何用快排思想在O(n)内查找第K大元素?

文章来源于极客时间前google工程师−王争专栏。 冒泡排序、插入排序、选择排序三种排序算法&#xff0c;时间复杂度都是O(n^2)&#xff0c;比较高&#xff0c;适合小规模数据的排序。 归并排序和快速排序两种时间复杂度O(nlogn)的排序算法&#xff0c;适合大规模的数据排序&am…

未授权和代码执行漏洞特征和检测方法

文章目录 一、Redis未授权访问二、MongoDB未授权访问三、Elasticsearch未授权访问四、Rsync未授权访问五、Windows RDP远程代码执行漏洞&#xff08;CVE-2019-0708&#xff09;六、Tomcat Web控制台弱口令七、WebLogic控制台弱口令&反序列化系列漏洞八、WebLogic SSRF(无检…

微服务设计原则:构建弹性和可维护的应用

文章目录 1. 单一职责原则2. 独立性和自治性3. 弹性和容错性4. API 网关5. 日志和监控6. 版本管理7. 自动化部署和持续集成8. 安全性9. 数据一致性10. 文档和通信拓展思考结论 &#x1f389;欢迎来到架构设计专栏~微服务设计原则&#xff1a;构建弹性和可维护的应用 ☆* o(≧▽…

Linux环境下Qt应用程序安装器(installer)制作

本文介绍Linux环境下Qt应用程序安装器(installer)的制作。 安装器(installer)是将应用程序安装到操作系统平台的可执行文件&#xff0c;它采用向导式对话框指导用户安装应用程序&#xff0c;如我们在Windows操作系统安装Office软件时&#xff0c;有1个向导让你选择安装哪些组件…

力扣:611. 有效三角形的个数

今日为大家分享一道力扣611有效三角形的个数&#xff01;本文将会为大家为大家讲解题目&#xff0c;然后算法思路&#xff0c;最后再进行代码的实现&#xff01;希望看完本文能对读者有一定的收获&#xff01; 一、题目描述 通过题目的描述可以看出&#xff0c;意思是给定一个…

[产品体验] GPT4识图功能

[产品体验] GPT4识图功能 图片配文字超强的OCR能力知识问答多图解释 打开chatgpt的时候突然发现能用识图了&#xff0c;赶紧去体验一下&#xff0c;大大的震撼… 图片配文字 超强的OCR能力 我传上去的图片并不清晰… 还能准确识别&#xff0c;orz &#xff01; 知识问答 多…

代码随想录Day18 LeetCode235 二叉搜索树的公共祖先 T701二叉搜索树中的插入操作 T140 删除二叉搜索树中的公共节点

LeetCode T235 二叉搜索树的公共祖先 题目链接235. 二叉搜索树的最近公共祖先 - 力扣&#xff08;LeetCode&#xff09; 题目思路 此题不涉及遍历顺序. 关于二叉搜索树的定义,这里我就不过多赘述了,前面几篇都说清楚了,根节点比左子树元素都大,比右子树元素都小,这道题我们就可…

计算机体系结构和操作系统

这篇文章的主要内容是冯诺依曼计算机体系结构和操作系统的理解。 目录 一.冯诺依曼计算机体系结构 二.操作系统的理解 一.冯诺依曼计算机体系结构 如图是冯诺依曼计算机体系结构&#xff0c;计算机本质就是对数据进行处理的机器&#xff0c;图中&#xff0c;数据从输入设备交给…

VMWare配置桥接

一、设置网络模式 二、编辑网卡配置 ip配置的子网掩码和默认网关保持和宿主机一致&#xff0c;ip局域网内不冲突。 # cd /etc/sysconfig/network-scriptslsvim ifcfg-ens160 TYPEEthernet PROXY_METHODnone BROWSER_ONLYno BOOTPROTOnone DEFROUTEyes IPV4_FAILURE_FATALno IP…