前段时间有个朋友问我,分布式主键生成策略在我们这边是怎么实现的,当时我给的答案是sequence,当然这在不高并发的情况下是没有任何问题,实际上,我们的主键生成是可控的,但如果是在分布式高并发的情况下,那肯定是有问题的。
突
然想起mongodb的objectid,记得以前看过文档,objectid是一种轻量型的,不同的机器都能用全局唯一的同种方法轻量的生成它,而不是
采用传统的自增的主键策略,因为在多台服务器上同步自动增加主键既费力又费时,不得不佩服,mongodb从开始设计就被定义为分布式数据库。
下面深入一点来翻翻这个Objectid的底细,在mongodb集合中的每个document中都必须有一个"_id"建,这个键的值可以是任何类型的,在默认的情况下是个Objectid对象。
当我们让一个collection中插入一条不带_id的记录,系统会自动地生成一个_id的key
> db.t_test.insert({"name":"cyz"})
> db.t_test.findOne({"name":"cyz"})
{ "_id" : ObjectId("4df2dcec2cdcd20936a8b817"), "name" : "cyz" }
可以发现这里多出一个Objectid类型的_id,当然了,这个_id是系统默认生成的,你也可以为其指定一个值,不过在同一collections中该值必须是唯一的
把 ObjectId("4df2dcec2cdcd20936a8b817")这串值拿出来并对照官网的解析来深入分析。
"4df2dcec2cdcd20936a8b817"
以这段字符串为例来进行解析,这是一个24位的字符串,看起来很长,很难理解,实际上它是由ObjectId(string)所创建的一组十六进制的字
符,每个字节两位的十六进制数字,总共使用了12字节的存储空间,可能有些朋友会感到很奇怪,居然是用了12个字节,而mysql的INT类型也只有4个
字节,不过按照现在的存储设备,多出来的这点字节也应该不会成为什么瓶颈,实际上,mongodb在设计上无处不在的体现着用空间换时间的思想,接下看吧
下面是官网指定Bson中ObjectId的详细规范
TimeStamp
前
4位是一个unix的时间戳,是一个int类别,我们将上面的例子中的objectid的前4位进行提取“4df2dcec”,然后再将他们安装十六进制
专为十进制:“1307761900”,这个数字就是一个时间戳,为了让效果更佳明显,我们将这个时间戳转换成我们习惯的时间格式
$ date -d '1970-01-01 UTC 1307761900 sec' -u
2011年 06月 11日 星期六 03:11:40 UTC
前
4个字节其实隐藏了文档创建的时间,并且时间戳处在于字符的最前面,这就意味着ObjectId大致会按照插入进行排序,这对于某些方面起到很大作用,如
作为索引提高搜索效率等等。使用时间戳还有一个好处是,某些客户端驱动可以通过ObjectId解析出该记录是何时插入的,这也解答了我们平时快速连续创
建多个Objectid时,会发现前几位数字很少发现变化的现实,因为使用的是当前时间,很多用户担心要对服务器进行时间同步,其实这个时间戳的真实值并
不重要,只要其总不停增加就好。
Machine
接下来的三个字节,就是 2cdcd2 ,这三个字节是所在主机的唯一标识符,一般是机器主机名的散列值,这样就确保了不同主机生成不同的机器hash值,确保在分布式中不造成冲突,这也就是在同一台机器生成的objectid中间的字符串都是一模一样的原因。
pid
上面的Machine是为了确保在不同机器产生的objectid不冲突,而pid就是为了在同一台机器不同的mongodb进程产生了objectid不冲突,接下来的0936两位就是产生objectid的进程标识符。
increment
前面的九个字节是保证了一秒内不同机器不同进程生成objectid不冲突,这后面的三个字节a8b817,是一个自动增加的计数器,用来确保在同一秒内产生的objectid也不会发现冲突,允许256的3次方等于16777216条记录的唯一性。
客户端生成
mongodb产生objectid还有一个更大的优势,就是mongodb可以通过自身的服务来产生objectid,也可以通过客户端的驱动程序来产生,如果你仔细看文档你会感叹,mongodb的设计无处不在的使
用空间换时间的思想,比较objectid是轻量级,但服务端产生也必须开销时间,所以能从服务器转移到客户端驱动程序完成的就尽量的转移,必须将事务扔给客户端来完成,减低服务端的开销,另还有一点原因就是扩展应用层比扩展数据库层要变量得多。
好吧,既然我们了解到我们的程序产生objectid是在客户端完成,那再继续,进一步了解,打开
mongodb java driver源码
,无源码可以到mongodb官网进行下载,下面摘录部分代码
public
class
ObjectId
implements
Comparable
<
ObjectId
>
, java.io.Serializable {
final
int
_time;
final
int
_machine;
final
int
_inc;
public
ObjectId(
byte
[] b ){
if
( b.length
!=
12
)
throw
new
IllegalArgumentException(
"
need 12 bytes
"
);
ByteBuffer bb
=
ByteBuffer.wrap( b );
_time
=
bb.getInt();
_machine
=
bb.getInt();
_inc
=
bb.getInt();
_new
=
false
;
}
public
ObjectId(
int
time ,
int
machine ,
int
inc ){
_time
=
time;
_machine
=
machine;
_inc
=
inc;
_new
=
false
;
}
public
ObjectId(){
_time
=
(
int
) (System.currentTimeMillis()
/
1000
);
_machine
=
_genmachine;
_inc
=
_nextInc.getAndIncrement();
_new
=
true
;
}
(完整代码请查看源码)
这里可以发现ObjectId的构建可以有多种方式,可以由自己制定字节,也可以指定时间,机器码和自增值,这里重点看看驱动程序默认的构建,也就是public ObjectId()
可以看到objectid主要由_time _machine _inc 所组成,其中 _time直接由(System.currentTimeMillis() / 1000)计算出所谓的时间戳,这里很简单,接下来是重点,主要看看机器码和进程码
的构建
private
static
final
int
_genmachine;
static
{
try
{
final
int
machinePiece;
//
机器码块
{
StringBuilder sb
=
new
StringBuilder();
Enumeration
<
NetworkInterface
>
e
=
NetworkInterface.getNetworkInterfaces();
//
NetworkInterface此类表示一个由名称和分配给此接口的 IP 地址列表组成的网络接口,它用于标识将多播组加入的本地接口,这里通过NetworkInterface此机器上所有的接口
while
( e.hasMoreElements() ){
NetworkInterface ni
=
e.nextElement();
sb.append( ni.toString() );
}
machinePiece
=
sb.toString().hashCode()
<<
16
;
//
将得到所有接口的字符串进行取散列值
LOGGER.fine(
"
machine piece post:
"
+
Integer.toHexString( machinePiece ) );
}
final
int
processPiece;
//
进程块
{
int
processId
=
new
java.util.Random().nextInt();
try
{
processId
=
java.lang.management.ManagementFactory.getRuntimeMXBean().getName().hashCode();
//
RuntimeMXBean是Java虚拟机的运行时系统的管理接口,这里是返回表示正在运行的 Java 虚拟机的名称,并进行取散列值。
}
catch
( Throwable t ){
}
ClassLoader loader
=
ObjectId.
class
.getClassLoader();
int
loaderId
=
loader
!=
null
?
System.identityHashCode(loader) :
0
;
StringBuilder sb
=
new
StringBuilder();
sb.append(Integer.toHexString(processId));
sb.append(Integer.toHexString(loaderId));
processPiece
=
sb.toString().hashCode()
&
0xFFFF
;
LOGGER.fine(
"
process piece:
"
+
Integer.toHexString( processPiece ) );
}
_genmachine
=
machinePiece
|
processPiece;
//
最后将机器码块的散列值与进程块的散列值进行位或运算,得到 _genmachine
LOGGER.fine(
"
machine :
"
+
Integer.toHexString( _genmachine ) );
}
catch
( java.io.IOException ioe ){
throw
new
RuntimeException( ioe );
}
}
Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
while ( e.hasMoreElements() ){
NetworkInterface ni = e.nextElement();
sb.append( ni.toString() );
}
machinePiece = sb.toString().hashCode() << 16;
这里的NetworkInterface.getNetworkInterfaces();取得的接口通常是按名称(如 "le0")区分的,大约是下面的类型
name:lo (Software Loopback Interface
1
) index:
1
addresses:
/
0
:
0
:
0
:
0
:
0
:
0
:
0
:
1
;
/
127.0
.
0.1
;
name:net0 (WAN Miniport (SSTP)) index:
2
addresses:
name:net1 (WAN Miniport (IKEv2)) index:
3
addresses:
name:net2 (WAN Miniport (L2TP)) index:
4
addresses:
name:net3 (WAN Miniport (PPTP)) index:
5
addresses:
name:ppp0 (WAN Miniport (PPPOE)) index:
6
addresses:
这里为什么要采取这样方面进行取散列值,感觉有些不太理解,应该网络接口本身相对而言是并不稳定的
int processId = new java.util.Random().nextInt();
try {
processId = java.lang.management.ManagementFactory.getRuntimeMXBean().getName().hashCode();
}
catch ( Throwable t ){
}
RuntimeMXBean是Java虚拟机的运行时系统的管理接口,这里是返回表示正在运行的 Java 虚拟机的名称,并进行取散列值,如果在这过程中出现异常,processId 将以随机数的方式继续计算
_genmachine = machinePiece | processPiece;
最后将机器码块的散列值与进程块的散列值进行位或运算,当然这里是十进制,你把这里的十进制专为十六进制,就会发现这块的值就是生产objectid中间部分的值,这里的构建跟服务端的构建是有些不一样的,不过最基本的构建元素还是一致的,就是TimeStamp,Machine ,pid,increment
。
mongodb的ObejctId生产思想在很多方面挺值得我们借鉴的,特别是在大型分布式的开发,如何构建轻量级的生产,如何将生产的负载进行转移,如何以空间换取时间提高生产的最大优化等等。
----------------------------------------
分享到:
相关推荐
MongoDB ObjectId是一个重要的概念,它是MongoDB数据库中用于唯一标识文档的一个特殊数据类型。ObjectId作为一个12字节的BSON类型数据,其结构包含了多种信息,以确保文档的唯一性和可追溯性。以下是对ObjectId的...
MongoDB 的 ObjectId 是一个关键的数据类型,它在数据库系统中起着至关重要的作用,特别是在文档数据库如 MongoDB 中。ObjectId 是一个 12 字节的唯一标识符,用于确保每条文档都有其独一无二的身份。在深入讨论 ...
在Java中与MongoDB进行交互时,经常需要查询具有特定`ObjectId`的文档。`ObjectId`是MongoDB中用于唯一标识文档的一种数据类型,通常作为每个文档的默认 `_id` 字段。本示例将详细解释如何使用Java来查询具有指定`...
一个用于创建非常难以猜测的 MongoDB ObjectID 的 NodeJS 模块 安装 安装很容易。 只需发出 NPM 安装命令: $ npm install mongo-roid 用法 使用几乎和安装一样简单。 var randomOID = require ( 'mongo-roid' ...
在MongoDB中,每个文档都有一个默认的主键字段`_id`,其类型为ObjectId。ObjectId是一个12字节的二进制结构,通常用于唯一标识文档。在本文中,我们将深入探讨如何从ObjectId中提取时间信息。 ObjectId的结构如下:...
MongoDB中的每个文档都有一个唯一的标识符,这就是`_id`字段,它默认是`ObjectId`类型。`ObjectId`是由12字节(96位)组成的,其中前4个字节表示创建该`ObjectId`的秒数,接下来3个字节是机器标识符,接着两个字节是...
由于几乎所有表都对另一个表有某种引用,因此我们需要在增量 Postgres-ID 和新的 MongoDB ObjectId 之间进行映射,以方便更新。 我们的小 NodeJS 后台任务来了! 它使用 Postgres 内置的触发/侦听功能侦听 ...
@ meanie / joi-object-id Joi扩展程序可将ISO日期字符串自动转换为object-id对象特征扩展现有的string验证器将传入值验证为MongoDB ObjectId值自动将值转换为Mongoose ObjectId实例安装# npmnpm install @meanie/...
阿尔弗雷德工作流程对象这个Alfred Workflow插件可以解析MongoDB ObjectId对象,并从中提取一些基本信息和重要信息,例如生成时间戳,机器哈希等。 注意:目前仅支持生成时提取。 即将发布的版本将支持其他ObjectId...
在MongoDB数据库中,ObjectId是用于唯一标识文档的关键字段,它是一个12字节的BSON类型数据。本文将深入探讨如何在C#环境中生成和处理MongoDB的ObjectId。 首先,ObjectId由四部分组成,每部分都有特定的意义: 1. ...
GraphQLObjectId mongodb ObjectId的GraphQL标量类型。例子const { GraphQLObjectType, GraphQLString } = require('graphql')const GraphQLObjectId = require('graphql-scalar-objectid')const UserType = new ...
基于MongoDB规范的ObjectID实现 目标是提供正确的MongoDB ObjectID实现,以确保不可能在快速生成时创建类似的条目(如在其他开源软件包中所见) 目录 安装 npm install mongo-objectid 用法 mkdir myproject cd ...
在Node.js环境中,当你使用MongoDB作为数据库时,经常需要处理`ObjectId`作为查询条件的情况。MongoDB在插入新文档时,会自动生成一个唯一的`ObjectId`作为文档的 `_id` 字段,这个字段通常用于唯一标识每条记录。...
如果您是与MongoDb ObjectId纠缠的人,那么hacken也可以简化工作。内容对象功能isObjValid 检查ObjectId是否有效。 var hacken = require ( 'hacken' ) ;if ( hacken . isObjValid ( objid ) ) {console . log ( ...
MongoDB的ObjectId是一个12字节的BSON类型数据,用于唯一标识文档。它由四个部分组成,每个部分都有特定的用途,以确保全局唯一性。以下是对标题和描述中涉及知识点的详细解释: 1. **时间戳(Timestamp)**: ...
5. **ObjectId 数据类型**:ObjectId 是 MongoDB 中的一种特殊数据类型,它是一个12字节的BSON类型数据,通常用于 `_id` 字段。 6. **ObjectId 结构**:ObjectId 的前4个字节代表创建该 ObjectId 的时间戳,精确到...
在.NET Core中,MongoDB驱动程序提供了对ObjectId的处理,ObjectId是MongoDB中用于标识文档的一个特殊类型。本文将深入探讨如何在.NET Core环境中反解ObjectId,以及它的数据结构和实现思路。 首先,ObjectId的数据...