发布者应具备的特性:作为一个独立的系统,发布者对外提供服务,满足高可用、高并发、弱依赖、无单点问题的服务自服务监控与治理现有系统接入成本低。ID唯一:没有重复的ID号趋于增加。实现方案目前常见的数字生成器实现方案如下:1.标准类型的UUIDUUID(UniversallyUniqueIdentifier)包含32个16位基数,用连字符分成五段,36个字符,形式为8-4-4-4-12,例如:550e8400-e29b-41d4-a716-446655440000。优点:非常高性能:本地生成,无网络消耗。缺点:不易存储:UUID太长,16字节128位,通常用36长度的字符串表示,很多场景不适用。信息不安全:基于MAC地址生成UUID的算法可能会导致MAC地址泄露。该漏洞曾被用于寻找Melissa病毒的创建者。使用ID作为主键时,在特定环境下会出现一些问题。比如在DB主键的场景下,UUID就很不适用。常见,所以单独分析)生成ID的算法。该方案将64位分成多个段,分别标记机器、时间等。时间可以表示(1L<<41)/(1000L*3600*24*365)=69年,10位机器分别可以表示1024台机器。如果我们有IDC划分的需求,我们也可以将10bit划分为IDC划分为5bit,工作机划分为5bit。这样就可以表示32个IDC,每个IDC可以有32台机器,可以根据自己的需要来定义。12个自增序列号可以代表2^12个ID。理论上,雪花方案的QPS约为409.6w/s。这种分发方式可以保证任何一个IDC中任何一台机器在任何毫秒内生成的任何ID都是不同的。这种方式的优缺点是:优点:毫秒数在高位,自增序列在低位,整个ID呈递增趋势。不依赖数据库等第三方系统,以服务形式部署,稳定性更高,生成ID的性能更高。比特可以根据自己的业务特点进行分配,非常灵活。缺点:对机器时钟依赖性强,如果拨回机器上的时钟,会造成重复编号或服务不可用。3、数据库生成以MySQL为例,为字段设置auto_increment_increment和auto_increment_offset,保证ID自增。各业务使用如下SQL读写MySQL获取身份证号。begin;REPLACEINTOTickets64(stub)VALUES('a');SELECTLAST_INSERT_ID();commit;这种方案优缺点如下:优点:非常简单,利用现有数据库系统的功能实现,低成本,DBA专业维护。ID号单调递增,可以实现一些对ID有特殊要求的业务。缺点:对DB的依赖性强,当DB出现异常时,整个系统不可用,这是一个致命的问题。配置主从复制可以尽可能的提高可用性,但是在特殊情况下数据的一致性很难保证。主从切换时不一致可能会导致重复编号。ID发行的性能瓶颈仅限于单个MySQL的读写性能。对于MySQL的性能问题,可以采用如下解决方案:在分布式系统中,我们可以多部署几台机器,为每台机器设置不同的初始值,步长等于机器的数量。例如,有两台机器。设置步长为2,TicketServer1的初始值为1(1,3,5,7,9,11...),TicketServer2的初始值为2(2,4,6,8,10...).如下图,为实现上述方案,分别设置两台机器的相应参数,TicketServer1从1开始发号,TicketServer2从2开始发号,两台机器每次发号加2。TicketServer1:auto-increment-increment=2auto-increment-offset=1TicketServer2:auto-increment-increment=2auto-increment-offset=2假设我们要部署N台机器,需要设置步长为N,初始每台机器的值依次为0,1,2...N-1,那么整个架构就变成了下图所示:这种架构看似能够满足性能需求,但是它有以下不足:系统横向扩展比较困难,比如定义step总结完机器数量,如果想增加机器怎么办?假设只有一台机器发送数字1,2,3,4,5(步长为1),此时需要扩容一台机器。可以这样做:将第二台机器的初始值设置得比第一台高很多,比如14(假设第一台机器不能在扩容时间内发到14),设置步长为2,那么本机发的数都是14之后的偶数,然后去掉第一个,保持ID值为奇数,比如7,然后修改第一个的步长为2。让它符合我们定义的号码段标准。对于这个例子,就是让第一个单元只产生奇数。缩放解决方案看起来复杂吗?好像还可以,现在想象一下如果我们有100台机器在线,这个时候扩容应该怎么做呢?那是一场噩梦。因此,系统的水平扩展方案复杂且难以实现。ID不具有单调递增的特性,只能呈趋势递增。这个缺点对于一般的业务需求来说不是很重要,可以容忍。数据库压力还是很大的。每获取一个ID,都要对数据库进行一次读写,只能通过堆机来提高性能。4.Leaf-segmentdatabasescheme第一个Leaf-segment方案在使用数据库的方案上有以下变化:-原方案每获取一个ID都要读写一次数据库,对数据库造成很大的压力数据库。而是使用代理服务器分批获取,每次获取一个段的值(步长决定大小)。使用后去数据库获取一个新的号码段,可以大大减轻数据库的压力。-biz_tag字段用于区分每个业务不同的发号要求,每个biz-tag的ID获取相互隔离,互不影响。如果以后因为性能需求需要扩容数据库,就不需要上面介绍的那些复杂的扩容操作,只需要对biz_tag分库分表即可。数据库表设计如下:+------------+------------+-----+-----+--------------------+-----------------------------+|领域|类型|空|键|默认|额外|+------------+------------+------+-----+--------------------+--------------------------------+|商业标签|变种(128)|否|优先级||||最大ID|大整数(20)|否||1|||步骤|整数(11)|否||空|||描述|(256)|是||空|||更新时间|时间戳|否||当前_时间戳|关于更新CURRENT_TIMESTAMP|+------------+---------------+------+------+-----------------+--------------------------+重要字段说明:biz_tag用于区分业务,max_id表示当前分配给biz_tag的ID号段的最大值,step表示每个时间段长度分配的编号。原来每次获取ID都需要写入数据库,而现在只需要设置步长足够大,比如1000,那么只有消费完1000个数才会读取数据库,又写了读取和写入数据库的频率已从1次降低到1次/step。大致结构如下图所示:test_tag在第一台Leaf机器上是1到1000之间的一个数字。当此号码范围用完时,将加载另一个号码范围。对于长度为step=1000的号码段,假设另外两个号码段没有更新,此时第一台机器新加载的号码段应该是3001~4000。同时,数据库对应的biz_tag数据的max_id将从3000更新为4000。更新编号段的SQL语句如下:BeginUPDATEtableSETmax_id=max_id+stepWHEREbiz_tag=xxxSELECTtag,max_id,stepFROMtableWHEREbiz_tag=xxxCommit这种模式有以下优点和缺点:优点:叶子服务可以轻松线性扩展,性能可以完全支持大部分业务场景。ID号是一个8字节的64位数字,呈递增趋势,满足上述数据库存储的主键要求。高容灾:Leaf服务内部有数段缓存,即使DB宕机,Leaf仍然可以在短时间内对外提供服务。可以自定义max_id的大小,非常方便业务从原来的id方式迁移。缺点:身份证号不够随机,会泄露发号数,不太安全。TP999数据波动较大。当numbersegment用完后,仍然会挂在更新数据库的I/O上,tg999数据会偶尔出现尖峰。DB宕机会导致整个系统不可用。对于第二个不足,Leaf-segment做了一些优化。简单的说,Leaf获取数段的时机是在数段用完的时候进行的,也就是说数段临界点的ID传递时间取决于下一次从DB中取回数段的时间,而这期间传入的请求也会导致线程阻塞,因为没有检索到DB号段。如果请求的DB的网络和DB的性能稳定,这种情况对系统影响不大,但是如果在取DB的时候网络出现波动,或者DB出现慢查询,整个系统的响应时间会慢下来。为此,我们希望DB取段的过程可以是非阻塞的,在DB取段的时候不需要阻塞请求线程,也就是当number段消费到一定的时候,下一个段将被异步加载到内存中。无需等到号码段用完再更新号码段。这样做可以大大降低系统的TP999指标。具体实现如下图所示:采用双缓冲的方式,Leaf服务内部有两个numbersegment缓冲段。当当前号段发出10%后,如果下一个号段还没有更新,则启动另一个更新线程更新下一个号段。当前段全部发送完后,如果下一个段就绪,则切换到下一个段发送到当前段,如此循环。每个商业标签都有消费速度监控。通常建议段长设置为服务高峰期QPS的600倍(10分钟),这样即使DB宕机,Leaf也可以不受影响继续发号10-20分钟。每次有请求过来,都会判断下一个号段的状态,更新这个号段,所以偶尔的网络抖动不会影响下一个号段的更新。5.TDDL序列生成方法TDDL是阿里的分库分表中间件,包含全局数据库ID的生成方法。主要思路是利用数据库同步ID信息。每次批量取出一定数量的可用ID存入内存。使用后,再次请求数据库获取下一批可用的ID。每次获取可用ID的个数由步长控制,可以根据实际业务中的使用速度进行配置。每个业务都可以给自己的序列起一个唯一的名字,用来隔离各个业务系统的ID。优点:与闪烁方案相比,数据库写入压力大大降低,数据库不再是性能瓶颈。与闪烁方案相比,ID生成的性能有很大的提升,因为在获得一个可用的号码段后,直接分配到内存中,这样每次读取数据库的性能提升了几个数量级。可以通过seqName字段区分不同业务的不同ID需求,各个seqName的ID获取相互隔离,互不影响。缺点:对数据库的依赖性强,数据库异常时整个系统不可用。下一篇将介绍这些方案的企业实施案例
