共计 6482 个字符,预计需要花费 17 分钟才能阅读完成。
作者:vivo IT 平台团队 - An Peng
本文介绍了什么是分布式 ID,分布式 ID 的业务场景以及 9 种分布式 ID 的实现形式,同时基于 vivo 外部 IT 的业务场景,介绍了自研鲁班分布式 ID 服务的实际。
一、计划背景
1.1 分布式 ID 利用的场景
随着零碎的业务场景复杂化、架构计划的优化演进,咱们在克服问题的过程中,也总会延长出新的技术诉求。分布式 ID 也是诞生于这样的 IT 倒退过程中,在不同的关联模块内,咱们须要一个全局惟一的 ID 来让模块既能并行地解耦运行,也能轻松地进行整合解决。以下,首先让咱们一起回顾这些典型的分布式 ID 场景。
1.1.1 零碎分库分表
随着零碎的继续运作,惯例的 单库单表 在撑持更高规模的数量级时,无论是在性能或稳定性上都曾经难以为继,须要咱们对指标逻辑数据表进行正当的物理拆分,这些同一业务表数据的拆分,须要有一套残缺的 ID 生成计划来保障拆分后的各物理表中同一业务 ID 不相冲突,并能在后续的合并剖析中能够方便快捷地计算。
以公司的营销零碎的订单为例,以后 岂但以分销与批发的指标组织区别来进行分库存储,来实现多租户的数据隔离,并且会以订单的业务属性(订货单、退货单、调拔单等等)来进一步分拆订单数据。在订单创立的时候,依据这些规定去结构全局惟一 ID,创立订单单据并保留在对应的数据库中;在通过订单号查问时,通过 ID 的规定,疾速路由到对应的库表中查问;在 BI 数仓的统计业务里,又须要汇总这些订单数据进行报表剖析。
1.1.2 零碎多活部署
无论是面对着全球化的 各国数据合规诉求 ,还是针对 容灾高可用的架构设计,咱们都会对同一套零碎进行多活部署。多活部署架构的各单元化服务,存储的单据(如订单 / 出入库单 / 领取单等)均带有部署区域属性的 ID 构造去形成全局惟一 ID,创立单据并保留在对应单元的数据库中,在前端依据单据号查问的场景,通过 ID 的规定,可疾速路由到对应的单元区域进行查问。对应多活部署架构的中心化服务,同步各单元的单据数据时,单据的 ID 是全局惟一,防止了汇聚数据时的 ID 抵触。
在公司的零碎部署中,公共畛域的 BPM、待办、营销畛域的零碎都大范畴地施行多活部署。
1.1.3 链路跟踪技术
在微服务架构风行的大背景下,此类微服务的利用比照单体利用的调用链路会更长、更简单,对问题的排查带来了挑战,应答该场景的解决方案,会在流量入口处产生全局惟一的 TraceID,并在各微服务之间进行透传,进行流量染色与关联,后续通过该 全局惟一的 TraceID,可疾速地查问与关联全链路的调用关系与状态,疾速定位根因问题。
在公司的各式各样的监控零碎、灰度治理平台、跨过程链路日志中,都会随同着这么一个技术组件进行撑持服务。
1.2 分布式 ID 外围的难点
- 唯一性:放弃生成的 ID 全局惟一,在任何状况下也不会呈现反复的值(如避免工夫回拔,时钟周期问题)。
- 高性能:ID 的需要场景多,核心化生成组件后,须要高并发解决,以靠近 0ms 的响应大规模并发执行。
- 高可用:作为 ID 的生产源头,须要 100% 可用,当接入的业务零碎多的时候,很难调整出各方都可承受的停机公布窗口,只能承受无损公布。
- 易接入:作为逻辑上简略的分布式 ID 要推广应用,必须强调开箱即用,容易上手。
- 规律性:不同业务场景生成的 ID 有其特色,例如有固定的前后缀,固定的位数,这些都须要配置化治理。
1.3 分布式 ID 常见的计划
罕用零碎设计中次要有下图 9 种 ID 生成的形式:
1.4 分布式 ID 鲁班的计划
咱们的零碎逾越了 公共、生产制作、营销、供应链、财经 等多个畛域。在分布式 ID 诉求下还有如下的 特点:
- 在业务场景上除了惯例的Long 类型ID,也须要反对“String 类型”、“MixId 类型”(后详述)等多种类型的 ID 生成,每一种类型也须要反对不同的长度的 ID。
- 在 ID 的形成规定上须要涵盖如操作类型、区域 、 代理 等业务属性的标识;须要集中式的配置管理。
- 在一些特定的业务上,基于平安的思考,还须要在尾部加上随机数来保障 ID 不能被轻易猜想。
综合参考了业界优良的开源组件与罕用计划均不能满足,为了对立治理这类根底技术组件的诉求,咱们抉择基于公司业务场景自研一套分布式 ID 服务:鲁班分布式 ID 服务。
二、零碎架构
2.1 架构阐明
三、设计要点
3.1 反对多种类型的 ID 规定
目前鲁班分布式 ID 服务共提供 ”Long 类型“、“String 类型”、“MixId 类型”等三种次要类型的 ID,相干 ID 形成规定与阐明如下:
3.1.1 Long 类型
(1)形成规定
动态构造由以下三局部数据组成,组成部分共19 位:
- 固定局部(4 位):由 FixPart+ServerPart 组成。
① FixPart(4 位):由大区 zone 1 位 / 代理 agent 1 位 / 我的项目 project 1 位 / 利用 app 1 位, 组成的 4 位数字编码。
② ServerPart(4 位):用于定义产生全局 ID 的服务器标识位,服务节点部署时动态分配。
- 动静局部 DynPart(13 位):System.currentTimeMillis()- 固定配置工夫的 TimeMillis(可满足应用 100 年)。
- 自增局部 SelfIncreasePart(2 位):用于在全局 ID 的客户端 SDK 外部自增局部,由客户端 SDK 管制,业务接入方无感知。共 2 位组成。
(2)降级机制
次要自增局部在服务器获取初始值后,由客户端 SDK 保护,直到自增 99 后再次拜访服务端获取下一轮新的 ID 以缩小服务端交互频率,晋升性能,服务端获取失败后抛出异样,接入业务侧需染指进行解决。
(3)样例阐明
3.1.2 String 类型
(1)形成规定
动态构造由以下五局部数据组成,组成部分共25~27 位:
- 固定局部操作位 op+FixPart(9~11 位):
① 操作位 op(2~4 位):2~4 位由业务方传入的业务类型标识字符。
② FixPart(7 位):业务接入时申请获取,由大区 zone 1 位,代理 agent 2 位,我的项目 project 2 位,利用 app 2 位组成。
- 服务器标识局部 ServerPart(1 位): 用于定义产生全局 ID 的服务器标识位,服务节点部署时动态分配 A~Z 编码。
- 动静局部 DynPart(9 位):System.currentTimeMillis()- 固定配置工夫的 TimeMillis,再转换为 32 进制字符串(可满足应用 100 年)。
- 自增局部 SelfIncreasePart(3 位):用于在全局 ID 的客户端 SDK 外部自增局部,由客户端 SDK 管制,业务接入方无感知。
- 随机局部 secureRandomPart(3 位):用于在全局 ID 的客户端 SDK 的随机局部,由 SecureRandom 随机生成 3 位 0 -9,A- Z 字母数字组合的平安随机数,业务接入方无感知。
(2)降级机制
次要自增局部由客户端 SDK 外部保护,个别状况下只应用 001–999 共 999 个全局 ID。也就是每向服务器申请一次,都在客户端内能够主动保护 999 个惟一的全局 ID。非凡状况下在拜访服务器连贯出问题的时候,能够应用带字符的自增来做服务器降级解决,应用产生 00A, 00B… 0A0, 0A1,0A2….ZZZ. 共有 36 36 36 – 1000(999 纯数字,000 不必)= 45656 个降级应用的全局 ID。
(3)样例阐明
3.1.3 MixId 类型
(1)形成规定
动态构造由以下三局部数据组成,组成部分共17 位:
- 固定局部 FixPart(4~6 位):
① 操作位 op(2~4 位):2~4 位由业务方传入的业务类型标识字符
② FixPart(2 位):业务接入时申请获取由代理 agent 2 位组成。
- 动静局部 DynPart(6 位): 生成 ID 的工夫,年(2 位)月(2 位)日(2 位)。
- 自增局部 SelfIncreasePart(7 位):用于在全局 ID 的客户端 SDK 外部自增局部,由客户端 SDK 管制,业务接入方无感知。
(2)降级机制
无,每次 ID 产生均需到服务端申请获取,服务端获取失败后抛出异样,接入业务侧需染指进行解决。
(3)样例阐明
3.2 业务自定义 ID 规定实现
鲁班分布式 ID 服务内置“Long 类型”,“String 类型”,“MixId 类型”等三种长度与规定固定的 ID 生成算法,除以上三种类型的 ID 生成算法外,业务侧往往有自定义 ID 长度与规定的场景诉求,在鲁班分布式 ID 服务内置 ID 生成算法未能满足业务场景时,为了能在该场景疾速反对业务,鲁班分布式 ID 服务提供了业务自定义接口并通过 SPI 机制在服务运行时动静加载,以实现业务自定义 ID 生成算法场景的反对,相干能力的 实现设计与接入流程 如下:
(1)ID 的形成局部次要分 FixPart、DynPart、SelfIncreasePart 三个局部。
(2)鲁班分布式 ID 服务的客户端 SDK 提供 LuBanGlobalIDClient 的接口与 getGlobalId(…)/setFixPart(…)/setDynPart(…)/setSelfIncreasePart(…)等四个接口办法。
(3)业务侧实现 LuBanGlobalIDClient 接口内的 4 个办法,通过 SPI 机制在业务侧服务进行加载,并向外暴露出 HTTP 或 DUBBO 协定的接口。
(4)用户在鲁班分布式 ID 服务治理后盾对自定义 ID 生成算法的类型名称与服务地址信息进行配置,并关联须要应用的 AK 接入信息。
(5)业务侧应用时调用客户端 SDK 提供的 LuBanGlobalIDClient 的接口与 getGlobalId 办法,并传入 ID 生成算法类型与 IdRequest 入参对象,鲁班分布式 ID 服务接管申请后,动静辨认与路由到对应 ID 生产算法的实现服务,并构建对象的 ID 返回给客户端,实现整个 ID 生成与获取的过程。
3.3 保障 ID 生成不反复计划
3.4 ID 服务无状态无损治理
服务部署的环境在虚拟机上,ip 是固定,惯例的做法是在配置表里配置 ip 与机器码的绑定关系(这样在服务扩缩容的时候就须要人为染指操作,存在肯定的脱漏配置危险,也带来了肯定的运维老本),但在容器的部署场景,因为每次部署时 IP 均是动态变化的,以前通过配置表里 ip 与机器码的映射关系的配置实现形式显然不能满足运行在容器场景的诉求,故在服务端设计了通过心跳上报实现机器码动态分配的机制,实现服务端节点 ip 与机器码动态分配、绑定的能力,达成部署自动化与无损公布的目标。
相干流程如下:
【留神】
服务端节点可能因为异样, 非正常地退出, 对于该场景, 这里就须要有一个解绑的过程,以后实现是通过公司平台团队的分布式定时工作服务,查看继续 5 分钟 (可配置) 没有上报心跳的机器码调配节点进行数据库绑定信息清理的逻辑, 重置相干机器码的地位供后续注册绑定应用。
3.5 ID 应用方接入 SDK 设计
SDK 设计次要以 ”接入快捷, 应用简略“ 的准则进行设计。
(1)接入时:
鲁班分布式 ID 服务提供了 spring-starter 包, 利用只需再 pom 文件依赖该 starter,在启动类里增加 @EnableGlobalClient,并配置 AK/SK 等租户参数即可实现接入。
同时鲁班分布式 ID 服务提供 Dubbo & Http 的调用形式,通过在启动注解配置 accessType 为HTTP/DUBBO来确定,SDK 主动加载相干依赖。
(2)应用时:
依据 ”Long“、”String“、”MixId“ 等三种 id 类型别离提供 GlobalIdLongClient、GlobalIdStringClient、GlobalIdMixIDClient 等三个客户端对象,并封装了对立的入参 RequestDTO 对象,业务零碎应用时只需构建对应 Id 类型的 RequestDTO 对象(反对链式构建),并调用对应 id 类型的客户端对象 getGlobalID(GlobalBaseRequestDTO globalBaseRequestDTO) 办法,即可实现 ID 的构建。
Long 类型 Id 获取代码示例:
package com.vivo.it.demo.controller;
import com.vivo.it.platform.luban.id.client.GlobalIdLongClient;
import com.vivo.it.platform.luban.id.dto.GlobalLongIDRequestDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
@RequestMapping("/globalId")
public class GlobalIdDemoController {
@Autowired
private GlobalIdLongClient globalIdLongClient;
@RequestMapping("/getLongId")
public String getLongId() {GlobalLongIDRequestDTO globalLongIDRequestDTO = GlobalLongIDRequestDTO.Builder()
.setAgent("1") // 代理, 接入申请时确定
.setZone("0") // 大区, 接入申请时确定
.setApp("8") // 利用, 接入申请时确定
.setProject("7") // 我的项目, 接入申请时确定
.setIdNumber(2); // 当次返回的 id 数量, 只对 getGlobalIDQueue 无效, 对 getGlobalID(...)有效
long longId = globalIdLongClient.getGlobalID(globalLongIDRequestDTO);
return String.valueOf(longId);
}
}
3.6 要害运行性能优化场景
3.6.1 内存应用优化
在我的项目上线初时,常常产生 FGC,导致服务进展,获取 ID 超时,通过剖析,鲁班分布式 ID 服务的服务端次要为内存敏感的利用,当高并发申请时,过多对象进入老年代从而触发 FGC,通过排查次要是 JVM 内存参数上线时是应用默认的,没有通过优化配置,JVM 初始化的内存较少,高并发申请时 JVM 频繁触发内存重调配,相干的对象也流程老年代导致最终频繁发送 FGC。
对于这个场景的优化思路次要是要相干内存对象在年老代时就疾速通过 YGC 回收,尽量少的对象进行老年代而引起 FGC。
基于以上的思路次要做了以下的优化:
- 增大 JVM 初始化内存(-Xms,容器场景里为 -XX:InitialRAMPercentage)
- 增大年老代内存(-Xmn)
- 优化代码,缩小代码里长期对象的复制与创立
3.6.2 锁颗粒度优化
客户端 SDK 再自增值应用完或肯定工夫后会向服务端申请新的 id 生成,这个时候须要保障该次申请在多线程并发时是只申请一次,以后设计是基于用户申请 ID 的接入配置,组成为 key,去获取对应 key 的对象锁,以缩小同步代码块锁的粒度,防止不同接入配置去在并发去近程获取新的 id 时,锁粒度过大,造成线程的阻塞,从而晋升在高并发场景下的性能。
四、业务利用
以后鲁班分布式 ID 服务日均 ID 生成量 亿级 ,均匀 RT 在0~1ms 内, 单节点可反对 万级 QPS,已全面利用在公司 IT 外部 营销订单、领取单据、库存单据、履约单据、资产治理编码 等多个畛域的业务场景。
五、将来布局
在可用性方面,以后鲁班分布式 ID 服务仍对 Redis、Mysql 等内部 DB 组件有肯定的依赖(如利用接入配置信息、MixId 类型自增局部 ID 计数器),布局在该依赖极其宕机的场景下,鲁班分布式 ID 服务仍能有一些降级策略,为业务提供可用的服务。
同时基于业务场景的诉求,反对规范模式的雪花算法等 ID 类型。
六、回顾总结
本文通过对分布式 ID 的 3 种利用场景,实现难点以及 9 种分布式 ID 的实现形式进行介绍,并对联合 vivo 业务场景个性下自研的鲁班分布式 id 服务从零碎架构,ID 生成规定与局部实现源码进行介绍,心愿对本文的阅读者在分布式 ID 的计划选型或自研提供参考。