UUID的学习与思考

我们在进行分布式系统相关开发的时候,经常需要ID号(例如,订单号,消息id号)。如果不是分布式系统的化,生成一个ID号是非常简单的,因为你自己知道自己生成的所有ID号,但是分布式系统环境下,你自己生成ID号的时候你是不知道其他人生成的ID号的,你再按原先的规则生成的ID号可能就与其他人生成的ID号重复了,这在很多时候是不行的。所以就有了UUID这个东西,其实就是分布式ID,让所有分布式节点各自生成ID而不与其他节点发生ID冲突。

UUID生成方法

常见的UUID有几种版本的生成方法,可参考A Universally Unique IDentifier (UUID) URN Namespace以及Universally_unique_identifier。比较常用的一种基于时间的生成方式是:<Time, MAC, Sequence>,也有基于哈希、随机数等方式。这种生成方式一个缺点就是生成的ID太长了,有的128bit或更长。生成的ID越长,一般意味着发生重复的概率更低,但在实际系统中,往往是ID越短越好。

snowflake算法

当我们要设计一个分布式ID算法时,如果要保证ID唯一性(不会发生重复),ID生成算法就必须有一定的历史记忆功能,知道以前生产过哪些ID,以便今后不再产生,即ID的值应该是一个单调递增(或单调递减)的过程。这样就能保证新产生的一定不是历史中产生过的ID。满足单调递增的可以是自增序列,但是自增序列有个问题是一旦重启,历史就消失了,所以自增序列可以放到整个id值得尾部,将时戳放到高位值得地方,这样重启后,时间增加,整个值能够保证递增。这是一种解决问题的思路,当然不是唯一的思路。snowflake算法基本就可以这么理解。

雪花算法(Snowflake)是twitter公司内部分布式项目采用的ID生成算法,共8字节正好一个long整型就可以表示了。
image
其中:最高位0表示是一个正数,41bit时间戳,以ms为单位可以表示math.pow(2,41)/(365*24*3600*1000)=69.7305700010147169年的时间,在使用时往往会设定一个基址时间,以这个基址时间为0开始时间的计算。10bit工作机器id可以表示1k多机器,大多数场景已足够。12bit序列号,每毫秒可产生4096个序列号,理论上性能上限为409.6w tps/s。

snowflake算法的最缺点就是唯一性严重依赖于时间,若发生时间回调,则可能会发生重复,此时唯一性依赖于最后的12bit序列号,如果回调时间内,12bit序列号没有发生重复,则安全,如果12bit序列号发生了重复,则不保证ID唯一。

解决时间这个问题,网络上有NTP时间同步的解决方案。局域网网络上利用NTP时间同步精度在1~50ms左右,精度为毫秒级,广域网可能在秒级。影响其精度最大的因素是网络因素,主要需要计算报文往返时间差,其最大的误差不超过网络延时。利用NTP虽然能同步时间,但是同步时间时很可能发生时间回调,这是我们不愿意看到的。

snowflake算法延伸

有没有办法解决snowflake算法中时间回调这一缺陷呢?有这么几种可能的解决办法,其中的一个解决办法其主要思想是如果时间发生回调,就更改机器workid,这个workid是单独一个集合,不与现有的wordid集合有交集。比如,现有机器500台,分配机器ID时全部分配为奇数ID(1,3,5,… , 499),如果某一机器ID时间发生回调,就新分配一个机器ID(如果现有ID是奇数就加1,如果是偶数就减1),这样,即时时间回调,新的机器ID仍能保证唯一性,缺点就是连续发生2次以上回调就无法保证唯一性了。

还可参考美团的解决方案Leaf

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章