百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

MongoDB进阶-01 ObjectID

toyiye 2024-06-21 12:32 9 浏览 0 评论

Mongo中ObjectId是一个12字节的BSON类型字符串。ObjectId是小的,接近唯一且快速生成的有序的id 。

 *               4 byte timestamp    5 byte process unique   3 byte counter
 *             |<----------------->|<---------------------->|<------------->
 * OID layout: [----|----|----|----|----|----|----|----|----|----|----|----]
 *             0                   4                   8                   12

3.2版本之前包括3.2

  1. 4字节 UNIX时间戳
  2. 3字节 机器识别码
  3. 2字节 进程id
  4. 3字节 随机数开始的计数器

objectid

3.4版本之后包含3.4

  • 4字节 UNIX时间戳
  • 5字节 随机数
  • 3字节 随机数开始的计数器

为什么会更新

引用自Mongo ObjectId 早就不用机器标识和进程号了 | Wolfogre's Blog https://blog.wolfogre.com/posts/mongo-objectid-design/


mongo官方没有给出详细解释,从网上看到有人的主观猜想。mongo 的 C++ 源码中,设置 ObjectId 中间 5 个字节的函数叫 setInstanceUnique,而在官方 golang 驱动中叫 processUnique。字面意思相近,都是说明这个值的作用是“区分不同进程实例”。而这个值具体怎么实现并没有什么要求,所以,使用“机器标识+进程号”来拿区分不同进程实例是可以的,使用互无关联的随机数来拿区分不同进程实例也是可以的。

可想而知,“在同一秒内,两个进程实例产生了相同的 5 字节随机数,且刚巧这时候两个进程的自增计数器的值也是相同的”——这种情况发生的概率实在太低了,完全可以认为不可能发生,所以使用互无关联的随机数来拿区分不同进程实例是完全合乎需求的。

为什么不继续使用“机器标识+进程号”呢?

机器标识码,ObjectId 的机器标识码是取系统 hostname 哈希值的前几位,问题来了,想必在座的各位都有干过吧:准备了几台虚拟机,hostname 都是默认的 localhost,谁都想着这玩意儿能有什么用,还得刻意给不同机器起不同的 hostname?此外,hostname 在容器、云主机里一般默认就是随机数,也不会检查同一集群里是否有 hostname 重名。

进程号,这个问题就更大了,要知道,容器内的进程拥有自己独立的进程空间,在这个空间里只用它自己这一个进程(以及它的子进程),所以它的进程号永远都是 1。也就是说,如果某个服务(既可以是 mongo 实例也可以是 mongo 客户端)是使用容器部署的,无论部署多少个实例,在这个服务上生成的 ObjectId,第八第九个字节恒为 0000 0001,相当于说这两个字节废了。


mongo server实现

#### mongo 源码中src\mongo\bson\oid.h

mongo::OID __oid() const {
        return OID::from(value());
}

class OID {
public:
    /**
     * Functor compatible with std::hash for std::unordered_{map,set}
     * Warning: The hash function is subject to change. Do not use in cases where hashes need
     *          to be consistent across versions.
     */
    struct Hasher {
        size_t operator()(const OID& oid) const;
    };

    OID() : _data() {}

    enum { kOIDSize = 12, kTimestampSize = 4, kInstanceUniqueSize = 5, kIncrementSize = 3 };
    explicit OID(const std::string& s) {
        init(s);
    }
    
}


void OID::init() {
    // each set* method handles endianness
    setTimestamp(time(0));  // time(0)当前时间 精确到s
    setInstanceUnique(_instanceUnique);
    setIncrement(Increment::next());
}

void OID::setTimestamp(const OID::Timestamp timestamp) {
    _view().write<BigEndian<Timestamp>>(timestamp, kTimestampOffset);
}

void OID::setInstanceUnique(const OID::InstanceUnique unique) {
    // Byte order doesn't matter here
    _view().write<InstanceUnique>(unique, kInstanceUniqueOffset);
}


void OID::setIncrement(const OID::Increment inc) {
    _view().write<Increment>(inc, kIncrementOffset);
}


