在文章《分布式服务化系统一致性的“最佳实干”》中提出了定期校对的模式来保证系统的最终一致性。常规校对模式中最常用的方法是在每个系统之间传递并保存一个统一的唯一序列号(或traceid),通过系统之间的两两校验确保系统之间的步骤一致,不存在滞后行为或者第三方统一校验唯一序列号,即系统之间的状态一致,在互联网世界中,生成唯一序列号的服务系统俗称编号生成器。Twitter的Snowflake是一种流行的开源flake实现。Slowfake由Scala语言实现,文档简单,发布方式单一,缺乏支持和维护,很难在实际项目中直接使用。为了让Java领域的小伙伴能够在不同环境下快速使用发行者服务,本文推荐一款自主研发的多场景分布式发行者Vesta,使用Java语言编写,可以通过Jar形式访问包的内嵌在任何Java开发项目中,也可以通过服务或REST服务发布。发布方式灵活多样,使用简单、方便、高效。Vesta是一种通用的唯一序列号生成器,具有全球唯一、粗序、可逆、可制造等特点。支持三种发布模式:嵌入式发布模式、中心服务器发布模式、REST发布模式,根据业务的性能需求,可以生成最大峰值型和最小粒度型两种ID。其实现架构使其具备高性能、高可用性、可扩展性等互联网产品所需要的品质属性。它是一种通用的高性能信号发生器产品。本文重点介绍笔者独创的多场景分布式信号发生器Vesta的设计、实现和性能评测。还介绍了Vesta的发布方式和使用方法,最后介绍了如何在你的项目中使用Vesta。1如何思考和设计1.1目前存在的问题目前业务系统的ID使用的是数据库的自增字段,而自增字段完全依赖于数据库。很麻烦。当数据库分库分表时,有一种方法可以通过调整自增字段或者数据库序列的步长来实现ID的跨库唯一性,但是仍然是强依赖于数据库,并且有很多限制。并且强烈依赖于数据库类型,我们不推荐这种方式。1.2为什么不用UUIDUUID虽然可以保证ID的唯一性,但是不能满足业务系统需要的很多其他特性,比如:时间粗序、可逆性和可制造性。另外在生成UUID时,使用的是完整的时间数据,性能比较差。而且UUID比较长,占用空间大,间接导致数据库性能下降。更重要的是,UUID是不有序的,导致B+树索引在写入时,会出现过多的随机写入操作(连续的ID会产生部分顺序写入)。另外,在写的时候,因为不能产生顺序追加操作,所以需要插入操作,会把整个B+树节点读入内存。然后插入这条记录,将整个节点写回磁盘。当记录占用大量空间时,此操作将导致性能大幅下降。具体的压测报告可以参考:Mysql性能压测实战报告1.3需求分析与组织由于数据库对自增ID和UUID的限制比较多,所以需要梳理一下发号方的需求。1.全局唯一性有些业务系统可以使用比较小规模的唯一性。例如,如果用户是唯一的,那么同一个用户的顺序在使用自增序列的用户范围内也是唯一的。但是这样设计的话,订单系统在逻辑上会依赖于用户系统,因此,不如我们保证ID系统范围内的全局唯一性那么实用。分布式系统保证唯一全局的悲观策略是使用锁或者分布式锁,但是只要使用锁,性能就会大大降低。因此,我们决定利用时间的有序性,在一定时间单位下使用自增序列来实现全局唯一。2.大体有序。上面讨论的UUID最大的问题就是乱序。任何业务都希望生成的ID是有序的。然而,要在分布式系统中实现完整的顺序涉及数据的聚合。当然使用锁或者布锁,考虑到效率,只能采用折中方案。粗犷有序,粗犷到什么程度呢?目前主流的方案有两种,一种是秒级有序,一种是毫秒级有序,这里权衡取舍,我们决定支持两种方式,通过配置来确定服务使用其中一种方式。3.可逆解ID生成后,ID本身包含了很多信息。在网上查询时,我们通常会先看到ID。如果我们可以根据ID知道它是什么时候产生的,从哪里来的,那么一个可逆的ID就可以帮上大忙了。如果ID中有时间,并且可以倒转,那么在存储层面会节省大量时间戳等传统字段占用的空间。这也是一箭双雕的设计。4、可以制造一个系统,即使是高可用,也不能保证永远不出问题。有问题怎么办,人工处理,数据被污染怎么办,数据清洗,但是人工处理或者清洗数据的时候,如果使用数据库自??动增量字段,ID已经被后面的业务覆盖了,如何恢复到系统出错的时间窗口?因此,我们使用的数字生成器必须是可复制的、可恢复的和可制造的。5、高性能无论是业务,无论是订单还是产品,如果插入一条新的记录,那一定是业务的核心功能,对性能的要求非常高。ID的生成依赖于网络IO和CPU的性能。这不是瓶颈。根据经验,单机的TPS应该达到10000/s。6.高可用性首先,发行者必须是对等集群。如果一台机器挂了,请求必须能够转发到其他机器。另外,重试机制也是必不可少的。最后,如果远程服务宕机了,我们需要一个本地的容错方案,本地库的依赖模式可以作为高可用的最后一道屏障。7.可扩展性作为分布式系统,永远不能忽视的是业务在不断增长。业务的绝对容量并不是衡量一个系统的唯一标准。必须知道,业务总是在增长。因此,系统设计不仅要考虑容量承载的绝对容量,还要考虑业务的增长速度。系统的横向扩展能否满足业务的增长速度,是衡量一个系统的另一个重要标准。1.4设计与实现1.发布模式根据最终客户使用情况,可分为嵌入式发布模式、中央服务器发布模式和REST发布模式。嵌入式发布方式:只适用于Java客户端,提供本地Jar包,Jar包是嵌入式原生服务,需要提前配置本地机器ID(或者Zookeeper在服务启动时动态分配唯一ID,在第二个版本实现),但不依赖于中央服务器。中心服务器发布模式:只适用于Java客户端,提供一个服务客户端Jar包,Java程序像本地API一样被调用,但是依赖于中心ID生成服务器。REST发布模式:中心服务器通过RestfulAPI导出服务,供非Java语言客户端使用。发布方式最终记录在生成的ID中。另请参阅下面“数据结构”部分中与发布模式相关的详细信息。2、ID型按时间位数和序号位数分为最大峰型和最小粒度型。1).最大峰值类型:采用秒级排序,秒级时间占30位,序号占20位2)。最小粒度类型:采用毫秒级排序,毫秒级时间占40位,序号占10位最大峰值类型可以承受较大的峰值压力,但粗排序的粒度有点大,最小粒度类型有更细的粒度,但是每毫秒能承受的理论峰值是有限的,也就是1k。如果同一毫秒内有多个请求产生,则必须等到下一毫秒才响应。ID类型在配置时指定,需要重启服务才能切换。3.数据结构1).机器ID为10位,2^10=1024,即最多支持1000+台服务器。中央发布模式和REST发布模式一般不会有太多的机器。按照设计,每台机器的TPS为10000/s,10台服务器可以有100000/s的TPS,基本可以满足大部分业务需求。但是考虑到我们在业务服务中可以使用嵌入式发布方式,对机器ID的需求变大,这里最多支持1024台服务器。2).序列号的最大峰值为20位。理论上平均每秒可以生成2^20=1048576个ID,百万级别。如果系统的网络IO和CPU足够强大,可以承受的峰值达到百万每毫秒级别。最小粒度类型为10位,每毫秒序列号总数为2^10=1024,即每毫秒最多产生1000+个ID。理论上可以容忍的峰值还不如我们的最大峰值方案。3).秒级时间/毫秒级时间,最大峰值30位,表示秒级时间,2^30/60/60/24/365=34,即可以使用30+年。最小粒度类型为40位,表示毫秒级时间,2^40/1000/60/60/24/365=34,也可以用30+年。4).生成方式的两位数用来区分三种发布模式:嵌入式发布模式、中央服务器发布模式、REST发布模式。00:嵌入式发布模式01:中央服务器发布模式02:REST发布模式03:保留不使用5).ID类型1比特,用于区分两种ID类型:最大峰型和最小粒度型。0:最大峰值类型1:最小粒度类型6)。Version1位,用于扩展位或扩容时的临时解决方案。0:默认值,以免转成整数再转回字符串被截断1:表示扩容或扩容时,30年后作为延期使用,或者ID为30年后几乎用完,将延长至秒级或毫秒级。系统移植的时间窗口可以通过使用更高级别的时间来赚取。事实上,只要延长一位,就可以再使用30年。4.并发对于中心服务器和REST发布方式,ID生成过程涉及网络IO和CPU操作。ID生成基本上是内存到缓存的操作,没有IO操作,网络IO是系统的瓶颈。与CPU计算速度相比,网络IO才是瓶颈。因此,ID生成服务使用多线程。对于ID生成过程中的竞争点时间和顺序,我们使用并发包的ReentrantLock进行互斥。5.机器ID的分配我们将机器ID分为两部分,一部分服务于中心服务器发布模式和REST发布模式,另一部分服务于嵌入式发布模式。0-923:嵌入式发布模式,预配置,(或由Zookeeper生成,二版实现),最多支持924台嵌入式服务器924-1023:中央服务器发布模式和REST发布模式,最多支持300台服务器,最大支持300*10000=300万/sTPS。如果嵌入式发布模式、中央服务器发布模式和REST发布模式的使用不满足这个比例,我们可以动态调整两个区间的值来适应。此外,每个垂直业务本身都是隔离的,每个业务最多可以使用1024台服务器。6.与Zookeeper集成对于嵌入式发布模式,服务需要连接到Zookeeper集群。Zookeeper在0-923范围内分配一个ID。如果0-923范围内的ID用完了,Zookeeper会分配一个大于923的ID,拒绝启动服务。如果您不想使用Zookeeper生成的唯一机器ID,我们提供默认的预配置机器ID解决方案。每个使用统一编号生成器的服务都需要预先配置一个默认的机器ID。注意:此功能在第二个版本中实现。7、时间同步利用Linux的定时任务crontab,通过定时服务器的虚拟集群(全球有3000多台服务器)定时核准服务器的时间。ntpdate-upool.ntp.orgpool.ntp.org时间相关的影响和注意事项:1.调整时间会影响ID生成功能吗?1).如果机器没有重新启动以减慢时间,Vesta会抛出异常并拒绝生成ID。重新启动机器以加快时间。调整后将正常生成ID,调整期间不再生成ID。2).重新启动机器以减慢时间。灶神星可能会产生重复的时间。系统管理员需要确保不会发生这种情况。重新启动机器以加快时间。调整后将正常生成ID,调整期间不再生成ID。每4年同步一次秒会影响生成ID的功能吗?1).原子钟和电子钟的误差是每四年1秒,也就是说每4年电子钟就会比原子钟慢1秒。因此,每隔四年,网络时钟就会同步一次时间,但是本机Windows、Linux等不会自动同步时间,需要手动同步,或者使用ntpupdate与网络时钟同步。2).由于时钟调快了1秒,调整后不会影响ID生成,调整后1秒内不会生成ID。8.设计验证我们根据不同的信息段构建ID,使ID具有全局唯一性、可逆性、可制造性。我们使用秒级时间或者毫秒级时间和时间单元内部顺序递增的方法来保证ID的大致有序。对于中心服务器发布模式和REST发布模式,我们采用多线程处理。为了减少多线程之间的竞争,我们使用ReentrantLock对竞争点时间和顺序进行互斥。由于ReentrantLock内部使用了CAS,因此比JVM的Synchronized关键字要好。性能更好。在千兆网卡的前提下,至少可以达到10000/s的TPS。由于我们支持中心服务器发布模式、嵌入式发布模式和REST发布模式,如果某种模式不可用,您可以回退到其他发布模式。如果Zookeeper不可用,您可以回退到使用本地供应的机器ID。以达到服务的最大可用性。由于ID的设计,我们最多支持1024台服务器。我们将服务器机器号分为两段,一段从0开始,一段从128开始。分割线可以动态调整,以满足扩展性。2如何保证性能要求一个软件的发布必须保证满足性能要求。这通常需要在项目开始时提出性能要求,并在项目进行过程中通过性能测试进行验证。请参考文末源码链接下载源码和查看性能测试。用例,本章只讨论性能需求和测试结果,以及改进点。2.1性能要求最终的性能验证必须保证每台服务器的TPS达到10000/s以上。2.2测试环境笔记本,客户端服务器同机双核2.4GI3CPU,4G内存2.3嵌入式发布模式压测结果设置:并发数:1002.4中心服务器发布模式压测结果设置:并发数:1002.5REST发布模式(Netty实现)压测结果设置:并发数:100Boss线程数:1Workder线程数:4测试结果:2.6REST发布模式(SpringBoot+Tomcat)压测结果设置:并发数:100Boss线程数:1Workder线程数:2Executor线程数:最小25最大200测试结果:2.7性能测试总结根据测试,Netty服务可以达到11000QPS,而Tomcat只能回答5000QPS左右。嵌入式发布模式,即JVM内部调用最快,一秒内可应答40万以上。可见,在线服务的瓶颈在于网络IO和网络IO处理。采用Dubbo导入导出的中心服务器发布方式QPS小于2000,比Tomcat提供的HTTP服务QPS要小。这不符合常识。一方面需要检查DubboRPC是否需要优化,包括线程池策略,顺序另一方面REST使用apacheab测试,嵌入式发布模式使用自写客户端测试,是否有测试工具的一定差异。测试时发现loopback虚拟网卡的流量达到了30+M,没有达到千兆网卡的极限,双核CPU占用接近200%,也就是CPU已达到瓶颈。参考上面第三个总结,中心服务器的性能问题需要后续版本进行跟进和优化。3如何快速使用Vesta多场景分布式发布器支持嵌入式发布模式、中心服务器发布模式、REST发布模式,各发布模式的API文档,使用向导参考项目首页文档链接。3.1安装与启动1.下载最新版REST发布方式的发布包。点击下载:vesta-rest-netty-0.0.1-bin.tar.gz如果你通过源码安装Vesta发布包到你的Maven私服,你可以直接从你的Maven私服下载这个安装包:wgethttp//ip:port/nexus/content/groups/public/com/robert/vesta/vesta-rest-netty/0.0.1/vesta-rest-netty-0.0.1-bin.tar.gz2。将发布包解压到任意目录,解压:tarxzvfvesta-rest-netty-0.0.1-bin.tar.gz3。解压后更改property文件属性文件:vesta-rest-netty-0.0.1/conf/vesta-rest-netty.properties文件内容:vesta.machine=1022vesta.genMethod=2vesta.type=0注:机器ID为1022,如果有多台机器,将机器ID减1,同一个服务中机器ID不能重复。genMethod为2表示使用嵌入式发布模式type为0表示最大峰值类型,如果要使用最小粒度类型,设置为14。REST发布模式默认端口为8088,可以更改端口号通过更改启动文件,这里以10010为例启动文件:vesta-rest-netty/target/vesta-rest-netty-0.0.1/bin/server.sh文件内容:port=100105.修改启动脚本,授予执行权限,进入目录:cdvesta-rest-netty-0.0.1/bin执行命令:chmod755*6。启动服务,进入目录:cdvesta-rest-netty-0.0.1/bin执行命令:./start.sh7。如果看到如下信息,服务启动成功输出:apppath:/home/robert/vesta/vesta-rest-netty-0.0.1VestaRestNettyServerisstarted.3.2测试Rest服务1.通过URL访问生成ID命令:curl结果:11387295110266882.反转生成的ID命令:curlresult:{"genMethod":0,"machine":1,"seq":0,"time":12235264,"type":0,"version":0}JSON字符串显示逆向解的ID各分量的值。3.反转生成的日期命令:curl结果:FriMay2214:41:04CST20154。使用逆向数据伪造ID命令:curl结果:11387295110266884总结思考发卡机作为分布式服务系统不可或缺的基础设施一,对于保证系统的正确运行和高可用起着不可替代的作用。本文介绍原创开源多场景分布式发行器Vesta,介绍Vesta的设计、实现和使用。读者可以在实际项目中直接使用其任意一种发布方式,既安装又使用,读者也可以借鉴其中的设计思路和思路,开发自己的分布式发布设备。除了发行设备本身,本文根据一个开源项目的生命周期来构思文章的结果,从设计、实现、验证到使用向导,以及讨论遗留问题等,提供参考开源实现帮助读者学习如何创建平台软件流程,帮助读者在技术的道路上发展得越来越好。在一文中提到,全局唯一流ID可以聚合分布式系统中一个请求的路径,调用链中的spanid可以将聚合后的请求路径以树状结构展示,让技术支持人员轻松实现发现系统问题,快速定位问题发生的服务节点,提高应急响应效率。点击《如何设计一款多场景分布式发号器(Vesta)》阅读原文。【本文为专栏作家“李彦鹏”原创稿件。转载可通过作者简书号(李彦鹏)或专栏取得联系】点此查看该作者更多好文
