第4章唯一ID生成器——4.5 美团点评开源方案Leaf

Leaf是美团点评公司基础研发平台推出的一个唯一ID生成器服务,其具备高可靠性、低延迟、全局唯一等特点,目前已经被广泛应用于美团金融、美团外卖、美团酒旅等多个部门。Leaf根据不同业务的需求分别实现了Leaf-segment和Leaf-snowflake两种方案,前者基于数据库的自增主键,后者基于Snowflake算法。接下来介绍这两种方案的技术原理。 需要注意的是,Leaf和前几节介绍的几种技术方案非常相似,只是多了一些思考和优化,这也是我们在本节中重点着墨的部分。

4.5.1 Leaf-segment 方案

Leaf-segment方案与4.4.2节介绍的批量缓存架构方案类似,只不过它没有依赖数据库的自增主键,而是在数据库中为每个业务场景都记录目前可用的唯一ID号段。具体的数据表设计如表4-1所示。

image-20250321221405708

不同业务方的唯一ID需求用biz_tag字段区分,每个biz_tag的ID相互隔离。当某业务请求携带biz_tag访问Leaf服务时,数据库会通过执行如下语句生成唯一ID:

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

比如在数据表中外卖业务方的biz tag为waimai_ordertag,此时max_id为10000, step 为2000,那么外卖业务方下次得到的唯一ID号段是10001-12000, max_id的值被更新为12000。

通过修改step字段值,可以方便地控制一个业务访问数据库的频率:

  • 如果step为1,则说明每次生成唯一ID时业务方都要访问数据库;

  • 如果step为1000,则说明每用 完1000个唯一ID时,业务方才再次访问数据库。

美团技术团队官网给出了Leaf-segment方案的大致架构图,如图4-14所示。

image-20250321221612937

从架构图中可以看到,Leaf-segment方案与4.4.2节介绍的批量缓存架构方案确实大同小异,服务实例在本地缓存一批可用的唯一ID号段供业务请求使用,当某业务请求发现唯一ID号段用完时,再从数据库中批量获取新的唯一ID号段。如果此时数据库发生网络抖动或慢查询,则会导致访问数据库的业务请求被阻塞,整个服务的响应变慢。

Leaf-segment方案针对这个问题做了优化:当使用可用的唯一ID号段到达某个检查点时,Leaf服务实例就异步地从数据库中获取下一个可用的唯一ID号段,而不需要等到唯一ID号段用完才访问数据库,这样可以防止唯一ID号段用完时阻塞业务请求。

具体来说,Leaf服务实例内部有两个唯一ID号段缓存区:

  • 第一个缓存区用于对外提供服务,业务请求从这里获取唯一ID;

  • 第二个缓存区用于提前向数据库加载下一个 可用的唯一 id号段。

当第一个缓存区已经下发10%可用的唯一ID时,Leaf服务实例将启动一个线程异步访问数据库,并将获取到的下一个可用的唯一ID号段保存到第二个缓存区。这样一来,当某业务请求发现第一个缓存区中已无可用的唯一ID时,Leaf服务实例就直接切换到第二个缓存区继续下发可用的唯一ID,如此循环往复,业务请求不会被阻塞在访问数据库的过程中。

这个技术优化的示意图如图4-15所示(参考自美团技术团队官网)。

image-20250321221806706

4.5.2 Leaf-snowflake方案

使用Leaf-segment方案可以生成趋势递增的唯一ID,但是ID值会反映实际的数据量,并不适用于订单ID生成的场景。如果将此方案应用在订单ID生成的场景中,则很容易被竞品公司计算出订单的总量,这等于把业务的数据表现直接实时暴露给其他公司。为了解决这个问题,美团点评公司提供了Leaf-snowflake方案,这个方案和4.3节介绍的基于时间戳的方案类似。

Leaf-snowflake方案在唯一ID的设计上完全沿用Snowflake算法,即使用1+41+10+12的方式组装ID;至于worker ID的分配问题,Leaf snowflake方案借助了ZooKeeper持久顺序节点的特性,每个Leaf服务实例都会在ZooKeeper的leaf_forever节点下注册一个持久顺序节点,将对应的顺序数字作为worker ID。假设现在有4个服务实例注册了持久顺序节点,leaf_forever节点的结构可能如图4-16所示。