void OID::regenMachineId() {
    std::unique_ptr<SecureRandom> entropy(SecureRandom::create());
    _instanceUnique = InstanceUnique::generate(*entropy);
}


c driver实现

// c driver

//bson-context.c
 
struct _bson_context_t {
   /* flags are defined in bson_context_flags_t */
   int flags;
   int32_t seq32;  // 4字节  取后三字节作为递增的随机数
   int64_t seq64;  // 8字节  后面的8字节,随机数5字节+3字节递增书
   uint8_t rand[5]; // 5字节随机值
   uint16_t pid;  // 2字节进程id

   void (*oid_set_seq32) (bson_context_t *context, bson_oid_t *oid); // 函数
   void (*oid_set_seq64) (bson_context_t *context, bson_oid_t *oid);  // 函数

   /* this function pointer allows us to mock gethostname for testing. */
   void (*gethostname) (char *out);
};


void
_bson_context_set_oid_rand (bson_context_t *context, bson_oid_t *oid)
{
   BSON_ASSERT (context);
   BSON_ASSERT (oid);

   if (context->flags & BSON_CONTEXT_DISABLE_PID_CACHE) {
      uint16_t pid = _bson_getpid ();

      if (pid != context->pid) {
         context->pid = pid;
         /* randomize the random bytes, not the sequence. */
         _bson_context_init_random (context, false);
      }
   }
   memcpy (&oid->bytes[4], &context->rand, sizeof (context->rand)); // 设置4字节后的5个随机数字节
}


static void
_bson_context_set_oid_seq32_threadsafe (bson_context_t *context, /* IN */
                                        bson_oid_t *oid)         /* OUT */
{
   int32_t seq = bson_atomic_int_add (&context->seq32, 1);

   seq = BSON_UINT32_TO_BE (seq);
   memcpy (&oid->bytes[9], ((uint8_t *) &seq) + 1, 3);
}



static void
_bson_context_set_oid_seq64_threadsafe (bson_context_t *context, /* IN */
                                        bson_oid_t *oid)         /* OUT */
{
   int64_t seq = bson_atomic_int64_add (&context->seq64, 1);  // 原子操作

   seq = BSON_UINT64_TO_BE (seq);
   memcpy (&oid->bytes[4], &seq, sizeof (seq));
}


static void
_bson_context_init (bson_context_t *context, bson_context_flags_t flags)
{
   context->flags = (int) flags;
   context->oid_set_seq32 = _bson_context_set_oid_seq32;
   context->oid_set_seq64 = _bson_context_set_oid_seq64;
   context->gethostname = _bson_context_get_hostname;

   if ((flags & BSON_CONTEXT_THREAD_SAFE)) {
      context->oid_set_seq32 = _bson_context_set_oid_seq32_threadsafe;  // 线程安全的设置函数
      context->oid_set_seq64 = _bson_context_set_oid_seq64_threadsafe;
   }

   context->pid = _bson_getpid ();
   _bson_context_init_random (context, true);
}


static BSON_ONCE_FUN (_bson_context_init_default)
{
   _bson_context_init (
      &gContextDefault,
      (BSON_CONTEXT_THREAD_SAFE | BSON_CONTEXT_DISABLE_PID_CACHE));
   BSON_ONCE_RETURN;
}

bson_context_t *
bson_context_get_default (void)
{
   // 构造默认的context
   static bson_once_t once = BSON_ONCE_INIT;

   bson_once (&once, _bson_context_init_default);

   return &gContextDefault;
}


# oid初始化
void
bson_oid_init (bson_oid_t *oid,         /* OUT */
               bson_context_t *context) /* IN */
{
   uint32_t now = (uint32_t) (time (NULL));  // 4字节的时间戳

   BSON_ASSERT (oid);

   if (!context) {
       // 使用默认的context
      context = bson_context_get_default ();
   }

   now = BSON_UINT32_TO_BE (now);
   memcpy (&oid->bytes[0], &now, sizeof (now)); // 前四个自己赋值在这

   _bson_context_set_oid_rand (context, oid);  // 中间5字节的随机数赋值
   context->oid_set_seq32 (context, oid); // 后3个字节赋值
}

