不少地方咱们都须要一个全局惟一的编号,也就是uuid。举一个常见的场景,电商系统产生订单的时候,须要有一个对应的订单编号。在composer上咱们也能够看到有不少能够产生uuid的优秀组件。那么,为何咱们还要本身实现发号器,来产生uuid呢?想了一下,主要有两个缘由吧:php
一、我但愿uuid是可反解的,经过反解uuid能够得出和我业务相关的数据。而我看到的composer关于uuid的相关组件,生成的都是一串指定格式的字符串,我很难将它同具体的业务关联起来。html
二、我但愿经过uuid是能够随着并放量进行调整的。好比说原有支持1秒钟能够产生1000个uuid,但随着业务规模增加,我但愿变成能够支持1秒钟产生一万个。并且,最好改下配置就能够了。redis
出于以上两个缘由,咱们须要本身的发号器来产生uuid。那么,下一个问题是,咱们应该如何实现发号器,实现发号器的原理又是什么呢?算法
关于发号器的实现原理,可能你们都听过鼎鼎大名的snowflake算法 -- 雪花算法,Twitter的分布式自增Id算法。国内的新浪微博也有本身实现的发号器算法,具体实现细节虽有不一样,可是原理相通,明白其中一个便可。这里咱们主要介绍snowflake。swoole
关于snowflaw的介绍,已经有不少文章进行介绍,并且写的也很不错,我没有必要在重写一遍,拿来粘贴便可,出于对做者的尊重,我会将原文连接添加到参考连接中。网络
推特的分布式自增ID算法,使用long (8 × 8 = 64 byte)来保存uuid。其中1bit留给固定符号位0,41bit留给毫秒时间戳,10bit给MachineID,也就是机器要预先配置,剩下12位留Sequence(可支持1毫秒内4096个请求)。composer
也许有的人会问若是超过了1毫秒4096个请求怎么办?通常的作法是,让它等上1毫秒,促使41bit的时间戳变化。分布式
这里咱们将MachineId进行了拆分,5byte留给机器(最多能够支持32机器),5byte留给了业务号(最多可支持32种业务)ui
这里的时间戳保存的是当前时间与固定过去时间得一个差值,不是当前时间。这样的好处是能使用更长时间,并且不受年份限制,只取决于从何时开始用的,2^41 / 1000360024*365=69年。lua
若是保存的是当前时间戳,最多只能使用到2039年。2^41=2199023255552=2039/9/7 23:47:35
理论上单机速度:2^12*1000 = 4 096 000/s
经过对snowflake的初步了解,发现,其实发号器也是创建在时间戳基础之上的,由于时间是自然的惟一元素。可是,如何在单位时间内,好比说一秒钟或者一毫秒以内,保证Sequence持续递增才是发号器实现的关键。
这里咱们实现的方式比较简单,直接使用redis的incr进行计数,对应的key就是毫秒时间戳。出于redis内存回收的考虑,咱们须要将每个key设置过时时间。若是key是秒级别的时间戳,那么过时时间就是1秒 + 网络延迟;若是key毫秒级别的时间戳,那么过时时间就是1毫秒 + 网络延迟。
与此同时,为了保证执行incr,expire(pexpire)具备原子性,咱们使用lua来进行实现。
好了,实现的思路大体如此。因为能力和水平有限,不免会有纰漏,但愿及时指出。