Spring Cloud(十六):微服务分布式唯一ID

news2024/11/19 4:38:35
  • 分布式唯一ID
    • 特点
    • 方案
  • 雪花算法
    • 特点
    • 开源实现
    • 优缺点
  • 替代方案
    • UUID
    • Mongdb
    • Seata
    • 数据库生成
    • Redis
  • 基于美团的 Leaf分布式 ID 微服务
    • Leaf-segment 数据库方案
      双 buffer 优化 — TP999 数据波动大
      Leaf 高可用容灾 — DB 可用性
    • Leaf-snowflake 雪花方案
      弱依赖 ZooKeeper
  • 解决时钟问题
    • 综合对比其余 Leaf 节点的系统时间
    • 每隔一段时间节点都会上报自身系统时间写入 ZooKeeper
    • 机器的 NTP 同步问题

特点

  • 全局唯一性
  • 趋势递增、单调递增
  • 信息安全

方案

雪花算法

Snowflake 把 64-bit 分别划分成多段,分开来标示机器、时间等

特点:

  • 第 0 位: 符号位(标识正负),始终为 0,没有用,不用管
  • 第 1~41 位 :一共 41 位,用来表示时间戳,单位是毫秒,可以支撑 2 ^41 毫秒(约 69 年)
  • 第 42~52 位 :一共 10 位,一般来说,前 5 位表示机房 ID,后 5 位表 示机器 ID(实际项目中可以根据实际情况调整),这样就可以区分不同集群/机房的节点,这样就可以表示32个IDC,每个IDC下可以有32台机器。
  • 第 53~64 位 :一共 12 位,用来表示序列号。 序列号为自增值,代表单 台机器每毫秒能够产生的最大 ID 数(2^12 = 4096),也就是说单台机器每毫秒最多可以生成4096个唯一ID

理论上 snowflake 方案的 QPS 约为 409.6w/s,这种分配方式可以保证在任何 一个 IDC 的任何一台机器在任意毫秒内生成的 ID 都是不同的

基于 Snowflake 算法的开源实现

  • 美团的 Leaf
  • 百度的 UidGenerator(自 18 年后,UidGenerator 就基本没有再维护了, https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md)

这些开源实现对原有的 Snowflake 算法进行了优化。在实际项目中,我们一般也 会对 Snowflake 算法进行改造,最常见的就是在算法生成的 ID 中加入业务类型信息

Snowflake 优缺点

优点:

  • 毫秒数在高位,自增序列在低位,整个 ID 都是趋势递增的
  • 不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成 ID 的 性能也是非常高的
  • 可以根据自身业务特性分配 bit 位,非常灵活

缺点:

  • 强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不 可用状态

替代方案

UUID

UUID.randomUUID() 形式为 8-4-4-4-12 的 36 个字符 f75d0fbf-77ce-47d0-a2b3-0a7ef4a410b2

优点:

  • 性能非常高:本地生成,没有网络消耗

缺点:

  • 不易于存储:UUID 太长,16 字节 128 位,通常以 36 长度的字符串表示, 很多场景不适用
  • 信息不安全:基于 MAC 地址生成 UUID 的算法可能会造成 MAC 地址泄露, 这个漏洞曾被用于寻找梅丽莎病毒的制作者位置
  • ID 作为主键时在特定的环境会存在一些问题,比如做 DB 主键的场景下,UUID 就非常不适用:
    • MySQL官方有明确的建议主键要尽量越短越好,36个字符长度的UUID不符合要求
    • MySQL 索引不利:如果作为数据库主键,在 InnoDB 引擎下,UUID 的 无序性可能会引起数据位置频繁变动,严重影响性能。在 MySQL InnoDB 引擎中 使用的是聚集索引,由于多数 RDBMS 使用 B-tree 的数据结构来存储索引数据, 在主键的选择上面我们应该尽量使用有序的主键保证写入性能

Mongdb objectID

通过“时 间+机器码+pid+inc”共 12 个字节,通过 4+3+2+3 的方式最终标识成一个 24 长度 的十六进制字符

Seata

内置了一个分布式 UUID 生成器, 用于辅助生成全局事务 ID 和分支事务 ID,我们同样可以拿来使用,完整类名为: io.seata.common.util.IdWorker

数据库生成

  1. 创建一个数据库表
CREATE TABLE `sequence_id` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `stub` char(10) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
UNIQUE KEY `stub` (`stub`) comment '字段无意义,只是为了占位; 给 stub 字段创建了唯一索引,保证其 唯一性'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  1. 通过 replace into 来插入数据
BEGIN;
REPLACE INTO sequence_id (stub) VALUES ('stub'); 
SELECT LAST_INSERT_ID();
COMMIT;

