分片

时间:2017-03-01 02:32:38

标签: java mongodb performance bulkinsert

我有一个Mongodb群集,其中包含一个主副本和一个副副本一起作为复制集运行。但随着流量的增长,我决定执行分片以获得更高的写入速度。

我根据此tutorial对“_id”列执行了散列分片,并将数据拆分为两个分片。然后,我进行了一些基准测试,发现在某些情况下,分片群集甚至比未钻孔群集慢。

这是测试结果。

  1. 最大吞吐量测试:使用10台机器同时运行“mongoimport”将数据加载到目标数据库中,以测试db的最大写入速度。

    结果:

    分片群集可以插入 39500 个文档。

    未整理的群集可以插入 27400 个文件。

  2. 单实例mongoimport测试:只使用一台机器运行“mongoimport”将数据加载到目标数据库中。

    结果:

    分片群集可以插入 14285 个文件。

    未整理的群集可以插入 14085 个文件。

  3. 使用mongodb java驱动程序加载单实例数据:通过调用mongodb java驱动程序的api,只使用一个实例将数据加载到目标数据库中。

    结果:

    分片群集可以插入 4630 文档。

    未分组的群集可以插入 17544 个文件。

  4. 第一次测试的结果非常有意义。您将数据库分成2个分片群集,吞吐量增加了约50%,一切都很完美,万岁!

    第二次测试有点道理。那么吞吐量大致相同,但可能是数据加载器方面的瓶颈,毕竟我们只用一个实例加载数据。

    但第三次测试确实让我感到困惑。没有意义的是,分片群集可以比未受干扰的群集慢得多。另一方面,unsharded db具有惊人的速度,甚至比使用mongoimport加载数据更快。

    下面粘贴了用于加载数据的java代码。我真的无法理解这一点,并提前感谢所有答案。

    public static void insert(String host, int port) throws FileNotFoundException,
            InterruptedException, ExecutionException {
        MongoClient mongoClient = new MongoClient(host, port);
        mongoClient.setWriteConcern(WriteConcern.UNACKNOWLEDGED);
        MongoDatabase database = mongoClient.getDatabase("my-db");
        MongoCollection<Document> collection = database.getCollection("my-collection");
        Scanner scan = new Scanner(new File("my-sample-dataset"));
    
        // Pre-load the data into the memory, so that the db load test won't be 
        // affected by disk I/O time.
        Queue<List<String>> resource = new LinkedList<>();
        for (int i = 0; i < 100; i++) {
            List<String> strs = new ArrayList<>();
            for (int j = 0; j < 10000; j++)
                strs.add(scan.nextLine());
            resource.add(strs);
        }
    
        System.out.println("start");
        long startTime = System.currentTimeMillis();
        while (!resource.isEmpty()) {
            List<String> strs = resource.poll();
             List<WriteModel<Document>> list = new ArrayList<>();
            for (int i = 0; i < 10000; i++) {
                 list.add(new
                 InsertOneModel<Document>(Document.parse(strs.get(i))));
            }
            collection.bulkWrite(list);
        }
        System.out.println("Finished loading. Time taken: " + (System.currentTimeMillis() - startTime) + "ms");
        scan.close();
    }
    

2 个答案:

答案 0 :(得分:3)

这可能是罪魁祸首 collection.bulkWrite(list);

如果是批量写入,mongos需要将您的批次分成更小的批次,这些批次会转到每个分片。

由于您尚未指定批处理中文档的插入顺序,MongoDB必须遵守插入按指定顺序发生的要求。结果是,当且仅当它们对应于相同的分片时,才能对连续插入进行批处理。

  

mongos维护原始文档顺序,因此只有属于同一分片的连续插入才能组合在一起

例如。考虑一下&#34; k&#34;是分片键。有两个分片,对应于范围

[MinKey, 10], (20, MaxKey]

现在假设我们批量插入以下文件:

[{k: 1}, {k: 25}, {k: 2}]

Doc1 - &gt; Shard1,Doc2 - &gt; Shard2,Doc3 - &gt; Shard3

没有两个连续文档属于同一个分片,因此在这种情况下,每个文档后都需要调用getLastError

在Hashed键的情况下,文档将在分片中更随机地分发。即属于相同分片的文档可能更分散,因此会产生更多批次分布越随机,批量越小,批次总数越多,{{1这实际上意味着性能更差。

FIX :指定getLastError

"ordered: false"

这告诉数据库您不关心严格保留插入的顺序。使用collection.bulkWrite(list, new BulkWriteOptions().ordered(false)); ,mongos将为每个分片创建一个批处理,从而避免额外的getLastError调用。可以在适当的分片上同时执行每个批处理操作,而无需等待前一批的"ordered: false"响应。

另外,

getLastError

  

基于单个mongodb节点创建Mongo实例,并且无法发现副本集或分片群集中的其他节点。

在这种情况下,所有写入请求都将路由到单个节点,由于分片群集,该节点负责所有额外的簿记工作。您应该使用的是

MongoClient mongoClient = new MongoClient(host, port);
  

根据请求类型(读取或写入)和读取首选项(如果它是一个),可以选择多个服务器   读取请求),驱动程序将随机选择要发送的服务器   请求。这适用于副本集和分片集群。

     

注意:在列表中放置尽可能多的服务器,系统将会   弄清楚剩下的。

答案 1 :(得分:0)

一般来说,无论何时使用分片解决方案,您都需要考虑:

  1. 您的客户端应用程序将支持群集,因此可以自行执行路由
  2. 您的客户端应用程序将联系执行路由的中间节点
  3. 我的怀疑是Mongo客户端“自动”不支持群集,这意味着如果您不指定它们,它不会查找属于群集的节点。这种感觉通过以下方式得到加强:

      

    您可以通过传递使用Java驱动程序连接到副本集   ServerAddress列表到MongoClient构造函数。例如:

         

    MongoClient mongoClient =新的MongoClient(Arrays.asList(new   ServerAddress(“localhost”,27017),新的ServerAddress(“localhost”,   27018),新的ServerAddress(“localhost”,27019)));

         

    你可以连接   使用相同的构造函数到分片集群。 MongoClient会   自动检测服务器是否是副本集成员列表或a   mongos服务器列表。