image-20250321221938261

每个服务实例都携带IP地址和端口号在leaf_forever节点下注册持久顺序节点(格式为IP:port),然后ZooKeeper会自动生成一个自增序号作为每个顺序节点的后缀,这个序号就可被分配作为实例的worker ID。Leaf-snowflake方案分配worker ID的流程如下。

  1. Leaf服务实例启动时,连接ZooKeeper。
  2. 服务实例查询leaf_forever节点是否存在。如果不存在,则跳至第4步,否则继续。
  3. 服务实例读取leaf_forever节点下的子节点列表,然后根据自身的IP地址和端口号遍历子节点列表,查询自己是否注册过子节点。
  4. 如果未找到子节点,则实例在leaf_forever节点下创建子节点,将所得到的节点后缀序号作为worker ID。
  5. 如果找到子节点,则将此子节点的后缀序号取出作为worker ID。
  6. 获取到worker ID后,Leaf服务实例就启动成功了;否则,启动失败。

Leaf服务实例在获取到worker ID后会将其保存到本地文件中,这样可以做到对ZooKeeper的弱依赖。将来,如果ZooKeeper出现故障,而此时Leaf服务实例恰好重启,那么就可以从本地文件中得到worker ID,避免了无法正常启动的问题。

每个Leaf服务实例都会每隔3s将自身的系统时间上报到其在leaf_forever节点下注册的子节点,并且还会在另一个ZooKeeper节点leaf_temporary下创建一个临时节点,leaf_temporary下的临时节点列表代表了此时正在运行的Leaf服务实例集合。也就是说, Leaf服务实际上与两个ZooKeeper父节点交互:

  • leaf_forever节点
  • leaf_temporary节点

如图4-17所示:

image-20250321222241067

Leaf-snowflake方案使用这两个节点来解决时钟回拨问题,具体的工作流程如下。

  1. 如果Leaf服务实例在leaf_forever节点下未注册持久顺序节点,那么在注册节点 时将顺便写入自身的系统时间。
  2. 如果Leaf服务实例已在leaf_forever节点下注册持久顺序节点,则对比持久顺序节点记录的时间与自身的系统时间。如果自身的系统时间更小,则认为发生了时钟回拨,服务实例启动失败。
  3. 否则,获取leaf_temporary节点下的所有临时节点信息,然后向这些临时节点代表的Leaf服务实例发送RPC请求查询它们的系统时间,并计算出平均时间,用于表示Leaf服务集群的系统时间。
  4. 如果平均时间与Leaf服务实例自身的系统时间的差值小于某个阈值,则认为本服务实例的系统时间是准确的,服务实例可以正常启动。
  5. 否则,说明本服务实例的系统时间相较于Leaf集群中的其他服务实例发生了大幅度的时钟漂移,服务实例启动失败。
  6. 启动成功的Leaf服务实例每隔3s将自身的系统时间上报到在leaf_forever节点下注册的持久顺序节点。

Leaf-snowflake方案通过检查服务实例上报的自身系统时间和其他Leaf服务实例的平均时间来解决时钟回拨问题,按照美团点评公司技术博客中的说法,这个策略有效地避免了时钟回拨对业务造成的影响。另外,此方案也建议关闭NTP时钟同步功能。

本章小结

分布式唯一ID应该具备占用空间小、可用作数据库主键的能力,所以一般用递增的long类型整数来表示。

递增可以分为单调递增和趋势递增。

单调递增的唯一ID生成器可以基于Redis INCRBY命令实现,或者基于数据库的自增主键实现。采用批量生成ID的方式可以提高唯一ID生成器的性能,ID生成器服务实例将一批唯一ID缓存到本地对外提供服务,当可用的唯一ID消耗完时再生成下一批唯一ID。不过,为了保证唯一ID单调递增,此时只能有一个服务实例对外工作。由于单调递增的唯一ID生成器服务无法兼顾高可用性和高性能,所以应用相对具有局限性。

如果把单调递增改为趋势递增,那么唯一ID生成器服务将打破局限性。一种方案是使用数据库分库分表架构生成自增主键,同时利用数据库自带的自增主键调整自增步长和设置初始值来防止各分表生成的自增主键冲突。这种方案可以提高数据库的高可用性与性能,但是可扩展性较差。另一种方案是使用批量缓存架构,即在批量获取单调递增的唯一ID的基础上采用多服务实例生成趋势递增的唯一ID。这两种方案都是基于数据库的自增主键生成唯一ID的,数值的可读性过强,在某些场景中有泄露业务数据的风险。基于时间戳生成唯一ID可以解决这个问题。