replace 是 insert 的增强版,replace into 首先尝试插入数据到表中

  • 如果发现 表中已经有此行数据(根据主键或者唯一索引判断)则先删除此行数据,然后插 入新的数据
  • 否则,直接插入新数据

在这里插入图片描述
优点:

  • 非常简单,利用现有数据库系统的功能实现,成本小,有 DBA 专业维护
  • ID 号单调自增,存储消耗空间小

缺点:

  • 支持的并发量不大
  • 存在数据库单点问题(可以使用数据库集群解决,不过 增加了复杂度)
  • ID 没有具体业务含义
  • 安全问题(比如根据订单 ID 的递增 规律就能推算出每天的订单量,商业机密啊! )
  • 每次获取 ID 都要访问一次数据库(增加了对数据库的压力,获取速度也慢)

对于 MySQL 性能问题,可用如下方案解决:

在分布式系统中我们可以多部 署几台机器,每台机器设置不同的初始值,且步长和机器数相等, 比如有两台机器。设置步长 step 为 2

  • TicketServer1 的初始值为 1 (1,3,5,7,9,11…)
  • TicketServer2 的初始值为 2 (2,4,6,8,10…)

缺点:

  • 系统水平扩展比较困难,比如定义好了步长和机器台数之后,如果要添加机器
  • ID 没有了单调递增的特性,只能趋势递增,这个缺点对于一般业务需求不 是很重要,可以容忍
  • 数据库压力还是很大,每次获取 ID 都得读写一次数据库,只能靠堆机器来 提高性能

Redis

通过 Redis 的incr命令即可实现对 id 原子顺序递增, 为了提高可用性和并发,我们可以使用 Redis Cluster

优点:

  • Redis 基于内存,我们需要持久化数据, 避免重启机器或者机器故障后数据丢失。很明显,Redis 方案性能很好并且生成 的 ID 是有序递增的

缺点:

  • Redis 开启了持久化,不管是快照(snapshotting, RDB)、只追加文件(append-only file, AOF)还是 RDB 和 AOF 的混合持久化依 然存在着丢失数据的可能,那就意味着产生的 ID 存在着重复的概率

基于美团的 Leaf分布式 ID 微服务

Leaf-segment 数据库方案 — 生成趋势递增的 ID,同时 ID 号是可计算的

DB调优:
批量获取,每次获取一个 segment(step 决定大小)号段的值。用完之后再去数据 库获取新的号段,可以大大的减轻数据库的压力

业务调优:
各个业务不同的发号需求用 biz_tag 字段来区分,每个 biz-tag 的 ID 获取相互 隔离,互不影响。如果以后有性能需求需要对数据库扩容,不需要上述描述的复 杂的扩容操作,只需要对 biz_tag 分库分表就行

在这里插入图片描述
现在有 3 台机器,每台机器各取 1000 个

  • 第一台机器是 1~1000 的号段
  • 第二台机器是 1001~2000 的号段
  • 第三台机器是 2001~3000 的号段

当这个号段用完时,会去加载另一个长度为 step=1000 的号段,假设另外两台号段都没有更新,这个时候第一台机器新加载的号段就应该是 3001~4000

同时数据库对应的 biz_tag 这条数据的 max_id 会从 3000 被更新成 4000

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 号码是趋势递增的 8byte 的 64 位数字,满足上述数据库存储的主键要求
  • 容灾性高:Leaf 服务内部有号段缓存,即使 DB 宕机,短时间内 Leaf 仍能正 常对外提供服务
  • 可以自定义 max_id 的大小,非常方便业务从原有的 ID 方式上迁移过来

缺点:

  • ID 号码不够随机,能够泄露发号数量的信息,不太安全
  • TP999 数据波动大,当号段使用完之后还是会在获取新号段时在更新数据库 的 I/O 依然会存在着等待,tg999 数据会出现偶尔的尖刺(压力瞬增)
  • DB 宕机会造成整个系统不可用

双 buffer 优化 — TP999 数据波动大

希望 DB 取号段的过程能够做到无阻塞,不需要在 DB 取号段的时候 阻塞请求线程,即当号段消费到某个点时就异步的把下一个号段加载到内存中。 而不需要等到号段用尽的时候才去更新号段。这样做就可以很大程度上的降低系 统的 TP999 指标

采用双 buffer 的方式,Leaf 服务内部有两个号段缓存区 segment

  • 当前号段已下发 10%时,如果下一个号段未更新,则另启一个更新线程去更新下一个号段
  • 当前号段全部下发完后,如果下个号段准备好了则切换到下个号段为当前 segment 接着下发,循环往复

通常推荐 segment 长度设置为服务高峰期发号 QPS 的 600 倍(10 分钟), 这样即使 DB 宕机,Leaf 仍能持续发号 10-20 分钟不受影响

