最近对MongoDB生成的ObjectId产生兴趣,遂研究
先看源码中的官方解释:
* BSON格式的ObjectID有12byte,由 4byte 有符号·大端对齐的时间戳,5byte的唯一机器码和3byte无符号·大端对齐的自增数构成
* 4 字节时间戳 5 字节机器码 3 字节自增数
* |<----------------->|<---------------------->|<------------->
* OID 结构: [----|----|----|----|----|----|----|----|----|----|----|----]
* 0 4 8 12
我们知道,计算机的时间戳(精确到秒)由该时刻减去1970.1.1 0:00 GMT的秒数得来,共10位
而MongoDB源码的时间戳定义为:
// Timestamp is 4 bytes so we just use int32_t
typedef int32_t Timestamp;
奇怪的误解:时间戳是怎么压缩到4个字符串的?
感觉有点不对,遂百度,得出结论:
一个字节(Byte)占8位,用4位0-1代码才能表示一个16进制数(0000~ffff)
所以一个16进制符号占4位<=>一个字节可以用两个16进制符号表示
所以时间戳占4个字节<=>用8个字符串表示
进入正题
时间戳、进程码和自增数比较容易构造,机器码一般用主机名称的HASH值,现用主机名每个字符串的Unicode码之和替代
const os = require('os');
class OID {
constructor ( date = new Date() ){
return this.getTimestamp( date.getTime() )
+ this.getPid()
+ this.getMachineCode()
+ this.getIncrecement()
}
getTimestamp(timestamp = Date.now() ){
return Math.floor( timestamp/1000 ).toString(16)
}
getPid () {
return process.pid.toString(16).padStart(4,'0')
}
getMachineCode(){
[...os.hostname()].reduce((p,c)=>p+c.charCodeAt(),0).toString(16).padStart(6,'0')
}
getIncrecement () {
if(OID.increment>=0xffffff){
OID.increment = 0;
}
return (OID.increment++).toString(16).padStart(6,0)
}
}
OID.increment = 0;
// 测试性能 (0xffff 等于65535):
console.time(1)
for(let i = 0; i < 0xffff; i++){
new OID()
}
console.timeEnd(1)
module.exports = OID;
测试生成效率(每次生成65535个重复10次),结果如下
![](https://img.haomeiwen.com/i3132311/5b6bb079290227da.png)
0xfff也就是65535次。即每秒生成的ID数不到1万5千个,效率真心不尽人意,正常的ObjectId,通过自增数能保证每秒生成 0xffffff(16777215) 个不重复的id,差距有天壤之别
性能优化:
影响代码执行速度的瓶颈在于读取主机名和16进制转换,因为主机、进程id是不变的,只需要生成一次,只需要改变自增值和刷新时间戳就可以了
const os = require('os');
function OID (){
return OID.timestamp
+ OID.getIncrecement()
+ OID.pid
+ OID.MachineCode
}
OID.getIncrecement = function () {
if( OID.increment>=0xffffff){
OID.timestamp = Math.floor( Date.now()/1000 ).toString(16);
OID.increment = 0;
}
return (OID.increment++).toString(16).padStart(6,0)
}
OID.timestamp = Math.floor( Date.now()/1000 ).toString(16);
OID.MachineCode = [...os.hostname()].reduce((p,c)=>p+c.charCodeAt(),0).toString(16).padStart(6,'0');
OID.pid = process.pid.toString(16).padStart(4,'0')
OID.increment = 0;
for(let i = 0; i < 10; i++){
console.time(1)
for(let i = 0; i < 0xffff; i++){
OID()
}
console.timeEnd(1)
}
module.exports = OID;
emmm..同样是执行65535次,性能上有质的飞跃!
![](https://img.haomeiwen.com/i3132311/d2e7b85a9946bd23.png)
放在服务器上执行,将循环次数改为0xfffff(1048575)次
![](https://img.haomeiwen.com/i3132311/9a3626550e1644c0.png)
![](https://img.haomeiwen.com/i3132311/f77ac6e817cab441.png)
也就是说,每秒大约可以生成300万个id
关于时间刷新的问题
我并没有实时刷新时间,而是每次自增到0xffffff(16777215)次以后再刷新时间,因为自增号从0增加到16777215耗时早已超过了1s,如果对数据没有严格的时间要求,其实问题也不大
网友评论