首先,正如@xtreme-biker所说,性能在很大程度上取决于您的硬件。具体来说,我的第一个建议是检查您是在虚拟机上运行还是在本机主机上运行。在我的情况下,在带有SDD驱动器的i7上的CentOS VM中,我每秒可以读取123,000个文档,但在同一驱动器上的Windows主机上运行的完全相同的代码每秒读取多达387,000个文档。
接下来,让我们假设您确实需要阅读完整的集合。这就是说您必须执行完全扫描。让我们假设您无法更改MongoDB服务器的配置,而只能优化代码。
然后一切都归结为什么
collection.find().forEach((Block<Document>) document -> count.increment());
确实如此。
快速展开 MongoCollection.find() 显示它实际上这样做:
ReadPreference readPref = ReadPreference.primary();
ReadConcern concern = ReadConcern.DEFAULT;
MongoNamespace ns = new MongoNamespace(databaseName,collectionName);
Decoder<Document> codec = new DocumentCodec();
FindOperation<Document> fop = new FindOperation<Document>(ns,codec);
ReadWriteBinding readBinding = new ClusterBinding(getCluster(), readPref, concern);
QueryBatchCursor<Document> cursor = (QueryBatchCursor<Document>) fop.execute(readBinding);
AtomicInteger count = new AtomicInteger(0);
try (MongoBatchCursorAdapter<Document> cursorAdapter = new MongoBatchCursorAdapter<Document>(cursor)) {
while (cursorAdapter.hasNext()) {
Document doc = cursorAdapter.next();
count.incrementAndGet();
}
}
这里相当快(低于10ms),大部分时间都花在while循环内,特别是在私有方法内部FindOperation.execute()
QueryBatchCursor.getMore()
getMore()
调用和它的时间基本上消耗在两个操作中:1)从服务器获取字符串数据和2)将字符串数据转换为BsonDocument。DefaultServerConnection.command()
事实证明,Mongo在获取大量结果集所需的网络往返次数方面非常聪明。它将首先使用 firstBatch 命令提取 100 个结果,然后提取更大的批次,nextBatch 是批大小,具体取决于集合大小,直到达到限制。
所以,在木头下面,会发生这样的事情来获取第一批。
ReadPreference readPref = ReadPreference.primary();
ReadConcern concern = ReadConcern.DEFAULT;
MongoNamespace ns = new MongoNamespace(databaseName,collectionName);
FieldNameValidator noOpValidator = new NoOpFieldNameValidator();
DocumentCodec payloadDecoder = new DocumentCodec();
Constructor<CodecProvider> providerConstructor = (Constructor<CodecProvider>) Class.forName("com.mongodb.operation.CommandResultCodecProvider").getDeclaredConstructor(Decoder.class, List.class);
providerConstructor.setAccessible(true);
CodecProvider firstBatchProvider = providerConstructor.newInstance(payloadDecoder, Collections.singletonList("firstBatch"));
CodecProvider nextBatchProvider = providerConstructor.newInstance(payloadDecoder, Collections.singletonList("nextBatch"));
Codec<BsonDocument> firstBatchCodec = fromProviders(Collections.singletonList(firstBatchProvider)).get(BsonDocument.class);
Codec<BsonDocument> nextBatchCodec = fromProviders(Collections.singletonList(nextBatchProvider)).get(BsonDocument.class);
ReadWriteBinding readBinding = new ClusterBinding(getCluster(), readPref, concern);
BsonDocument find = new BsonDocument("find", new BsonString(collectionName));
Connection conn = readBinding.getReadConnectionSource().getConnection();
BsonDocument results = conn.command(databaseName,find,noOpValidator,readPref,firstBatchCodec,readBinding.getReadConnectionSource().getSessionContext(), true, null, null);
BsonDocument cursor = results.getDocument("cursor");
long cursorId = cursor.getInt64("id").longValue();
BsonArray firstBatch = cursor.getArray("firstBatch");
然后用于获取每个下一批。cursorId
在我看来,驱动程序实现的“问题”是注入了字符串到JSON解码器,但JsonReader(解码()方法所依赖的JsonReader却没有。即使到您已经接近套接字通信的地方,也是这样。com.mongodb.internal.connection.InternalStreamConnection
因此,我认为,除非你深入到MongoCollection.find()
InternalStreamConnection.sendAndReceiveAsync()
您无法减少往返次数,也无法更改将响应转换为 BsonDocument 的方式。不是没有绕过驱动程序并编写自己的客户端,我怀疑这是一个好主意。
邮 编如果你想尝试上面的一些代码,你需要getCluster()方法,它需要对mongo-java-driver进行肮脏的黑客攻击。
private Cluster getCluster() {
Field cluster, delegate;
Cluster mongoCluster = null;
try {
delegate = mongoClient.getClass().getDeclaredField("delegate");
delegate.setAccessible(true);
Object clientDelegate = delegate.get(mongoClient);
cluster = clientDelegate.getClass().getDeclaredField("cluster");
cluster.setAccessible(true);
mongoCluster = (Cluster) cluster.get(clientDelegate);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
System.err.println(e.getClass().getName()+" "+e.getMessage());
}
return mongoCluster;
}