Leaf 高可用容灾 — DB 可用性

  • 采用一主两从
  • 分机房部署
  • Master 和 Slave 之间采用半同步方式同步数据,这种方案在一 些情况会退化成异步模式,甚至在非常极端情况下仍然会造成数据不一致的情况, 但是出现的概率非常小
  • 如果要保证 100%的数据强一致,可以选择使用“类 Paxos 算法”实现的强一致 MySQL 方案

Leaf-snowflake 雪花方案

Leaf-snowflake 方案完全沿用 snowflake 方案的 bit 位设计,即是“1+41+10+12” 的方式组装 ID 号

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

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

弱依赖 ZooKeeper

除了每次会去 ZK 拿数据以外,也会在本机文件系统上缓存一个 workerID 文 件。当 ZooKeeper 出现问题,恰好机器出现问题需要重启时,能保证服务能够正 常启动。这样做到了对三方组件的弱依赖。

解决时钟问题

1. 新节点通过检查综合对比其余 Leaf 节点的系统时间来判断自身系统时间 是否准确

  1. 取所有运行中的 Leaf-snowflake 节点的服务 IP:Port
  2. 通过 RPC 请求得到所有节点的系统时间
  3. 计算 sum(time)/nodeSize
  4. 本机时间与这个平均值是否在阈值之内
  5. 准确正常启动服务
  6. 不准确启动失败并报警

2. 在运行过程中,每隔一段时间节点都会上报自身系统时间写入 ZooKeeper

ZooKeeper 中登记过的老节点,同样会比较自身系统时间和 ZooKeeper 上本节点曾经的记录时间以及所有运行中的 Leaf-snowflake 节点的时间,不准确 同样启动失败并报警

3. 服务运行过程中,机器的 NTP 同步也会造成秒级别的回退,由于强依赖 时钟,对时间的要求比较敏感

  1. 直接关闭 NTP 同步
  2. 时钟回拨的时候直接不提供服务直接返回 ERROR_CODE
  3. 做一层重试,然后上报报警系统,更或者是发现有时钟回拨之 后自动摘除本身节点并报警

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

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

相关文章

加减大师-第10届蓝桥杯Scratch选拔赛真题精选

[导读]:超平老师计划推出Scratch蓝桥杯真题解析100讲,这是超平老师解读Scratch蓝桥真题系列的第98讲。 蓝桥杯选拔赛每一届都要举行4~5次,和省赛、国赛相比,题目要简单不少,再加上篇幅有限,因此我精挑细选…

Vue学习笔记--第一章(尚硅谷学习视频总结)

目录 一、第一章 Vue核心 1.1. Vue简介 1.1.1. 官网 1.1.2. 介绍与描述 1.1.3. Vue 的特点 1.1.4. 与其它 JS 框架的关联 1.1.5. Vue 周边库 1.2.初识Vue 1.3. 模板语法 1.4. 数据绑定 1.5 el与data的两种写法 1.6 MVVM模型 1.7 Vue中的数据代理 1.8.事件处理 1.…

从零学习 InfiniBand-network架构(九) —— IB协议中子网本地地址

从零学习 InfiniBand-network架构(九) —— IB协议中子网本地地址 🔈声明: 😃博主主页:王_嘻嘻的CSDN主页 🔑未经作者允许,禁止转载 🚩本专题部分内容源于《InfiniBand-n…

鸿蒙3.0应用开发若干问题及上架总结

