作者: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的调用形式,通过在启动注解配置accessTypeHTTP/DUBBO来确定,SDK主动加载相干依赖。

(2)应用时:

依据"Long"、"String"、"MixId"等三种id类型别离提供GlobalIdLongClientGlobalIdStringClientGlobalIdMixIDClient等三个客户端对象,并封装了对立的入参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的计划选型或自研提供参考。