cxx driver实现

// C++的实现依赖C的实现,依赖C实现的库在基础上进行了C++的封装

///
/// A BSON ObjectId value.
///
struct BSONCXX_API b_oid {
    static constexpr auto type_id = type::k_oid;

    oid value;
};

oid::oid() {
    bson_oid_t oid;
    bson_oid_init(&oid, nullptr);

    std::memcpy(_bytes.data(), oid.bytes, sizeof(oid.bytes));
}

go driver 实现

//mongo-tools\vendor\gopkg.in\mgo.v2\bson\bson.go
// 暂未更新,比较旧的实现
// NewObjectId returns a new unique ObjectId.
func NewObjectId() ObjectId {
	var b [12]byte
	// Timestamp, 4 bytes, big endian  # 时间戳
	binary.BigEndian.PutUint32(b[:], uint32(time.Now().Unix()))
	// Machine, first 3 bytes of md5(hostname)
    // 机器码
	b[4] = machineId[0]
	b[5] = machineId[1]
	b[6] = machineId[2]
	// Pid, 2 bytes, specs don't specify endianness, but we use big endian.
    // 进程id
	b[7] = byte(processId >> 8)
	b[8] = byte(processId)
	// Increment, 3 bytes, big endian
    // 自增数
	i := atomic.AddUint32(&objectIdCounter, 1)
	b[9] = byte(i >> 16)
	b[10] = byte(i >> 8)
	b[11] = byte(i)
	return ObjectId(b[:])
}



// 新版本

var objectIDCounter = readRandomUint32()
var processUnique = processUniqueBytes()

// New generates a new ObjectID.
func New() ObjectID {
	var b [12]byte

	binary.BigEndian.PutUint32(b[0:4], uint32(time.Now().Unix()))
	copy(b[4:9], processUnique[:])
	putUint24(b[9:12], atomic.AddUint32(&objectIDCounter, 1))

	return b
}

// ……

func processUniqueBytes() [5]byte {
	var b [5]byte
	_, err := io.ReadFull(rand.Reader, b[:])
	if err != nil {
		panic(fmt.Errorf("cannot initialize objectid package with crypto.rand.Reader: %v", err))
	}

	return b
}

java driver实现

// mongo-java-driver-r4.1.0

// ObjectId.java
"objectId" -> new BsonObjectId(new ObjectId());


// Use primitives to represent the 5-byte random value.
private static final int RANDOM_VALUE1;
private static final short RANDOM_VALUE2;
private final int timestamp;
private final int counter;
private final int randomValue1;
private final short randomValue2;


public ObjectId() {
    this(new Date());
}
public ObjectId(final Date date) {
    this(dateToTimestampSeconds(date), NEXT_COUNTER.getAndIncrement() & LOW_ORDER_THREE_BYTES, false);
}

private ObjectId(final int timestamp, final int counter, final boolean checkCounter) {
    this(timestamp, RANDOM_VALUE1, RANDOM_VALUE2, counter, checkCounter);
}

private ObjectId(final int timestamp, final int randomValue1, final short randomValue2, final int counter,
                 final boolean checkCounter) {
    if ((randomValue1 & 0xff000000) != 0) {
        throw new IllegalArgumentException("The machine identifier must be between 0 and 16777215 (it must fit in three bytes).");
    }
    if (checkCounter && ((counter & 0xff000000) != 0)) {
        throw new IllegalArgumentException("The counter must be between 0 and 16777215 (it must fit in three bytes).");
    }
    this.timestamp = timestamp;
    this.counter = counter & LOW_ORDER_THREE_BYTES;
    this.randomValue1 = randomValue1;
    this.randomValue2 = randomValue2;
}


