分布式Id生成策略-美团Leaf

之前在做物流相关的项目时候,需要在分布式系统生成运单的id。

1.需求:

1.全局唯一性:不能出现重复的ID。(基本要求)

2.递增:大多数关系型数据库(如 MySQL)使用 B+ 树作为索引结构。如果 ID 是递增的,新数据总是追加到索引的末尾,这样索引的维护成本较低,因为数据库不需要频繁调整树的结构。相反,如果 ID 是随机的,数据插入时可能需要频繁调整索引结构,导致写入性能下降。

3.业务性:对具体的场景的ID要具备业务的特性,比如顺丰运单ID为类似SF1000000000760

4.精简性:某些场景下的ID不宜过长,所以对位数/长度有所限制。

在分布式系统中我们还应该考虑:生成方案能子啊多节点正常工作,能够有一定的解决故障问题,有高可用性,生成ID的速率要快,能够随业务扩展进行水平拓展,比如在分库分表后也能兼容原来的ID。

2.方案

有几个方案可以考虑:

本地的UUID生成:它的优点的生成速度很快,产生的值也几乎达到不重复的要求,但是产生的ID值比较长,可以达到36个字符,可读性还非常差,而且ID完全随机,没有任何的顺序。因此这个方案不考虑。

依靠数据库自增特性:为不同业务模块建立一张自增表维护递增序列。这种方法比较简单,靠数据库保证自增机制。缺点也很明显,当数据库异常整个系统不可用,而且ID生成的性能瓶颈也限制在单台MYSQL数据库。单台数据库性能问题可以同过部署多态机器,每个机器设置不同初始值,并且步长和机器数相同来优化。但是也带来很多问题,比如扩容很是麻烦。

Redis实现:通过给不同业务设计不同的key,通过INCR命令,对key自增,得到全局唯一并有序的ID。Redis每秒支持10W的读写,所以性能问题得到解决,但是Redis依靠内存,虽然有持久化机制,但是它持久化是先写内存再异步刷盘,遇到没来得及持久化就宕机也是会出大问题的。

雪花算法: 是 Twitter 于 2010 年开源的一种分布式唯一 ID 生成算法,它可以在分布式系统中生成高效、有序的唯一 ID。雪花算法生成的 ID 是一个 64 位的长整型long),在保证唯一性的同时,也确保了生成的 ID 按时间顺序递增。

在这里插入图片描述

  1. 全局唯一性:生成的 ID 保证全局唯一,雪花算法结合时间戳、机器 ID 和序列号确保在分布式系统中不会产生重复的 ID。

  2. 高效生成:雪花算法不依赖数据库,因此生成 ID 的过程非常高效,可以在本地的内存中生成,具有极高的性能。每台机器每秒钟可以生成上百万个 ID。

  3. 趋势递增:生成的 ID 是按时间顺序递增的,尤其是基于时间戳的部分,使得 ID 具有递增的特性。这对数据库插入数据时索引的维护非常友好(例如 B+ 树结构索引的维护成本较低)。

  4. 灵活可扩展:通过调整数据中心 ID 和机器 ID 的位数分配,可以根据业务的需要适当扩大集群规模或提升单机 ID 生成的并发能力。

    雪花算法依赖 机器码 来保证不同机器生成的 ID 唯一性。如果在分布式环境中多台机器未能准确区分它们的机器码,可能导致多个机器在同一时间生成相同的 ID,造成 ID 冲突。因此,在分布式系统中,每台服务器、虚拟机或容器必须手动指定一个唯一的机器标识符。**

雪花算法的不足

  • 依赖机器时间:由于 ID 的递增性依赖时间戳,一旦服务器的系统时钟发生回拨,可能会引发 ID 冲突或无法生成 ID 的问题。虽然有一些解决方案(如等待或借助其他算法生成 ID),但还是可能影响生成 ID 的稳定性。

3.美团Leaf

下面就将引入我选取的美团Leaf这个id生成策略。

其源码托管于GitHub:https://github.com/Meituan-Dianping/Leaf

这里有个美团的技术播客,专门介绍了Leaf:https://tech.meituan.com/2017/04/21/mt-leaf.html

目前Leaf覆盖了美团点评公司内部金融、餐饮、外卖、酒店旅游、猫眼电影等众多业务线。在4C8G VM基础上,通过公司RPC方式调用,QPS压测结果近5w/s,TP999 1ms。