1.如何去掉默认标题栏,实现全屏显示? 在config.json中的ability配置信息中添加属性: "abilities": [ {..."metaData": {"customizeData": [{"name": "hwc-theme","value": &q…

Buildroot系列开发(五)bootloader简述

参考:百问网 文章目录1.什么是Boot-loader?2.有哪些bootloader?哪些支持linux?3.Bootloader支持的Flash设备4.Bootloader支持的文件系统类型4.Bootloader支持的CPU架构5.Bootloader总结1.什么是Boot-loader? 2.有哪些bootloader?哪些支持linux&#…

广州蓝景分享——前端学习5 种在 JavaScript 中获取字符串第一个字符的方法

在本文中,我们将研究多种方法来轻松获取 JavaScript 中字符串的第一个字符。 1.charAt() 方法 要获取字符串的第一个字符,我们可以在字符串上调用 charAt() ,将 0 作为参数传递。例如,str.charAt(0) 返回 str 的第一个字符。 c…

AT1106S(PHS/EN输入接口通道0.8A低压H桥直流刷式电机驱动IC)

描述 泛海微AT1106S为摄像机、消费类产品、玩具和其它低电压或者电池供电的运动控制类应用提供了一个集成的电机驱动器解决方案。泛海微AT1106S能够驱动一个直流电机或其他诸 如螺线管的器件。输出驱动模块由N MOS功率管构成的H桥组成,以驱动电机绕组。泛海微AT110…

车企接连押注「重感知」 ,高精地图真会被弃用?

实现高阶智能驾驶,“重感知”是否为大势所趋? 答案正日益明晰。 2022年,以特斯拉为代表的“重感知”阵营,押注者正日趋增多。以在2022年尝试落地城市NOA的三家厂商为例:毫末智行一早便属“重感知”阵营;小…

【20221208】【每日一题】目标和

给你一个整数数组 nums 和一个整数 target 。 向数组中的每个整数前添加 或 - ,然后串联起所有整数,可以构造一个 表达式 : 例如,nums [2, 1] ,可以在 2 之前添加 ,在 1 之前添加 - ,然后串…

5G无线技术基础自学系列 | SA移动性管理流程

素材来源:《5G无线网络规划与优化》 一边学习一边整理内容,并与大家分享,侵权即删,谢谢支持! 附上汇总贴:5G无线技术基础自学系列 | 汇总_COCOgsta的博客-CSDN博客 SA移动性管理流程包括站内切换、Xn切换…

Java基础之序列化和反序列化

序列化的实现 java.io中的对象流提供了序列化和反序列化对象的方法 对象输出流 ObjectOutputStream 构造方法: ObjectOutputStream(OutputStream out) 保存对象的方法: void writeObject(Object obj) 对象输入流 ObjectInputStream 构造方法&…

使用 MySQL、Thymeleaf 和 Spring Boot Framework 上传、存储和查看图像

在本文中,我们将使用 Spring Boot 框架从头开始构建映像库应用程序,用户可以在其中列出其映像。 以下是我们将在应用程序中实现的功能。 用户可以列出他们的图像以及详细信息,例如, 名字描述图像价格。(如果他们想卖…

Win11 启用旧右键菜单 _ Windows11 右键改回老版

Win11 系统在使用上和之前的系统差不多,但是在设计上,有了很大的改变,系统界面,设置等功能都使用了全新的风格,包括右键菜单,这让很多用户使用起来都很不习惯,因此想改回旧版的右键菜单来使用。…

汽车控制器概述

目录 一、整车控制器(VCU) 功能 工作模式 二、发动机控制器/电子控制单元(ECU) ECU基本组成 ECU的作用 ECU的工作原理 常见的ECU的类型 三、电机控制器(MCU) 四、 电池管理系统(BMS&a…

cubeIDE开发, stm32的USB从设备串口驱动设计

一、USB_OTG简介 USB_OTG(OTG,ON THE GO)是一款双角色设备(DRD) 控制器,同时支持从机(USB DEVICE)功能和主机(USB HOST)功能。在主机模式下,OTG 支持全速(OTG…

2022圣诞在即,出海品牌如何做好网红营销?

随着2022圣诞节逐渐临近,节日气氛也开始浓郁起来,尤其在社交媒体上,圣诞主题的内容越来越多,随之而来的则是各种营销与折扣。受经济形势影响,性价比在当下显得尤为重要,有60%的消费者表示,今年圣…

【能效分析】安科瑞变电所运维云平台解决方案应用分析

概述 安科瑞 李亚俊 壹捌柒贰壹零玖捌柒伍柒 AcrelCloud-1000变电所运维云平台基于互联网+大数据、移动通讯等技术开发的云端管理平台,满足用户或运维公司监测众多变电所回路运行状态和参数、室内环境温湿度、电缆及母线运行温度、现场设备或环境视频场…

CentOS MySQL安装

1.查询是否已经存在mariadb。 rpm -qa|grep mariadb如果存在需要卸载。 rpm -e --nodeps mariadb-libs-5.5.68-1.el7.x86_642.通过xftp上传MySQL和Hive压缩包。 3.解压压缩包。 tar -zxvf apache-hive-2.3.4-bin.tar.gz -C /opt/apps/ tar -zxvf mysql-5.7.27-el7-x86_64.ta…

042-推箱子游戏源代码2

上一讲:041-推箱子游戏1_CSDN专家-赖老师(软件之家)的博客-CSDN博客 摘要: 1、使用JAVA基础知识 2、GUI界面编程实现推箱子界面,常用控件的综合应用; 3、使用JAVA绘图技术实现推箱子过程的绘图功能; 4、使用键盘事件,通过方向键实现推箱子过程; 5、使用音频技术,…

Android实现车辆检测(含Android源码 可实时运行)

Android实现车辆检测(含Android源码 可实时运行) 目录 Android实现车辆检测(含Android源码 可实时运行) 1. 前言 2. 车辆检测数据集说明 3. 基于YOLOv5的车辆检测模型训练 4.车辆检测模型Android部署 (1) 将Pytorch模型转换ONNX模型 &#xff08…