ID generation distributed systems approach

Foreword

Projects generally stand-alone or a single database may be relatively small size, adapt scenes are more limited, traffic and traffic platforms are small, the way business is generated ID of primitive enough, but it does not bring such a system problems and bottlenecks, so in this case we do not have given it much attention. But large-scale complex business, high concurrent distributed application scenarios, it is clear that the ID-generation project will not be as small as for manufacturers that rely solely on simple data increment sequence to complete, but in a distributed environment this method has been unable to meet the needs of the business, not only can not be completed operational capacity, business ID generation speed or repeated failure could have serious problems to the system. So this time, we look at how manufacturers are to analyze and solve problems of this ID generation, I also will come before the way I used to compare and see what problems, which can get what inspired.

Distributed generation characteristic of the ID

Prior to the analysis, we first identify some of the characteristics generated service ID, on the basis of this feature, we can have a more profound understanding and perception of these types generate the following way.

  • Globally unique , which is a basic requirement, not duplicated.
  • Digital type, incremental trend behind the ID must be larger than the front, which is from the MySQL storage engine to consider the need to ensure that the write performance data.
  • Short length , can improve query performance, which is also from the MySQL database specification, especially ID as the primary key.
  • Information security , if the ID generated continuously, it is bound to disclose business information, and may even be guessed, so no need to rule irregular.
  • Availability low latency , fast generation ID, can be Kangzhu high concurrency, latency is low enough not to become a bottleneck service.

Several approaches generation of distributed ID

Here are a few ways to generate distributed ID I accumulated, the network can find, and I finishing late plus accumulated by learning to share their insights here. Although usually possible because of the small size of the project and we do not need, but this idea proposed scheme is still very worth learning, especially as the US group's Leaf program, I feel especially cool.

table of Contents:

  • Based UUID
  • Based on a database primary key increment
  • Multi-based database instance increment primary key
  • Snowflake algorithm based on class
  • Redis is generated based approach
  • Leaf scheme based on US group (ID segment, bis Buffer, dynamic adjustment of the Step)

Based UUID

It is easy to think of the program, after all, UUID globally unique characteristics of popular, however, anything familiar MySQL database features people should not use this as a business ID, it unreadable and too long, this is not a good idea, unless your system is small enough and do not pay attention to them, then the other said. Below we briefly summarize the advantages and disadvantages to use UUID as a business ID, and applicable in this way business scenarios.

advantage

  • Code implementation is simple enough to use.
  • Locally generated no performance problems.
  • Because with the unique characteristics of the world, so the problem does not exist for the database migration situation.

Shortcoming

  • Each generated ID is disorderly, and not all digital, and can not guarantee trend increase.
  • UUID generated string, a difference character string storage performance, slower query efficiency.
  • UUID is too long, not for storage, cost database performance.
  • ID no fixed business meaning, poor readability.

Applicable scene

  • The token can be used to generate a token category scene, not sufficiently recognizable and readable disorder is long enough.
  • No requirements may be used in pure numbers, random increment, non-scene-readability requirements.

Based on a database primary key increment

Database master key from the growing fashion is relatively common, and with MySQL, for example, specify the primary key in the new table to auto_increment way to grow automatically generate, or re-specified number of incremental steps, business systems in small-scale stand-alone deployment inside enough to use, simple to use and possess the necessary operational, but in highly concurrent systems distributed inside, but it is not applicable to distributed systems involving sub-library sub-table, across the room and even under cross-machine deployment environment, from database by the way can not meet business needs while in the case under high concurrent access to a large number of, the capacity of the database is limited, we simply display the advantages and disadvantages of this approach.

advantage

  • Simple, can rely on a database, small cost.
  • Digital ID, monotonous increment, and database storage to meet query performance.
  • It has some business readability.

Shortcoming

  • Strong dependence DB, the presence of a single point, if the database is down, the service is not available.
  • DB generation ID limited performance, a single pressure point database, can not carry high concurrency scenarios.