Leaf 提供两种生成的ID的方式(segment模式和snowflake模式),我们采用segment模式(号段)来生成运单号。

号段模式

号段模式采用的是基于MySQL数据生成id的,它并不是基于MySQL表中的自增长实现的,因为基于MySQL的自增长方案对于数据库的依赖太大了,性能不好,Leaf的号段模式是基于一张表来实现,每次获取一个号段,生成id时从内存中自增长,当号段用完后再去更新数据库表,如下:

在这里插入图片描述

字段说明:

  • biz_tag:业务标签,用来区分业务
  • max_id:表示该biz_tag目前所被分配的ID号段的最大值
  • step:表示每次分配的号段长度。如果把step设置得足够大,比如1000,那么只有当1000个号被消耗完了之后才会去重新读写一次数据库。读写数据库的频率从1减小到了1/step
  • description:描述
  • update_time:更新时间

架构图如下:

在这里插入图片描述

图片来源:https://tech.meituan.com/2017/04/21/mt-leaf.html

说明: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 取号段的时机是在号段消耗完的时候进行的,也就意味着号段临界点的ID下发时间取决于下一次从DB取回号段的时间,并且在这期间进来的请求也会因为DB号段没有取回来,导致线程阻塞。如果请求DB的网络和DB的性能稳定,这种情况对系统的影响是不大的,但是假如取DB的时候网络发生抖动,或者DB发生慢查询就会导致整个系统的响应时间变慢。Leaf服务内部有号段缓存,即使DB宕机,短时间内Leaf仍能正常对外提供服务。

双buffer优化

Leaf为此做了优化,增加了双buffer优化。

当号段消费到某个点时就异步的把下一个号段加载到内存中。而不需要等到号段用尽的时候才去更新号段。这样做就可以很大程度上的降低系统的TP999指标。
在这里插入图片描述

双buffer原理,来自:https://tech.meituan.com/2017/04/21/mt-leaf.html

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

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

4.项目使用

我们只用到了号段的方式,并没有使用雪花方式,所以只需要创建数据库表即可

将其镜像运行:

docker run \
-d \
-v /hujx/meituan-leaf/leaf.properties:/app/conf/leaf.properties \
--name meituan-leaf \
-p 28838:8080 \
--restart=always \
registry.cn-hangzhou.aliyuncs.com/itheima/meituan-leaf:1.0.1

leaf.properties

leaf.name=leaf-server
leaf.segment.enable=true
leaf.jdbc.url=jdbc:mysql://192.168.150.101:3306/hjx_leaf?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
leaf.jdbc.username=root
leaf.jdbc.password=123leaf.snowflake.enable=false
#leaf.snowflake.zk.address=
#leaf.snowflake.port=

创建sl_leaf数据库脚本:

