冰河开源了全网首个完全开源的分布式全局有序序列号(分布式ID)框架!!

设计与实现


整体架构设计

mykit-serial的整体架构图如下所示。

在这里插入图片描述

mykit-serial框架各模块的含义如下:

  • mykit-bean:提供统一的bean类封装和整个框架使用的常量等信息。

  • mykit-common:封装整个框架通用的工具类。

  • mykit-config:提供全局配置能力。

  • mykit-core:整个框架的核心实现模块。

  • mykit-db:存放数据库脚本。

  • mykit-interface:整个框架的核心抽象接口。

  • mykit-service:基于Spring实现的核心功能。

  • mykit-rpc:以RPC方式对外提供服服务(后续支持Dubbo,motan、sofa、SpringCloud、SpringCloud Alibaba等主流的RPC框架)。

  • mykit-server:目前实现了Dubbo方式,后续迁移到mykit-rpc模块。

  • mykit-rest:基于SpringBoot实现的Rest服务。

  • mykit-rest_netty:基于Netty实现的Rest服务。

  • mykit-test:整个框架的测试模块,通过此模块可以快速掌握mykit-serial的使用方式。

发布模式

根据最终的客户使用方式,可分为嵌入发布模式,RPC发布模式和Rest发布模式。

  1. 嵌入发布模式:只适用于Java客户端,提供一个本地的Jar包,Jar包是嵌入式的原生服务,需要提前配置本地机器ID(或者服务启动时,由Zookeeper动态分配唯一的分布式序列号),但是不依赖于中心服务器。

  2. RPC发布模式:只适用于Java客户端,提供一个服务的客户端Jar包,Java程序像调用本地API一样来调用,但是依赖于中心的分布式序列号(分布式ID)产生服务器。

  3. REST发布模式:中心服务器通过Restful API提供服务,供非Java语言客户端使用。

发布模式最后会记录在生成的全局序列号中。

序列号类型

根据时间的位数和序列号的位数,可分为最大峰值型和最小粒度型。

1. 最大峰值型:采用秒级有序,秒级时间占用30位,序列号占用20位

| 字段 | 版本 | 类型 | 生成方式 | 秒级时间 | 序列号 | 机器ID |

| :-: | :-: | :-: | :-: | :-: | :-: | :-: |

| 位数 | 63 | 62 | 60-61 | 30-59 | 10-29 | 0-9 |

2. 最小粒度型:采用毫秒级有序,毫秒级时间占用40位,序列号占用10位

| 字段 | 版本 | 类型 | 生成方式 | 毫秒级时间 | 序列号 | 机器ID |

| :-: | :-: | :-: | :-: | :-: | :-: | :-: |

| 位数 | 63 | 62 | 60-61 | 20-59 | 10-19 | 0-9 |

最大峰值型能够承受更大的峰值压力,但是粗略有序的粒度有点大,最小粒度型有较细致的粒度,但是每个毫秒能承受的理论峰值有限,为1024,同一个毫秒如果有更多的请求产生,必须等到下一个毫秒再响应。

分布式序列号(分布式ID)的类型在配置时指定,需要重启服务才能互相切换。

数据结构

1. 序列号

最大峰值型

20位,理论上每秒内平均可产生2^20= 1048576个ID,百万级别,如果系统的网络IO和CPU足够强大,可承受的峰值达到每毫秒百万级别。

最小粒度型

10位,每毫秒内序列号总计2^10=1024个, 也就是每个毫秒最多产生1000+个ID,理论上承受的峰值完全不如我们最大峰值方案。

2. 秒级时间/毫秒级时间

最大峰值型

30位,表示秒级时间,2^30/60/60/24/365=34,也就是可使用30+年。

最小粒度型

40位,表示毫秒级时间,2^40/1000/60/60/24/365=34,同样可以使用30+年。

3. 机器ID

10位, 2^10=1024, 也就是最多支持1000+个服务器。中心发布模式和REST发布模式一般不会有太多数量的机器,按照设计每台机器TPS 1万/s,10台服务器就可以有10万/s的TPS,基本可以满足大部分的业务需求。

但是考虑到我们在业务服务可以使用内嵌发布方式,对机器ID的需求量变得更大,这里最多支持1024个服务器。

