我们在实际编程过程中会经常遇到需要用唯一ID的场合,这些唯一ID还会存到数据库中以便于我们将来进行查询。 例如用户编号、订单编号、客户编号等等,几乎凡是需要用来严格划分用户数据归属性的地方就需要用到唯一ID,否则A的数据到了B那,数据乱了整个系统也就算是毁了。 那么唯一ID该如何有效的生成就变成了一门学问了。 今天我们来讲讲在Python里生成唯一ID的几种方式,包括但不限于以下4种。 1.UUIDUUID应该是大家耳熟能详的一个东西了,它的全称叫 通用唯一识别码(英語:Universally Unique Identifier,缩写:UUID) import uuid
uid = uuid.uuid1()
print(uid)
print(uid.hex) 通过以上代码我们用Python生成来一个UUID字符串,用的是uuid1方法生成,默认会生成一个带减号(-)的字符串,我们可以通过hex数据拿到不带减号的版本,可以根据实际情况使用。 那么什么是uuid1呢?我们来看看Python关于UUID的源码。 篇幅有限我就不贴所有源码了,不过我们可以从源码注释里得知,uuid1这个方法主要是根据当前机器ID,一个随机序列号和时间戳生成的一个随机字符串。 如果是简单的应用,数据量也不大的情况,是可以用UUID来作为唯一编号的,不过在大型系统里,UUID其实不太适合作为主要数据的唯一ID,特别是用户ID,订单ID等等。 原因是我们在数据库里通常会对这些数据字段做了索引以便于系统进行频繁的查询,而在很大的数据体量里,一串32位长度的随机字符串作为索引就是一个灾难,我们一般不建议在非线性数据的字段上做索引,这样效率会非常低。 至于为什么字符串不适合做数据库索引我们以后再讲,在这里只需要知道UUID不适合做需要频繁查询的数据唯一ID就是了。 2:数据库主键自增ID前面说到UUID因为索引的问题不适合在海量数据里作为主要数据的唯一ID,那么数据库自带的主键ID是否能作为唯一ID使用呢? 什么是自增ID呢?它长成下面这样。 默认情况下在数据库每一个表里都有一个名为id的字段,它也被称之为主键ID,它的特点是从1开始,当每新增一条数据,id里的值就会自动加1,因为它的内容是int类型的且数据具有连续性,也适合用来作为索引,看起来我们找到了唯一ID的办法。 但是自增ID的问题在于以下两点:
综上所述,自增ID虽然能用,但也不是一个特别好的办法。 3: mongodb的ObjectId
在python里直接使用一个叫bson的第三方包即可,BSON是一种计算机数据交换格式,主要被用作MongoDB数据库中的数据存储和网络传输格式。
不过mongodb的ObjectId和uuid一样,还是存在相同的问题,这里仅作参考。
以上三种办法我们都试过了,或多或少都有一些缺陷,那么有没有更好的办法呢?既可以兼顾查询效率,也可以兼顾数据的唯一性。 让我们来把目光瞄向很多大厂,因为它们的服务每天都会产生海量的数据,那么我们看看它们是如何做的。 twitter(推特)前些年把自己的唯一ID生成算法开源了,也叫做雪花算法,取自(世界上没有一片相同的雪花)。 先看看雪花算法的基本原理。 snowflake算法核心思想上是用64位的二进制来表示数据。 其中41位的时间戳表示:当前时间戳减去某个设定的起始时间。 10位标识表示:不同的机器、数据库的标识ID等等,序列号为每秒或每毫秒内自增的id。 因为雪花算法最后生成出来的其实是一串唯一的数字,而非字符串,适合做数据库索引,看起来非常满足我们的需求,下面就来看看雪花算法在Python中的实现吧。 核心代码如下: def __init__(self, worker_id=0, data_center_id=0):
self.worker_id = worker_id
self.data_center_id = data_center_id
self.user_agent_parser = re.compile('^[a-zA-Z][a-zA-Z\-0-9]*$')
self.logger = logging.getLogger('idworker')
# stats
self.ids_generated = 0
# 2019.08.08 08:08:08 timestamp
self.twepoch = 1565222888000
self.sequence = 0
self.worker_id_bits = 5
self.data_center_id_bits = 5
self.max_worker_id = -1 ^ (-1 << self.worker_id_bits)
self.max_data_center_id = -1 ^ (-1 << self.data_center_id_bits)
self.sequence_bits = 12
self.worker_id_shift = self.sequence_bits
self.data_center_id_shift = self.sequence_bits self.worker_id_bits
self.timestamp_left_shift = self.sequence_bits self.worker_id_bits self.data_center_id_bits
self.sequence_mask = -1 ^ (-1 << self.sequence_bits)
self.last_timestamp = -1
def _time_gen(self):
return int(time.time() * 1000)
def _till_next_millis(self, last_timestamp):
timestamp = self._time_gen()
while last_timestamp <= timestamp:
timestamp = self._time_gen()
return timestamp
def _next_id(self):
timestamp = self._time_gen()
if self.last_timestamp > timestamp:
self.logger.warning('clock is moving backwards. Rejecting request until %i' % self.last_timestamp)
raise InvalidSystemClock(
'Clock moved backwards. Refusing to generate id for %i milliseocnds' % self.last_timestamp)
if self.last_timestamp == timestamp:
self.sequence = (self.sequence 1) & self.sequence_mask
if self.sequence == 0:
timestamp = self._till_next_millis(self.last_timestamp)
else:
self.sequence = 0
self.last_timestamp = timestamp
new_id = ((timestamp - self.twepoch) << self.timestamp_left_shift) | (
self.data_center_id << self.data_center_id_shift) | (self.worker_id << self.worker_id_shift) | self.sequence
self.ids_generated = 1
return new_id
通过以上例子我们可以发现,雪花算法第一是生成的数字类型的数据,第二数字之间是具有一定的连续性的,这样的优点在查询上非常具有效率。 以上介绍的4种生成唯一ID的方式各有千秋,在不同的应用场景下会发挥不同的用处,事实上在分布式系统里,还有很多各种各样的解决方案,在这里就不一一提供了。 我们先来总结以上四种方式的优缺点吧。 总结
关于如何生成唯一ID我们就介绍到这,在设计系统时根据实际情况选择最适合我们的方案,从个人的角度出发,有限推荐雪花算法的方案,理论上来说,它在一秒钟可以生成400多万个唯一ID,也就是说我们的业务数据并发量没有达到每秒400万次的情况下,这个方案都是安全的。 还有更多的唯一ID生成方式,欢迎大家回复,便于交流。 因篇幅有限,关于Python的雪花算法实现代码请私信我获取。 |
|