CREATE TABLE `leaf_alloc` (`biz_tag` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '',`max_id` bigint NOT NULL DEFAULT '1',`step` int NOT NULL,`description` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (`biz_tag`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;-- 插入运单号生成规划数据
INSERT INTO `leaf_alloc` (`biz_tag`, `max_id`, `step`, `description`, `update_time`) VALUES ('transport_order', 1000000000001, 100, 'Test leaf Segment Mode Get Id', '2023-07-07 11:32:16');

封装服务

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.sl.transport.common.enums.IdEnum;
import com.sl.transport.common.exception.SLException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;/*** id服务,用于生成自定义的id*/
@Service
public class IdService {@Value("${sl.id.leaf:}")private String leafUrl;/*** 生成自定义id** @param idEnum id配置* @return id值*/public String getId(IdEnum idEnum) {String idStr = this.doGet(idEnum);return idEnum.getPrefix() + idStr;}private String doGet(IdEnum idEnum) {if (StrUtil.isEmpty(this.leafUrl)) {throw new SLException("生成id,sl.id.leaf配置不能为空.");}//访问leaf服务获取idString url = StrUtil.format("{}/api/{}/get/{}", this.leafUrl, idEnum.getType(), idEnum.getBiz());//设置超时时间为10sHttpResponse httpResponse = HttpRequest.get(url).setReadTimeout(10000).execute();if (httpResponse.isOk()) {return httpResponse.body();}throw new SLException(StrUtil.format("访问leaf服务出错,leafUrl = {}, idEnum = {}", this.leafUrl, idEnum));}}
public enum IdEnum implements BaseEnum {TRANSPORT_ORDER(1, "运单号", "transport_order", "segment", "SL");private Integer code;private String value;private String biz; //业务名称private String type; //类型:自增长(segment),雪花id(snowflake)private String prefix;//id前缀IdEnum(Integer code, String value, String biz, String type, String prefix) {this.code = code;this.value = value;this.biz = biz;this.type = type;this.prefix = prefix;}@Overridepublic Integer getCode() {return this.code;}@Overridepublic String getValue() {return this.value;}public String getBiz() {return biz;}public String getType() {return type;}public String getPrefix() {return prefix;}@Overridepublic String toString() {final StringBuffer sb = new StringBuffer("IdEnum{");sb.append("code=").append(code);sb.append(", value='").append(value).append('\'');sb.append(", biz='").append(biz).append('\'');sb.append(", type='").append(type).append('\'');sb.append(", prefix='").append(prefix).append('\'');sb.append('}');return sb.toString();}
}

使用步骤:

  • 在配置文件中进行配置sl.id.leaf为: 地址:你的服务端口 如:http://192.168.150.101:28838
    pend(“, type='”).append(type).append(‘’‘);
    sb.append(", prefix=’").append(prefix).append(‘’‘);
    sb.append(’}');
    return sb.toString();
    }
    }

使用步骤:

  • 在配置文件中进行配置sl.id.leaf为: 地址:你的服务端口 如:http://192.168.150.101:28838
  • 在Service中注入IdService,调用getId()方法即可,例如:idService.getId(IdEnum.TRANSPORT_ORDER)

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

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

相关文章

大数据 flink 01 | 从零环境搭建 简单Demo 运行

什么是Flink Flink是一个开源的流处理和批处理框架,它能够处理无界和有界的数据流,具有高吞吐量、低延迟和容错性等特点 Flink 可以应用于多个领域如:实时数据处理、数据分析、机器学习、事件驱动等。 什么是流式处理?什么是批处理 流处理…

吴师兄:非科班程序员,创作出Github标星75.3K的宝藏项目,一周爆火……

这是《开发者说》的第18期,今天我们采访的是在Github上传LeetCode动画题解,获得75.3K标星宝藏项目的程序员吴师兄。 吴师兄从985大学毕业,从通信工程外包零基础转码程序员,逐渐进入一些中厂和大厂,工资也从三四千起步…

Elasticsearch——介绍、安装与初步使用

目录 1.初识 Elasticsearch1.1.了解 ES1.1.1.Elasticsearch 的作用1.1.2.ELK技术栈1.1.3.Elasticsearch 和 Lucene1.1.4.为什么不是其他搜索技术?1.1.5.总结 1.2.倒排索引1.2.1.正向索引1.2.2.倒排索引1.2.3.正向和倒排 1.3.Elasticsearch 的一些概念1.3.1.文档和字…

C++之stack 和 queue

目录 前言 1.stack的介绍和使用 1.1 stack的介绍 1.2 stack的使用 1.3 stack 的模拟 2. queue的介绍和使用 2.1 queue的介绍 2.2 queue的使用 2.3 queue的模拟 3.适配器 3.1 什么是适配器 3.2 STL标准库中stack和queue的底层结构 3.3 deque 的介绍(了解&…

智慧城市主要运营模式分析

(一)运营模式演变 作为新一代信息化技术落地应用的新事物,智慧城市在建设模式方面借鉴了大量工程建设的经验,如平行发包(DBB,Design-Bid-Build)、EPC工程总承包、PPP等模式等,这些模式在不同的发展阶段和条件下发挥了重要作用。 在智慧城市发展模式从政府主导、以建为主、…

一日连发两款视频大模型,火山引擎杀疯了!

9月24日,字节跳动旗下火山引擎在深圳举办AI创新巡展,并首次对外发布豆包视频生成-PixelDance、豆包视频生成-Seaweed两款AI大模型,并公布了多项AI大模型的全新升级,以一种全新的姿态迎接AI时代的到来。 雷科技此次受邀参与巡展&a…

David律所代理Jose Martin幽默水果版权首发维权,尚未TRO

案件基本情况:起诉时间:2024/9/18案件号:2024-cv-08484原告:Jose Martin原告律所:David起诉地:伊利诺伊州北部法院涉案商标/版权:原告品牌简介:西班牙的卓越艺术家Jose Martin以他非…

深度学习:常见损失函数简介--名称、作用和用法

目录 1. L1 Loss 2. NLL Loss (Negative Log Likelihood Loss) 3. NLLLoss2d 4. Gaussian NLL Loss 5. MSE Loss (Mean Squared Error Loss) 6. BCE Loss (Binary Cross-Entropy Loss) 7. Smooth L1 Loss 8. Cross Entropy Loss 1. L1 Loss 作用:计算预测值…

数据结构(Day18)

一、周学习内容 1、9.18 数据结构(Day15)-CSDN博客 2、9.19 数据结构(Day16)-CSDN博客 3、9.20 链表 目的 插入删除不需要移动任何节点(元素)。 不需要预估存储空间大小,长度动态增长或减小。…

jQuery——jQuery的2把利器

1、jQuery 核心函数 ① 简称:jQuery 函数,即为 $ 或者 jQuery ② jQuery 库向外直接暴露的是 $ 或者 jQuery ③ 引入 jQuery 库后,直接使用 $ 即可 当函数用:$(xxx) 当对象用:$.xxx&#x…

类与对象—python

一、类的含义 1.1类的作用(理解) 收集学生信息时,如果让同学们自主填写,信息的顺序、格式不一,内容混乱。如果发给同学们既定的表格,同学们按照规定的顺序、格式进行填写,那信息就会一目了然&…

深度学习03-神经网络01-什么是神经网络?

神经网络的基本概念 人工神经网络(Artificial Neural Network,ANN): 是一种模仿生物神经网络的计算模型。由多个神经元(或称为节点)组成,这些节点通过不同的连接来传递信息。 每个神经元可以接…

Git从了解到操作

Git常用命令 基本的linux命令 ls / ll 查看当前目录( ls 是查看目录有哪些文件夹,ll 是查看隐藏文件)cat 查看文件内容touch 创建文件vi vi编辑器 (使用 vi 编辑器是为了方便展示效果,也可以记事本、editPlus、notPad等其它编辑器) 备注 Git GUl: Gi…

html TAB、table生成

1. 代码 <!DOCTYPE html> <head> <meta charset"UTF-8"> <title>Dynamic Tabs with Table Data</title> <style> /* 简单的样式 */ .tab-content { display: none; border: 10px solid #ccc; padding: 30px; mar…

全志A133 android10 适配EC20 4G模块

一&#xff0c;移植适配 1. 驱动移植 代码路径&#xff1a;longan/kernel/linux-4.9/drivers/usb/serial/option.c diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index 9f96dd2..2f25466 100644 --- a/drivers/usb/serial/option.cb/drivers/us…

单例模式(饿汉式-懒汉式)

我给面试官讲解了单例模式后&#xff0c;他对我竖起了大拇指&#xff01;https://blog.csdn.net/weixin_41949328/article/details/107296517?ops_request_misc%257B%2522request%255Fid%2522%253A%2522FAEE9ABD-432D-416C-98C6-9DD939138DEB%2522%252C%2522scm%2522%253A%252…

Spring AOP实现原理-动态代理

目录 代理的基础概念 示例1&#xff1a;静态代理&#xff08;场景&#xff1a;客户通过中介租房东的房子&#xff09; 示例2&#xff1a;JDK动态代理实现房东、中介出租房屋 示例3&#xff1a;CGLib动态代理实现房东出租房屋 示例4&#xff1a;观察Spring IOC容器中代理对象…

【算法】贪心+堆排序实现大根堆及标准库容器类的融合使用

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由 JohnKi 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f4e2;未来很长&#…

git push错误:Out of memory, malloc failed (tried toallocate 947912704 bytes)

目录 一、错误截图 二、解决办法 一、错误截图 因项目文件过大&#xff0c;http.postBuffer设置的内存不够&#xff0c;所以报错。 二、解决办法 打开cmd窗口&#xff0c;执行如下命令即可 git config --global http.postBuffer 1024000000 如图所示 执行完成以后&#…

NCNN 源码(1)-模型加载-数据预处理-模型推理

参考 ncnn 第一个版本的代码。 0 整体流程 demo&#xff1a;squeezenet ncnn 自带的一个经典 demo&#xff1a;squeezenet 的代码: // 网络加载 ncnn::Net squeezenet; squeezenet.load_param("squeezenet_v1.1.param"); squeezenet.load_model("squeezenet_…