python中Mongodb的Objectid 实现
Mongodb 作为非关系型数据库,默认实现 了Objectid 作为索引,对数据进行排序。
ojbectid 组成
在 ObjectId 类中,说明了objectid 的组成:
An ObjectId is a 12-byte unique identifier consisting of: - a 4-byte value representing the seconds since the Unix epoch, - a 5-byte random value, - a 3-byte counter, starting with a random value.
在 objectid 的函数中可以同样得知objectid的组成:
def __generate(self):
"""Generate a new value for this ObjectId.
"""
# 4 bytes current time
oid = struct.pack(">I", int(time.time()))
# 5 bytes random
oid += ObjectId._random()
# 3 bytes inc
with ObjectId._inc_lock:
oid += struct.pack(">I", ObjectId._inc)[1:4]
ObjectId._inc = (ObjectId._inc + 1) % (_MAX_COUNTER_VALUE + 1)
self.__id = oid
这样我们就清楚了, o i d oid oid 由三部分组成
- 4 bytes, 当前的 UNIX 时间,这个时间是秒级精度的
- 5 bytes, 随机产生的字节
- 3 bytes, 计数器
oid组成如下图所示:
每部分含义
4字节时间
要理解 o i d oid oid 最开始的四个字节,我们需要理解 s t r u c t . p a c k ( ) struct.pack() struct.pack() 函数。
struct.pack
(format, v1, v2, …)返回一个 bytes 对象,其中包含根据格式字符串 format 打包的值 v1, v2, … 参数个数必须与格式字符串所要求的值完全匹配。
源代码中struct.pack(">I", int(time.time()))
,>
表示字节顺序为大端在先,I
表示为按照python类型的整数转换为C类型的unsigned int
.
5字节随机数
源码采用ObjectId._random()
来生成五字节的随机数,其对每一个进程生成一个 5 字节的随机数。
def _random(cls):
"""Generate a 5-byte random number once per process.
"""
pid = os.getpid()
if pid != cls._pid:
cls._pid = pid
cls.__random = _random_bytes()
return cls.__random
_pid = os.getpid()
__random = _random_bytes()
...
def _random_bytes():
"""Get the 5-byte random field of an ObjectId."""
return os.urandom(5)
3 字节计数器
with ObjectId._inc_lock:
oid += struct.pack(">I", ObjectId._inc)[1:4]
ObjectId._inc = (ObjectId._inc + 1) % (_MAX_COUNTER_VALUE + 1)
...
_MAX_COUNTER_VALUE = 0xFFFFFF
源码中可以得知,oid
和_inc
pack后的后三个字节有关,由于是大端模式,那么就是和_inc
的低位字节有关。然后再更改 _inc 的值。从上面也可以得出 _inc
的取值范围是[0,0xFFFFFF]
,即[0,16777215]
。
那最初的_inc
是怎么生成的呢,_inc = SystemRandom().randint(0, _MAX_COUNTER_VALUE)
,也是一个[0,16777215]
的随机数了。
小结
以上主要讲解了PyMongo 中对于 ObjectId 的实现,如果有什么问题,可以在评论区留言交流。