Applicable scene

  • Small-scale, data access small amounts of business scenarios.
  • No high concurrency scene, inserting a scene recorded controllable.

Multi-based database instance increment primary key

上面我们大致讲解了数据库主键自增的方式,讨论的时单机部署的情况,如果要以此提高ID生成的效率,可以横向扩展机器,平衡单点数据库的压力,这种方案如何实现呢?那就是在auto_increment的基础之上,设置step增长步长,让DB之前生成的ID趋势递增且不重复。

从上图可以看出,水平扩展的数据库集群,有利于解决数据库单点压力的问题,同时为了ID生成特性,将自增步长按照机器数量来设置,但是,这里有个缺点就是不能再扩容了,如果再扩容,ID就没法儿生成了,步长都用光了,那如果你要解决新增机器带来的问题,你或许可以将第三台机器的ID起始生成位置设定离现在的ID比较远的位置,同时把新的步长设置进去,同时修改旧机器上ID生成的步长,但必须在ID还没有增长到新增机器设置的开始自增ID值,否则就要出现重复了。

优点

  • 解决了ID生成的单点问题,同时平衡了负载。

缺点

  • 一定确定好步长,将对后续的扩容带来困难,而且单个数据库本身的压力还是大,无法满足高并发。

适用场景

  • 数据量不大,数据库不需要扩容的场景。

这种方案,除了难以适应大规模分布式和高并发的场景,普通的业务规模还是能够胜任的,所以这种方案还是值得积累。

基于类Snowflake算法

我们现在的项目都不大,使用的是IdWorker——国内开源的基于snowflake算法思想实现的一款分布式ID生成器,snowflake雪花算法是twitter公司内部分布式项目采用的ID生成算法,现在开源并流行了起来,下面是Snowflake算法的ID构成图。

这种方案巧妙地把64位分别划分成多段,分开表示时间戳差值、机器标识和随机序列,先以此生成一个64位地二进制正整数,然后再转换成十进制进行存储。

其中,1位标识符,不使用且标记为0;41位时间戳,用来存储时间戳的差值;10位机器码,可以标识1024个机器节点,如果机器分机房部署(IDC),这10位还可以拆分,比如5位表示机房ID,5位表示机器ID,这样就有32*32种组合,一般来说是足够了;最后的12位随即序列,用来记录毫秒内的计数,一个节点就能够生成4096个ID序号。所以综上所述,综合计算下来,理论上Snowflake算法方案的QPS大约为409.6w/s,性能足够强悍了,而且这种方式,能够确保集群中每个节点生成的ID都是不同的,且区间内递增。

优点

  • 每秒能够生成百万个不同的ID,性能佳。
  • 时间戳值在高位,中间是固定的机器码,自增的序列在地位,整个ID是趋势递增的。
  • 能够根据业务场景数据库节点布置灵活挑战bit位划分,灵活度高。

缺点

  • 强依赖于机器时钟,如果时钟回拨,会导致重复的ID生成,所以一般基于此的算法发现时钟回拨,都会抛异常处理,阻止ID生成,这可能导致服务不可用。

适用场景

  • 雪花算法有很明显的缺点就是时钟依赖,如果确保机器不存在时钟回拨情况的话,那使用这种方式生成分布式ID是可行的,当然小规模系统完全是能够使用的。

基于Redis生成办法

Redis的INCR命令能够将key中存储的数字值增一,得益于此操作的原子特性,我们能够巧妙地使用此来做分布式ID地生成方案,还可以配合其他如时间戳值、机器标识等联合使用。

优点

  • 有序递增,可读性强。
  • 能够满足一定性能。

缺点

  • 强依赖于Redis,可能存在单点问题。
  • 占用宽带,而且需要考虑网络延时等问题带来地性能冲击。

适用场景

  • 对性能要求不是太高,而且规模较小业务较轻的场景,而且Redis的运行情况有一定要求,注意网络问题和单点压力问题,如果是分布式情况,那考虑的问题就更多了,所以一帮情况下这种方式用的比较少。