// 初始化RANDOM_VALUE1 这里不再采用机器编号+进程编号的方式了
static {
    try {
        SecureRandom secureRandom = new SecureRandom();
        RANDOM_VALUE1 = secureRandom.nextInt(0x01000000); // 随机产生一个大于等于0不等于0x01000000(不包含0x01000000)的正整数
        RANDOM_VALUE2 = (short) secureRandom.nextInt(0x00008000);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

// 可以看到Java对ObjectId的封装后数据所占用的空间14个字节
// 对齐后占用16字节


uuid简单实现版

import java.util.Date;

class MyUUID
{
    private static int _machineID=0;
    private static int _incID=0;

    private static int getMachineID() {
        //复杂些的可以通过主机主板序列号、IP、硬盘序列号等生成机器码,简单起见默认为1
        _machineID+=1;
        return _machineID%127;
    }

    private static int getInc()
    {
        //一般使用Memcached统一维护一个全局自增ID,简单起见默认为1
        _incID+=1;
        return _incID;
    }

    public static long ObjectID() {  
        long v_time = (int) (System.currentTimeMillis() / 1000);
        int v_machine = getMachineID();
        int v_inc = getInc();

        long myUUID=0;
        myUUID=myUUID | v_inc;
        myUUID=myUUID | (v_machine<<24);
        myUUID=myUUID | (v_time<<32);

        return myUUID;
    }

    public static void reverseObjectID(long myUUID) 
    {
        //反向解析myUUID
        int v_inc =(int) myUUID & 0xFFFFFF;//取出最右边3字节(即24bit)的值,每个16进制数存储4bit
        int v_machine=(int) (myUUID >>> 24) & 0xFF;//无符号右移24位,然后取最右边1字节的值
        long v_time=(myUUID >>>32) & 0xFFFFFFFF;//无符号右移32位,然后取四字节的秒数

        System.out.println("v_time="+Long.toHexString(v_time));
        System.out.println("时间为:"+new java.util.Date(v_time*1000));
        System.out.println("v_machine="+v_machine);
        System.out.println("inc="+v_inc);
    }

    public static void main(String[] args) 
    {
        long objectid;
        long id1=0,id2=0;

        //生成100000个MyUUID
        for (int i=0; i<100000; i++)
        {
            objectid=ObjectID();
            System.out.println("MyUUID="+Long.toHexString(objectid));

            if (i==0)
                id1=objectid;
            else if (i==99999)
                id2=objectid;
            else;
        }

        System.out.println("反向解析MyUUID...................");
        reverseObjectID(id1);
        reverseObjectID(id2);
    }
}
# -*- coding: utf-8 -*-

import os
import hashlib
import socket
import random
import threading
import struct
import time
import binascii

def _machine_bytes():
    """Get the machine portion of an ObjectId.
    """
    machine_hash = hashlib.md5()
    machine_hash.update(socket.gethostname())
    return machine_hash.digest()[0:3]


_machine_bytes = _machine_bytes()

_inc_lock = threading.Lock()
_inc = random.randint(0, 0xFFFFFF)


oid = struct.pack(">i", int(time.time()))  # 4字节当前时间
oid += _machine_bytes  # 3字节机器码
oid += struct.pack(">H", os.getpid() % 0xFFFF)  # 2字节pid

with _inc_lock:
	oid += struct.pack(">i", _inc)[1:4]  # 自增数加锁保证线程安全
	_inc = (_inc + 1) % 0xFFFFFF
print binascii.hexlify(oid)

转换

mongodb的每一个document都有一个_id字段,是Mongo的ObjectId对象。ObjectId是一个12字节的BSON对象,但是在mongoshell中显示的时候字符串长度为24,这个字符串是一个16进制的数字,做所周知长度24的16进制的数字是12个字节。

mongo shell objectid


在一般游戏开发中不会直接用ObjectId的16进制值来表示玩家的id,类似XbZceckLMEuRO3Tx,entity_id是一个长度为16字节的字符串。

entity_id -> base64 decode -> hex encode -> ObjectId
ObjectId -> hex decode -> base64 encode -> entity_id


# 转换代码
def entity_id2objectid(entity_id):
    return base64.b64decode(entity_id).encode("hex")

def objectid2entity_id(oid):
    return base64.b64encode(oid.decode("hex"))

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码