美文网首页
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