4. 生成方式

2位,用来区分三种发布模式:嵌入发布模式,RPC发布模式,REST发布模式。

00:嵌入发布模式

01:RPC发布模式

02:REST发布模式

03:保留未用

5. 序列号类型

1位,用来区分两种ID类型:最大峰值型和最小粒度型。

0:最大峰值型

1:最小粒度型

6. 版本

1位,用来做扩展位或者扩容时候的临时方案。

0:默认值,以免转化为整型再转化回字符串被截断

1:表示扩展或者扩容中

作为30年后扩展使用,或者在30年后ID将近用光之时,扩展为秒级时间或者毫秒级时间来挣得系统的移植时间窗口,其实只要扩展一位,完全可以再使用30年。

并发处理

对于中心服务器和REST发布方式,ID生成的过程涉及到网络IO和CPU操作,ID的生成基本都是内存到高速缓存的操作,没有IO操作,网络IO是系统的瓶颈。

相对于CPU计算速度来说网络IO是瓶颈,因此,ID产生的服务使用多线程的方式,对于ID生成过程中的竞争点time和sequence,这里使用了多种实现方式

  1. 使用concurrent包的ReentrantLock进行互斥,这是缺省的实现方式,也是追求性能和稳定两个目标的妥协方案。
  1. 使用传统的synchronized进行互斥,这种方式的性能稍微逊色一些,通过传入JVM参数-Dmykit.serial.sync.lock.impl.key=true来开启。
  1. 使用CAS方式进行互斥,这种实现方式的性能非常高,但是在高并发环境下CPU负载会很高,通过传入JVM参数-Dmykit.serial.atomic.impl.key=true来开启。

机器ID的分配

我们将机器ID分为两个区段,一个区段服务于RPC发布模式和REST发布模式,另外一个区段服务于嵌入发布模式。

0-923:嵌入发布模式,预先配置,(或者由Zookeeper产生),最多支持924台内嵌服务器

924 – 1023:中心服务器发布模式和REST发布模式,最多支持300台,最大支持300*1万=300万/s的TPS

如果嵌入式发布模式和RPC发布模式以及REST发布模式的使用量不符合这个比例,我们可以动态调整两个区间的值来适应。

另外,各个垂直业务之间具有天生的隔离性,每个业务都可以使用最多1024台服务器。

与Zookeeper集成

对于嵌入发布模式,服务启动需要连接Zookeeper集群,Zookeeper分配一个0-923区间的一个ID,如果0-923区间的ID被用光,Zookeeper会分配一个大于923的ID,这种情况,拒绝启动服务。

如果不想使用Zookeeper产生的唯一的机器ID,我们提供缺省的预配的机器ID解决方案,每个使用统一分布式全局序列号(分布式ID)服务的服务需要预先配置一个默认的机器ID。

时间同步

使用mykit-serial生成分布式全局序列号(分布式ID)时,需要我们保证服务器的时间正常。此时,我们可以使用Linux的定时任务crontab,定时通过授时服务器虚拟集群(全球有3000多台服务器)来核准服务器的时间。

ntpdate -u pool.ntp.orgpool.ntp.org

性能


最终的性能验证要保证每台服务器的TPS达到1万/s以上。

Restful API文档


产生分布式全局序列号

  • 描述:根据系统时间产生一个全局唯一的全局序列号并且在方法体内返回。

  • 路径:/genSerialNumber

  • 参数:N/A

  • 非空参数:N/A

  • 示例:http://localhost:8080/genSerialNumber

  • 结果:3456526092514361344

反解全局序列号

  • 描述:对产生的serialNumber进行反解,在响应体内返回反解的JSON字符串。

  • 路径:/expSerialNumber

  • 参数:serialNumber=?

  • 非空参数:serialNumber

  • 示例:http://localhost:8080/expSerialNumber?serialNumber=3456526092514361344

  • 结果:{“genMethod”:2,“machine”:1022,“seq”:0,“time”:12758739,“type”:0,“version”:0}

翻译时间

  • 描述:把长整型的时间转化成可读的格式。

  • 路径:/transtime

  • 参数:time=?

  • 非空参数:time

  • 示例:http://localhost:8080/transtime?time=12758739

  • 结果:Thu May 28 16:05:39 CST 2015