Redis的方案其实可靠性有待考究,毕竟依赖于网络,延时故障或者宕机都可能导致服务不可用,这种风险是不得不考虑在系统设计内的。

基于美团的Leaf方案

从上面的几种分布式ID方案可以看出,能够解决一定问题,但是都有明显缺陷,为此,美团在数据库的方案基础上做了一个优化,提出了一个叫做Leaf-segment的数据库方案。

原方案我们每次获取ID都需要去读取一次数据库,这在高并发和大数据量的情况下很容易造成数据库的压力,那能不能一次性获取一批ID呢,这样就无需频繁的造访数据库了。

Leaf-segment的方案就是采用每次获取一个ID区间段的方式来解决,区间段用完之后再去数据库获取新的号段,这样一来可以大大减轻数据库的压力,那怎么做呢?

很简单,我们设计一张表如下:

+-------------+--------------+------+-----+-------------------+-----------------------------+
| Field       | Type         | Null | Key | Default           | Extra                       |
+-------------+--------------+------+-----+-------------------+-----------------------------+
| biz_tag     | varchar(128) | NO   | PRI |                   |                             |
| max_id      | bigint(20)   | NO   |     | 1                 |                             |
| step        | int(11)      | NO   |     | NULL              |                             |
| desc        | varchar(256) | YES  |     | NULL              |                             |
| update_time | timestamp    | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+-------------+--------------+------+-----+-------------------+-----------------------------+

其中biz_tag用来区分业务,max_id表示该biz_tag目前所被分配的ID号段的最大值,step表示每次分配的号段长度,后面的desc和update_time分别表示业务描述和上一次更新号段的时间。原来每次获取ID都要访问数据库,现在只需要把Step设置的足够合理如1000,那么现在可以在1000个ID用完之后再去访问数据库了,看起来真的很酷。

我们现在可以这样设计整个获取分布式ID的流程了:

  1. 用户服务在注册一个用户时,需要一个用户ID;会请求生成ID服务(是独立的应用)的接口
  2. 生成ID的服务会去查询数据库,找到user_tag的id,现在的max_id为0,step=1000
  3. 生成ID的服务把max_id和step返回给用户服务,并且把max_id更新为max_id = max_id + step,即更新为1000
  4. 用户服务获得max_id=0,step=1000;
  5. 这个用户服务可以用[max_id + 1,max_id+step]区间的ID,即为[1,1000]
  6. 用户服务把这个区间保存到jvm中
  7. 用户服务需要用到ID的时候,在区间[1,1000]中依次获取id,可采用AtomicLong中的getAndIncrement方法。
  8. 如果把区间的值用完了,再去请求生产ID的服务的接口,获取到max_id为1000,即可以用[max_id + 1,max_id+step]区间的ID,即为[1001,2000]

显而易见,这种方式很好的解决了数据库自增的问题,而且可以自定义max_id的起点,可以自定义步长,非常灵活易于扩容,于此同时,这种方式也很好的解决了数据库压力问题,而且ID号段是存储在JVM中的,性能获得极大的保障,可用性也过得去,即时数据库宕机了,因为JVM缓存的号段,系统也能够因此撑住一段时间。

优点

  • 扩张灵活,性能强能够撑起大部分业务场景。
  • ID号码是趋势递增的,满足数据库存储和查询性能要求。
  • 可用性高,即使ID生成服务器不可用,也能够使得业务在短时间内可用,为排查问题争取时间。
  • 可以自定义max_id的大小,方便业务迁移,方便机器横向扩张。

缺点

  • ID号码不够随机,完整的顺序递增可能带来安全问题。
  • DB宕机可能导致整个系统不可用,仍然存在这种风险,因为号段只能撑一段时间。
  • 可能存在分布式环境各节点同一时间争抢分配ID号段的情况,这可能导致并发问题而出现ID重复生成。

