发号器的设计

数据库中的每条记录都需要一个ID,即使在分库分表后这个ID需要全局唯一性。因此,分库分表后不能使用Mysql自带的自增ID了。因为不通的库之间的ID可能是一样的。

我们以记录海量的用户信息为例,可能会想到身份证号、电话号码或者email。但是这些信息是会变的。如果用户要修改这些信息,那么ID就失效了。无异于新增一条记录,删掉原来的记录。

基于 Snowflake 算法搭建发号器

雪花算法可以提供全局唯一的ID。雪花算法生成的ID一般是分几段的,下图就是典型的ID组成:41位时间戳(一般是毫秒级?)+10位机器ID+12位序列号。

 

41位时间戳毫秒级的大概有69年,10位机器ID可以表示一个由1024台机器组成的发号器集群。12位序列号表示一毫秒每台机器可以提供4000个不通的ID,也就是一秒钟400W个。。。

为何不用UUID?

这是个好问题,我之前没考虑过。

确实,UUID是32个16进制组成的字符串,也具有全局唯一性。

那么为何不用呢。此处需要考虑业务ID需要哪些特性:1、全局唯一性(UUID符合)2、生成性能要好(UUID符合)3、写入性能要好 4、最好要有具体的业务含义,方便定位问题。

前两点UUID都符合,我们都知道Mysql对于随机写的TPS是很差的,对于顺序写性能很好。因为顺序写入节约了一次寻道时间。而写入是要按照ID排序的。UUID是随机的,不是递增的,所以如果用UUID作为mysql的ID是会严重影响写入性能的。

不用UUID作为mysql ID的另一个原因是UUID不具备业务解释性,比如我们之前设计的ID就是用类似于雪花算法生成的,时间戳+机器ID+其他一些业务信息,这样出现了badcase,第一时间拿到ID就知道是哪个方面出问题了。比如文章ID最后两位表示文章来源,那么如果badcase的ID都集中在某一个来源,那么直觉告诉我们某些特定业务出问题了,便于排查问题。

雪花算法如何落地?

大致有两种做法:

一、嵌入在代码里面,每台机器有自己的机器ID,这样就能生成唯一的ID了。这样做的好处是没有网络交互,性能好。不过如果机器众多或者机器重启,比较难保证每台机器ID是唯一的,这样就要引入ZK等组件来保证ID的一致性,这样就引入了新的zk问题。

二、第二种做法是,搭建一个发号器集群。这样做每次插入ID都会有一个额外的网络交互,但是对于内网来说,性能损失很小,这方面的损失是可以接受的。由于发号器性能很好,所以只需要少量机器就能提供大量的ID,QPS很高。这样可以减少机器数量,ID中用于表示机器ID的位数可以减少,提供的号就会变多。如果只有一台发号器,多个备份发号器,那么可以无需机器ID。一般情况下有少量的几台一起提供发号服务,此时由于机器较少,可以把机器ID写到配置文件中,也不会因为方法一带来的第三方组件问题。

可能引入的问题

雪花算法最大的问题就是他依赖于时间戳,如果时间不准了,那么就有可能导致产生重复的ID,此时需要调整时间,再发号。

还有一个可能引入的问题是如果QPS不高,那么以毫秒为粒度的ID可能分配不均匀,极端例子就是QPS为1,每秒的第一个毫秒产生一个ID,业务方拿到ID后根据时间戳去hash到每个库中,那么就会导致数据的不均匀。因此,很多实践过程中都是根据自己的需要进行变种的。可以把雪花算法前面的时间戳粒度变为秒,而不是毫秒。这样ID至于时间的分布就会相对均匀很多。

posted @ 2020-01-04 00:10  guhowo  阅读(759)  评论(0编辑  收藏  举报