Java基础、中级、高级、架构面试资料

Mongo高性能揭秘之ObjectId解密

JAVA herman 2144浏览
公告:“业余草”微信公众号提供免费CSDN下载服务(只下Java资源),关注业余草微信公众号,添加作者微信:xttblog2,发送下载链接帮助你免费下载!
本博客日IP超过2000,PV 3000 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog2,之前的微信号好友位已满,备注:返现
受密码保护的文章请关注“业余草”公众号,回复关键字“0”获得密码
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序
视频教程免费领
腾讯云】1核2G5M轻量应用服务器50元首年,高性价比,助您轻松上云

当你有空闲时间的时候,看看 Redis、Mongo 等系统的设计,收获会颇丰。

我在前面的一章讲解了,MongoDB 中 ObjectId 的生成原理。Mongo 中的 ObjectId 设计的很精妙,长度比雪花算法还长,还能不完全依赖于时钟(比如时钟回拨,我们可以通过调整 pid 的形式保障 ObjcetId 不重复)。但是它也有缺点,就是长度太长,占用空间比雪花算法还大,在 Java 中我们必须通过字符串来标识它。

我猜有部分网友可能会说,雪花算法我通过 Java 就可以实现哈,单独为了一个分布式 ID,我放弃雪花,引入 MongoDB 并不明智。

确实是这样的,但是技术架构师在技术选型时,往往考虑的是 Mongo 的 NoSQL 特性。

Mongo 在设计之初,就考虑了分布式,高性能的插入等需求场景,所以在 ObjectId 的设计上,它还支持让客户端生成 ObjectId。

具体的生成方法,我们通过 Mongo 的 Java 驱动程序来一探究竟。

访问网址https://github.com/mongodb/mongo-java-driver/downloads,下载 mongodb 的 java driver 源码(本地反编译驱动程序也是可以的,或者直接在 github 上看对应类的源码)。

驱动源码打开后,我们找到 org.bson 包下的 ObjectId.java 文件,针对性的看看这个类,进行分析。

默认构建的 objectId 代码主要由 _time,_machine 和 _inc 组成。为了方便讲解,我把部分源码摘录如下:

public class ObjectId implements Comparable<ObjectId>, Serializable {
	final int _time;
	final int _machine;
	final int _inc;
	boolean _new;

	public ObjectId(){
		_time = (int) (System.currentTimeMillis() / 1000);
		_machine = _genmachine;
		_inc = _nextInc.getAndIncrement();
		_new = true;
	}
}

需要说明的是,这里的 _machine 变量,实际上包括了机器码 machinePiece 和进程码 processPiece 两个部分。

_machine 变量的算法过程大致是这样的:先通过 NetworkInterface 这个类,获取机器的所有网络接口信息,然后将得到的字符串取散列值,就得到了机器码;再然后通过 RuntimeMXBean.getName() 方法获取 pid,接着再拼装 classloaderid,得到进程码;最后将机器码和进程码进行位或运算得到 _machine。当然这里生成的 _machine 是十进制的,需转成十六进制。

_time 就是我们的时间戳变量,直接由 System.currentTimeMillis() / 1000 计算得出的时间戳,当然这里生成的也是 10 进制的。

_inc 变量就是我们的自增变量,它的自增数是通过 Java 中的 AtomicInteger 类的 getAndIncrement() 方法获取,它能保证每次得到的值是一个递增并不重复的值。

需要注意的是,不同的版本,ObjectId 源码可能不一样,比如我又看了一个 mongo-java-driver-3.8.2.jar,它其中的变量分布如下,但是核心逻辑都是一样的。

public final class ObjectId implements Comparable<ObjectId>, Serializable {
    private static final long serialVersionUID = 3670079982654483072L;
    // ...
    private static final AtomicInteger NEXT_COUNTER = new AtomicInteger((new SecureRandom()).nextInt());
    private final int timestamp;
    private final int machineIdentifier;
    private final short processIdentifier;
    private final int counter;
    // ...
}

下面看看机器码的生成代码。

private static int createMachineIdentifier() {
    int machinePiece;
    try {
        StringBuilder sb = new StringBuilder();
        Enumeration e = NetworkInterface.getNetworkInterfaces();

        while(e.hasMoreElements()) {
            NetworkInterface ni = (NetworkInterface)e.nextElement();
            sb.append(ni.toString());
            byte[] mac = ni.getHardwareAddress();
            if (mac != null) {
                ByteBuffer bb = ByteBuffer.wrap(mac);

                try {
                    sb.append(bb.getChar());
                    sb.append(bb.getChar());
                    sb.append(bb.getChar());
                } catch (BufferUnderflowException var7) {
                    ;
                }
            }
        }

        machinePiece = sb.toString().hashCode();
    } catch (Throwable var8) {
        machinePiece = (new SecureRandom()).nextInt();
        LOGGER.debug("Failed to get machine identifier from network interface, using SecureRandom instead");
    }

    machinePiece &= 16777215;
    return machinePiece;
}

机器码生成的逻辑和我上面说的 _machine 类似,下面再看看进程 pid 的获取方式。

private static short createProcessIdentifier() {
    short processId;
    try {
        String processName = ManagementFactory.getRuntimeMXBean().getName();
        if (processName.contains("@")) {
            processId = (short)Integer.parseInt(processName.substring(0, processName.indexOf(64)));
        } else {
            processId = (short)ManagementFactory.getRuntimeMXBean().getName().hashCode();
        }
    } catch (Throwable var2) {
        processId = (short)(new SecureRandom()).nextInt();
        LOGGER.debug("Failed to get process identifier from JMX, using SecureRandom instead");
    }

    return processId;
}

nodejs 等其他语言的相关实现也是类似的,我就不在一一列举了。总之 Mongo 的高性能精髓还是很智慧的!

nodejs中mongo ObjectID的实现

虽然 MongoDB 中 ObjectId 是轻量级的,但是如果全部在服务端生成肯定会花费一点开销,所以 Mongo 团队就想办法让 ObjectId 交给客户端去生成,让后服务端判断,如果插入的数据中已经有了 ObjcetId,我就采用,如果没有 ObjectId,服务端再生成。

Mongo 的高性能保障,ObjectId 的精妙设计只是其中之一。

业余草公众号

最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加作者微信号:xttblog2。备注:“1”,添加博主微信拉你进微信群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作也可添加作者微信进行联系!

本文原文出处:业余草: » Mongo高性能揭秘之ObjectId解密