美文网首页
MongoDB ObjectId 理解日记

MongoDB ObjectId 理解日记

作者: Kagashino | 来源:发表于2018-11-08 20:21 被阅读0次

最近对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次),结果如下


berore.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次,性能上有质的飞跃!


after.png

放在服务器上执行,将循环次数改为0xfffff(1048575)次


machine.png after2.png

也就是说,每秒大约可以生成300万个id

关于时间刷新的问题
我并没有实时刷新时间,而是每次自增到0xffffff(16777215)次以后再刷新时间,因为自增号从0增加到16777215耗时早已超过了1s,如果对数据没有严格的时间要求,其实问题也不大

相关文章

网友评论

      本文标题:MongoDB ObjectId 理解日记

      本文链接:https://www.haomeiwen.com/subject/hcebaftx.html