SnowFlake 生成唯一订单id
Twitter-Snowflake算法产生的背景相当简单,为了满足Twitter每秒上万条消息的请求,每条消息都必须分配一条唯一的id,这些id还需要一些大致的顺序,让twitter可以通过一定的索引来进行检索,而在Twitter庞大的分布式系统中不同机器产生的id必须又必须不同。
它的好处显而易见,不仅全局唯一,并且有序按时间递增,同时占用空间少,生成的id仅仅是19位的整形数字,正好契合mysql的bigint数据类型,简直完美。
Snowflake的逻辑也非常简单,雪花算法生成64位的二进制正整数,然后转换成10进制的数。64位二进制数由如下部分组成:
- 第一个部分,是 1 个 bit:0,这个是无意义的
因为二进制里第一个 bit 为如果是 1,那么都是负数,但是我们生成的 id 都是正数,所以第一个 bit 统一都是 0。
- 第二个部分是 41 个 bit:表示的是时间戳。
41 bit 可以表示的数字多达 2^41 - 1,也就是可以标识 2 ^ 41 - 1 个毫秒值,换算成年就是表示 69 年的时间。
- 第三个部分是 5 个 bit:表示的是机房 id,10001。
5 个 bit 代表机房,id意思就是最多代表 2 ^ 5 个机房(32 个机房)。
- 第四个部分是 5 个 bit:表示的是机器 id,1 1001。
5 个 bit 代表机器 id,每个机房里可以代表 2 ^ 5 个机器(32 台机器)
- 第五个部分是 12 个 bit:表示的序号,就是某个机房某台机器上这一毫秒内同时生成的 id 的序号,0000 00000000。
12 bit 可以代表的最大正整数是 2 ^ 12 - 1 = 4096,也就是说可以用这个 12 bit 代表的数字来区分同一个毫秒内的 4096 个不同的 id。
# 首先安装库 pip3 install pysnowflake # 安装完成后,就可以在本地命令行启动snowflake服务 snowflake_start_server --worker=1 # 这里的worker就是当前节点的标识,此时编写代码就可以打印出当前客户端使用的snowflake的服务信息 import snowflake.client print(snowflake.client.get_stats()) {'dc': 0, 'worker': 1, 'timestamp': 1591871273195, 'last_timestamp':550281600000, 'sequence': 0, 'sequence_overload': 0, 'errors': 0} # 当然了,如果一台服务器上起了很多节点服务,也可以指定相关的节点进行装载
host = '127.0.0.1' port = 8910 snowflake.client.setup(host, port)
# 随后我们就可以根据该服务生成唯一id了 import snowflake.client print(snowflake.client.get_guid()) 4368750411956359169 # 可以看到这些id很明显带有递增的连续性,有的人会问了,假设我搭建了上千个节点的分布式系统,此时接口接到参数id,我怎么判断该id的订单信息存储在那个节点中呢? # 其实很容易就可以判断,从SnowFlake的算法结构入手,本身就是二进制转换十进制的整形,现在我们反着进行解析即可,这里以这个19位的id为例子:4368750411956359169 首先将其转换为二进制 print(bin(4368750411956359169)) 0b11110010100000111010110101101001100001000000000001000000000001
可以看到上文所述的第一位是标识符,此后是41位的时间戳,紧接着10位的节点标识码,最后12位的递增序列,从后面数12位是:000000000001,再数5位是:00001 这5位就是某个节点的存储标识,但是它目前是二进制,我们再将它转换为十进制
print(int('00001',2)) 1
可以看到,转换结果显示该id存储在节点1的数据库中,如此就具备了相当强的业务属性,通过反推逻辑我们可以快速准确的定位到数据的具体存储位置从而进行查询。
结语: 其实关于分布式唯一id的解决方案,也不仅仅只有uuid或者snowflake,像redis的incr原子性操作自增,亦或者Mongodb极具特色的_objectid的生成方式,专为分布式而设计的ID生成方案。都是可以参考的解决方案,但是方案总归是方案,总有其自身的特点和缺陷,这就需要根据实际应用场景而具体问题进行具体分析了。
# 生成订单编号 import snowflake.client def order_generate(uid): # 判断uid,选择worker if uid % 2 == 0: snowflake.client.setup('localhost', '9001') # 生成订单 order = snowflake.client.get_guid() # 反推获取worker bin_worker_num = bin(order)[-17:-12] worker_num = int(bin_worker_num, 2) return {'order': order, 'worker_num': worker_num} snowflake.client.setup('localhost', '9002') # 生成订单 order = snowflake.client.get_guid() # 反推获取worker bin_worker_num = bin(order)[-17:-12] worker_num = int(bin_worker_num, 2) return {'order': order, 'worker_num': worker_num} if __name__ == '__main__': print(order_generate(7))