如何基于时间戳设计唯一ID生成器呢? Snowflake算法为我们提供了很好的思路:将分布式环境下的各变量体现到唯一ID的二进制位上,比如不同的机房、不同的服务实例、不同的时间、相同时间不同的请求。每个ID生成器服务实例都需要有唯一表示自己的worker ID,可以使用数据库的自增主键、分布式协调服务ZooKeeper或etcd来实现;同时,服务实例维护从系统上线时间开始经过的总毫秒数、当前毫秒内已生成的ID数量,以便区分时间和并发请求。最后,一定要防止时钟漂移问题影响ID的唯一性。

美团点评公司的唯一ID生成器服务Leaf实现了两种生成唯一ID的方案:Leaf-segment和Leaf-snowflake。前者采用了批量缓存ID的思想,后者是对Snowflake算法的应用。

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

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

相关文章

分布式搜索和分析引擎Elasticsearch实战指南

ES 介绍与安装 Elasticsearch, 简称 ES,它是个开源分布式搜索引擎,它的特点有:分布式,零配置,自动发现,索引自动分片,索引副本机制,restful 风格接口,多数据源…

【13】C# 窗体应用WinForm——.NET Framework、WinForm、工程创建、工具箱简介、窗体属性及创建

文章目录1. WinForm工程创建 及 界面介绍1.1 WinForm工程创建1.2 窗体 Form1.cs “查看代码”1.3 打开窗体设计器2. 工具箱3. 窗体属性及创建3.1 窗体属性3.2 实例:创建一个新窗体3.2.1 添加新Windows窗体3.2.2 窗体属性配置3.2.3 设置该窗体为启动窗体WinForm 是 W…

论文阅读-IGEV

文章目录1 概述2 模块2.1 总体说明2.2 特征抽取器2.3 CGEV2.4 基于Conv-GRU的更新算子2.5 空间上采样2.6 损失函数3 效果参考文献1 概述 在双目深度估计中,有一类是基于3D卷积的方法,代表就是PSMNet,它应用 3D 卷积编码器-解码器来聚合和正则…

[2025CVPR-图象分类方向]SPARC:用于视觉语言模型中零样本多标签识别的分数提示和自适应融合

1. ​背景与问题定义​ 视觉语言模型(如CLIP)在单标签识别中表现出色,但在零样本多标签识别(MLR)任务中表现不佳。MLR要求模型识别图像中多个对象(例如,图像包含“猫”和“沙发”)&…

2025创始人IP如何破局?

内容持续更新却无人点赞,课程精心打磨却无人报名,直播卖力讲解却无人停留 —— 明明有内容、有经验、有成果,却始终难以打动用户。问题的核心,或许在于你尚未打造出真正的 “创始人IP”。‌一、创始人IP:不止标签&…

告别配置混乱!Spring Boot 中 Properties 与 YAML 的深度解析与最佳实践

一、Spring配置文件 1.1、什么是Spring配置 Spring配置指的是在Spring框架中定义和管理应用程序组件(如Bean)及其依赖关系的过程 作用: 配置文件主要用于解决硬编码问题,它将可能变更的信息集中存放。程序启动时,会从…

无人机喷洒系统技术要点与难点解析

一、 模块运行方式1. 任务规划与加载模块:输入:农田边界、障碍物信息、作物类型、病虫害信息、所需喷洒量、天气条件。运行:利用地面站软件或移动APP,规划最优飞行路径,设定飞行高度、速度、喷洒参数、作业区域。将规…

mongodb源代码分析createCollection命令创建Collection流程分析