上面的缺点同样需要引起足够的重视,美团技术团队同样想出了一个妙招——双Buffer

正如上所述,既然可能存在多个节点同时请求ID区间的情况,那么避免这种情况就好了,Leaf-segment对此做了优化,将获取一个号段的方式优化成获取两个号段,在一个号段用完之后不用立马去更新号段,还有一个缓存号段备用,这样能够有效解决这种冲突问题,而且采用双buffer的方式,在当前号段消耗了10%的时候就去检查下一个号段有没有准备好,如果没有准备好就去更新下一个号段,当当前号段用完了就切换到下一个已经缓存好的号段去使用,同时在下一个号段消耗到10%的时候,又去检测下一个号段有没有准备好,如此往复。

下面简要梳理下流程:

  1. 当前获取ID在buffer1中,每次获取ID在buffer1中获取
  2. 当buffer1中的Id已经使用到了100,也就是达到区间的10%
  3. 达到了10%,先判断buffer2中有没有去获取过,如果没有就立即发起请求获取ID线程,此线程把获取到的ID,设置到buffer2中。
  4. 如果buffer1用完了,会自动切换到buffer2
  5. buffer2用到10%了,也会启动线程再次获取,设置到buffer1中
  6. 依次往返

双buffer的方案考虑的很完善,有单独的线程去观察下一个buffer何时去更新,两个buffer之间的切换使用也解决了临时去数据库更新号段可能引起的并发问题。这样的方式能够增加JVM中业务ID的可用性,而且建议segment的长度为业务高峰期QPS的100倍(经验值,具体可根据自己业务来设定),这样即使DB宕机了,业务ID的生成也能够维持相当长的时间,而且可以有效的兼容偶尔的网络抖动等问题。

优点

  • 基本的数据库问题都解决了,而且行之有效。
  • 基于JVM存储双buffer的号段,减少了数据库查询,减少了网络依赖,效率更高。

缺点

  • segment号段长度是固定的,业务量大时可能会频繁更新号段,因为原本分配的号段会一下子用完。
  • 如果号段长度设置的过长,但凡缓存中有号段没有消耗完,其他节点重新获取的号段与之前相比可能跨度会很大。

针对上面的缺点,美团有重新提出动态调整号段长度的方案。

动态调整Step

一般情况下,如果你的业务不会有明显的波峰波谷,可以不用太在意调整Step,因为平稳的业务量长期运行下来都基本上固定在一个步长之间,但是如果是像美团这样有明显的活动期,那么Step是要具备足够的弹性来适应业务量不同时间段内的暴增或者暴跌。

假设服务QPS为Q,号段长度为L,号段更新周期为T,那么Q * T = L。最开始L长度是固定的,导致随着Q的增长,T会越来越小。但是本方案本质的需求是希望T是固定的。那么如果L可以和Q正相关的话,T就可以趋近一个定值了。所以本方案每次更新号段的时候,会根据上一次更新号段的周期T和号段长度step,来决定下一次的号段长度nextStep,下面是一个简单的算法,意在说明动态更新的意思:

T < 15min,nextStep = step * 2
15min < T < 30min,nextStep = step
T > 30min,nextStep = step / 2

至此,满足了号段消耗稳定趋于某个时间区间的需求。当然,面对瞬时流量几十、几百倍的暴增,该种方案仍不能满足可以容忍数据库在一段时间不可用、系统仍能稳定运行的需求。因为本质上来讲,此方案虽然在DB层做了些容错方案,但是ID号段下发的方式,最终还是需要强依赖DB,最后,还是需要在数据库高可用上下足工夫。

参考资料

1、https://www.toutiao.com/i6682195317716156942/?group_id=6682195317716156942

2、https://www.toutiao.com/i6682672464708764174

3、https://tech.meituan.com/2017/04/21/mt-leaf.html

4、https://tech.meituan.com/2019/03/07/open-source-project-leaf.html

Guess you like

Origin www.cnblogs.com/captainad/p/10954331.html