制造全局序列号

  • 描述:通过给定的分布式全局序列号元素制造分布式全局序列号。

  • 路径:/makeSerialNumber

  • 参数:genMethod=?&machine=?&seq=?&time=?&type=?&version=?

  • 非空参数:time,seq

  • 示例:http://localhost:8080/makeSerialNumber?genMethod=2&machine=1022&seq=0&time=12758739&type=0&version=0

  • 结果:3456526092514361344

Java API文档


产生全局序列号

  • 描述:根据系统时间产生一个全局唯一的分布式序列号(分布式ID)并且在方法体内返回。

  • 类:SerialNumberService

  • 方法:genSerialNumber

  • 参数:N/A

  • 返回类型:long

  • 示例:long serialNumber= serialNumberService.genSerialNumber();

反解全局序列号

  • 描述:对产生的分布式序列号(分布式ID)进行反解,在响应体内返回反解的JSON字符串。

  • 类:SerialNumberService

  • 方法:expSerialNumber

  • 参数:long serialNumber

  • 返回类型:SerialNumber

最后

权威指南-第一本Docker书

引领完成Docker的安装、部署、管理和扩展,让其经历从测试到生产的整个开发生命周期,深入了解Docker适用于什么场景。并且这本Docker的学习权威指南介绍了其组件的基础知识,然后用Docker构建容器和服务来完成各种任务:利用Docker为新项目建立测试环境,演示如何使用持续集成的工作流集成Docker,如何构建应用程序服务和平台,如何使用Docker的API,如何扩展Docker。

总共包含了:简介、安装Docker、Docker入门、使用Docker镜像和仓库、在测试中使用Docker、使用Docker构建服务、使用Fig编配Docke、使用Docker API、获得帮助和对Docker进行改进等9个章节的知识。

image

image

image

image

关于阿里内部都在强烈推荐使用的“K8S+Docker学习指南”—《深入浅出Kubernetes:理论+实战》、《权威指南-第一本Docker书》,看完之后两个字形容,爱了爱了!

如何构建应用程序服务和平台,如何使用Docker的API,如何扩展Docker。

总共包含了:简介、安装Docker、Docker入门、使用Docker镜像和仓库、在测试中使用Docker、使用Docker构建服务、使用Fig编配Docke、使用Docker API、获得帮助和对Docker进行改进等9个章节的知识。

[外链图片转存中…(img-85BAXrjC-1714584811607)]

[外链图片转存中…(img-w1wdMBFw-1714584811607)]

[外链图片转存中…(img-gcWuXOBf-1714584811607)]

[外链图片转存中…(img-EP6lzwQ6-1714584811608)]

关于阿里内部都在强烈推荐使用的“K8S+Docker学习指南”—《深入浅出Kubernetes:理论+实战》、《权威指南-第一本Docker书》,看完之后两个字形容,爱了爱了!

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
public synchronized String nextId() { long timestamp = timeGen(); //获取当前毫秒数 //如果服务器时间有问题(时钟后退) 报错。 if (timestamp < lastTimestamp) { throw new RuntimeException(String.format( "Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); } //如果上次生成时间和当前时间相同,在同一毫秒内 if (lastTimestamp == timestamp) { //sequence自增,因为sequence只有12bit,所以和sequenceMask相与一下,去掉高位 sequence = (sequence + 1) & sequenceMask; //判断是否溢出,也就是每毫秒内超过4095,当为4096时,与sequenceMask相与,sequence就等于0 if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); //自旋等待到下一毫秒 } } else { sequence = 0L; //如果和上次生成时间不同,重置sequence,就是下一毫秒开始,sequence计数重新从0开始累加 } lastTimestamp = timestamp; long suffix = (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence; String datePrefix = DateFormatUtils.format(timestamp, "yyyyMMddHHMMssSSS"); return datePrefix + suffix; } protected long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } protected long timeGen() { return System.currentTimeMillis(); } private byte getLastIP(){ byte lastip = 0; try{ InetAddress ip = InetAddress.getLocalHost(); byte[] ipByte = ip.getAddress(); lastip = ipByte[ipByte.length - 1]; } catch (UnknownHostException e) { e.printStackTrace(); } return lastip; }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值