MongoDB 提供两种方式创建集合:隐式创建 和 显式创建。方式 1:隐式创建(推荐)当你向不存在的集合中插入文档时,MongoDB 会自动创建该集合。示例在 db中隐式创建 users 集合:javascriptdb.users.insertOne({…

c++注意点(13)----设计模式(抽象工厂)

创建型模式抽象工厂模式(Abstract Factory Pattern)是一种创建型设计模式,它提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们具体的类。简单说,它就像一个 "超级工厂"&#xff…

【大语言模型入门】—— Transformer 如何工作:Transformer 架构的详细探索

Transformer 如何工作:Transformer 架构的详细探索Transformer 如何工作:Transformer 架构的详细探索什么是 Transformer?什么是 Transformer 模型?历史背景从 RNN 模型(如 LSTM)到 Transformer 模型在 NLP…

iOS安全和逆向系列教程 第20篇:Objective-C运行时机制深度解析与Hook技术

iOS安全和逆向系列教程 第20篇:Objective-C运行时机制深度解析与Hook技术 引言 在上一篇文章中,我们深入学习了ARM64汇编语言的基础知识,掌握了从寄存器操作到指令分析的完整技能体系。现在,我们将把这些底层知识与iOS应用的高层逻辑联系起来,深入探讨Objective-C运行时…

IDEA中全局搜索快捷键Ctrl+Shift+F为何失灵?探寻原因与修复指南

在软件开发中,高效地查找和管理代码是提升生产力的关键。IntelliJ IDEA,作为一款功能强大的集成开发环境(IDE),提供了丰富的搜索功能,帮助开发者迅速定位代码、资源、甚至是IDE功能本身。 在 IntelliJ IDE…

【学习笔记】Lean4 定理证明 ing

文章目录概述Lean4 定理证明初探示例:证明 1 1 2示例:证明 2 * (x y) 2 * x 2 * yLean4 定理证明基础命题与定理命题(Proposition)定理(Theorem)量词策略概述 Lean证明是指在Lean环境中,通…

墨者:SQL注入漏洞测试(HTTP头注入)

墨者学院:SQL注入漏洞测试(HTTP头注入)🚀 1. 什么是HTTP头注入?🔍 HTTP头注入是指攻击者通过篡改HTTP请求头部的字段(如User-Agent、Referer、Cookie、Host等),将恶意SQL代码插入到后端数据库查…

linux_前台,后台进程

*在用户访问端口时,操作系统会形成对应的session,在其的内部进一步形成bash等进程 *一个会话只有一个前台进程,可以有多个后台进程,前台与后台进程的区别在于谁拥有键盘的使用权*前台与后台进程都可以访问显示器但是后台无法访问标准输入获取…

spring data mongodb 入门使用手册

<!--pom.xml引入依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId></dependency>文档映射类Student.java import lombok.Data; import lombok.NoArgsCons…

Fastjson2常用操作大全:对象、字符串、集合、数组、Map与JSON互转实战

高性能&#xff1a; 核心解析器和生成器经过深度优化&#xff0c;性能远超许多同类库。 功能丰富&#xff1a; 支持标准JSON、JSONPath查询、泛型处理、日期格式化、自定义序列化/反序列化等。 易用性&#xff1a; API 设计简洁直观&#xff0c;JSON 工具类提供了最常用的 toJS…

大模型——字节Coze重磅开源!Dify何去何从

大模型——字节Coze重磅开源!Dify何去何从 想必很多人盼了很久,就在昨晚,字节Coze终于开源了!Coze Studio 是字节跳动新一代 AI Agent 开发平台扣子(Coze)的开源版本。 提供 AI Agent 开发所需的全部核心技术:Prompt、RAG、Plugin、Workflow,使得开发者可以聚焦创造 A…

NaVid——基于单目RGB捕获的视频让VLM规划「连续环境中VLN」的下一步:无需地图/里程计/深度信息(含MP3D/R2R/RxR,及VLN-CE的详解)

前言 因为我司「七月在线」准备于25年7月底复现下NaVILA&#xff0c;而在研究NaVILA的过程中&#xff0c;注意到了这个NaVid 虽然NaVid目前已经不是VLN sota了&#xff0c;但其首次展示了VLM在无需地图、里程计或深度输入的情况下&#xff0c;能够实现优秀的导航性能且对后来…

【Vue2】结合chrome与element-ui的网页端条码打印

所有文章都是免费查看的&#xff0c;如果有无法查看的情况&#xff0c;烦请联系我修改哈~ 序言 为什么要做这个呢&#xff1f;因为所需要的条码打印功能比较简单&#xff0c;符合需要即可&#xff0c;但是呢网上查看了发现并没有合适的开源项&#xff0c;其他成熟的软件收费又超…