热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

MongoDBJavaScriptDriver驱动的使用示例

下面文章是对MongoDB的几个Node.js客户端的评测,在评测后,作者虽然没有选择MongoDB+Node.js的组合,但是其测试过程和结果具有参考意义,在故障情况下的测试,确实也是我们选择使用每一种工具时必要的一环。如果你在使用MongoDB,你对下面说的情况做过

下面文章是对 MongoDB 的几个 Node.js 客户端的评测,在评测后,作者虽然没有选择 MongoDB+Node.js 的组合,但是其测试过程和结果具有参考意义,在故障情况下的测试,确实也是我们选择使用每一种工具时必要的一环。如果你在使用MongoDB,你对下面说的情况做过测试吗?

本文来源于作者@latteye 的热心投搞。原文链接:latteye.com。

本站欢迎各种NoSQL方面的新闻技术文章投稿,下面是原文。

在对 node.js + MongoDB 做了一周不到的测试之后,我们决定放弃这对组合。放弃的原因有二:

MongoDB 对数据的保障性不是我们所需要的。这不是 MongoDB 的错误,这是我们选择产品的错误。我觉得 MongoDB 其实就是放弃了这样的数据保障性才获得了更好的性能。所以才更适合类似 facebook twitter 对消息保障性要求不高,但是量大的应用。

Javascript 的 driver 略显不成熟。其实各类开发速度都很快,同时我对他们的熟悉程度还不够好。所以总的感觉现在还没到用的时候。

这里对第二点做个流水账式样的记录,在学习的过程中发现相关的英文和中文资料都比较缺乏。

我所测试到的 Driver 有:

这三个 Driver 里,mongolian 和 mongoose 都是依赖 native 的。不过在这里mongolian的作者提到 mongolian对 native db class 部分并不调用。看来依赖的程度有所不一。

所测试的内容是 failover。MongoDB 推荐的 failover 方案为 Replica Set,这个架构逻辑上不难理解。至少三个节点,至多七个节点;各个节点可以有 0-99 的优先级等一系列特性让他成为非常优秀的 HA 方案。

测试方法: 插入 N 条数据,并且在插入的过程中将 Primary 进程杀死。查看客户端(node.js)是否正常转移到新的 Primary ,并且最终检查数据一致性。可以接受插入不了数据,但是一定要有错误返回。返回错误的数量一定要和数据库内未插入的数据数量一致。

一、native

先给出 native 的测试脚本:

var mongodb = require('mongodb');
var Db = require('mongodb').Db,
  Connection = require('mongodb').Connection,
  Server = require('mongodb').Server,
  ReplSetServers = require('mongodb').ReplSetServers;
var replStat = new ReplSetServers([
        new Server('172.16.5.151', 28010, { auto_reconnect: true }),
        new Server('172.16.5.152', 28010, { auto_reconnect: true }),
        new Server('172.16.5.153', 28010, { auto_reconnect: true })
        ],
        {rs_name: 'rs1'}
);
var db = new Db('a', replStat);
db.open(function (error, client) {
  if (error) throw error;
  var collection = new mongodb.Collection(client, 'blogposts');
function test_read(t)
{
  console.log('enum elements...');
  var start = new Date;
  var times = 0;
  for(var i = 1; i <= t; i++ )
  {
        collection.find({'_id':i}, {limit:1}).nextObject(function(err, docs) {
            if (err) console.warn(err.message);
            //else console.dir(docs);
            if(++times >= t)
                console.log('enum finished:cost time:' + (new Date - start) + 'ms');
          });
  }
}
function test_write(t)
{
  var start = new Date;
  var times = 0;
  console.log('add elements...');
  for(var i = 1; i <= t; i++ )
  {
        collection.insert({date: (new Date()).getTime(), body:'sadf', title:'abc', _id:i}, {safe:{w:2, wtimeout: 10000}},
                    function(err, objects) {
            if (err) console.warn(err.message);
            if(++times >= t)
            {
                console.log('add finished:cost time:' + (new Date - start) + 'ms');
                test_read(t);
            }
        });
  }
}
var wtimes = 10000;
test_write(wtimes);
//test_read(wtimes);
});

三个 driver 中文档工作做的最好的就是 native 了,example 也比较多。不过作者在 Replica Set 的 examples 中给了个让人很莫名的开头:

var port1 = 27018;
var port2 = 27019;
var server = new Server(host, port, {});
var server1 = new Server(host, port1, {});
var server2 = new Server(host, port2, {});
var servers = new Array();
servers[0] = server2;
servers[1] = server1;
servers[2] = server;
var replStat = new ReplSetServers(servers);

对于我这种不写代码的人来说,您老写成这样着实让我纠结了一番

测试结果: 在插入的过程中将 Primary kill 后大约有 1/3 的概率 node.js crash 了。其余 2/3 的概率 node.js 彻底卡住。MongoDB 端 Primary 正常转移,但未见数据继续插入进来。我很想贴一点 log 上来,但 native driver 真的没有任何 log,就是单纯的卡住了卡住卡

Crash log:

[root@localhost bin]# ./node ~/native_test.js
2
3
4
add elements...
node: src/uv-common.c:92: uv_err_name: Assertion `0' failed.
已放弃

在经过几天的搜索以后[1 2 3 ] 我发现似乎有人和我做过类似的测试,但是从来没有得到明确的答案。昨天我也将这个问题发到的 native 论坛上,目前还没有人回复。
但是后来又随后开始怀疑自己的脚本,同时看到新的解答[4],于是开始尝试不在一个 db.open 里面写 for,而在 for 里面反复的 db.open 和 db.close。但是没有成功,循环插入10条数据,成功插入的只有第一条。无论有没有 db.close 都是这个现象。这个不工作的代码就不贴上来了,如果有那位做过类似测试希望可以交流一下。

二、mongoose

测试脚本:

var mongoose = require('mongoose');
mongoose.createSetConnection('mongodb://172.16.5.151:28010/a,mongodb://172.16.5.152:28010/a,mongodb://172.16.5.153:28010/a');
var Schema = mongoose.Schema,
    ObjectId = Schema.ObjectId;
var BlogPost = new Schema({
//    author    : ObjectId
    _id       : Number
  , title     : String
  , body      : String
  , date      : Date
});
mongoose.model('BlogPost', BlogPost);
var post = mongoose.model('BlogPost');
function test_read(t)
{
        var start = new Date();
        var times = 0;
        console.log('enum elements...');
        for(var i = 1; i <= t; i++)
        {
                //console.log("read:"+i);
                post.findById(i, function(err, doc){
                        if(err)
                                console.log(err);
                        //else
                        //      console.log(doc);
                        if(++times >= t)
                        {
                                var end = new Date();
                                console.log('enum finished:cost time:' + (end - start) + "ms");
                        }
                });
        }
};
function test_write(t)
{
        var start = new Date();
        var times = 0;
        console.log('add elements...');
        for(var i = 1; i <= t; i++)
        {
                //console.log("write:"+i);
                var p = new post();
                p._id = i;
                p.title = 'abc';
                p.body = 'sadf';
                p.date = (new Date()).getTime();
                p.save(function(err){
                        if(err)
                        {
                                console.log(err);
                        }
                        if(++times >= t)
                        {
                                var end = new Date();
                                console.log('add finished:cost time:' + (end - start) + "ms");
                                test_read(t);
                        }
                });
        }
}
var wtimes = 10000;
test_write(wtimes);
//process.exit(0);

首先!连接 Replica Set 要用createSetConnection:

mongoose.createSetConnection('mongodb://172.16.5.151:28010/a,mongodb://172.16.5.152:28010/a,mongodb://172.16.5.153:28010/a');

你或许和我一样走过一些弯路[5 6]。

测试结果: OSE 的测试结果几乎和 native 一样,唯一好一点的是它从来没把 node.js 弄 crash 过。它唯一的反应就是 卡住卡住
OSE 和 native 在这个测试上的区别是,native 一边产生数据一边插入。OSE 先将数据在内存中产生出来以后,再一次插入数据库。而 node.js 存在一个内存限制的问题 (一个浏览器有什么理由需要2G的内存呢?),所以当 OSE driver 占用超过 1.9G 内存之后,node.js 不出意料的 crash。

PS. google 论坛上有人说可以通过参数让 node.js 支持任何大小的内存。经过我的测试(CentOS 6 x86-64,0.5.x,0.4.x)没有成功过。可工作的最高数值为 1900M。

你可以注意到了 native 驱动有一个 auto_reconnect 参数(尽管它没有 reconnect),而 mongoose 脚本里面没看到。OSE 的确也有设置 auto_reconnect 的方式[7],但是只看到给普通连接设置的方式。没有看到给 Replica Set 用的方式。自己胡乱尝试了几个设置方式无一成功。希望 ose 的作者能再多花点时间在文档方面。另一方面也可以看到,OSE 其实对 native 依赖还是蛮严重的。这种设置方式的出现似乎只是传递给 native 驱动,我猜测 OSE 自己没有对这块做任何处理。

三、mongolian

测试脚本:

var mongodb = require('mongolian');
var server = new mongodb(
    "172.16.5.151:28010",
    "172.16.5.152:28010",
    "172.16.5.153:28010"
)
var db = server.db("a")
var blogposts = db.collection("blogposts")
function test_read(t)
{
  console.log('enum elements...');
  var start = new Date;
  var times = 0;
  for(var i = 1; i <= t; i++ )
  {
        blogposts.find({'_id':i}, {limit:1}).nextObject(function(err, docs) {
            if (err) console.warn(err.message);
            //else console.dir(docs);
            if(++times >= t)
                console.log('enum finished:cost time:' + (new Date - start) + 'ms');
          });
  }
}
function test_write(t)
{
  var start = new Date;
  var times = 0;
  console.log('add elements...');
  for(var i = 1; i <= t; i++ )
  {
        blogposts.insert({date: (new Date()).getTime(), body:'sadf', title:'abc', _id:i},
                    function(err, objects) {
            if (err) console.warn(err.message);
            if(++times >= t)
            {
                console.log('add finished:cost time:' + (new Date - start) + 'ms');
                test_read(t);
            }
        });
  }
}
var wtimes = 10000;
test_write(wtimes);
//test_read(wtimes);

测试结果: mongolian(以下简称lian) 的反应是这三个驱动中最好的。首先当开启 node 的时候,lian 会给出 debug 信息,明确告诉你他连接到了哪台 mongodb,作者也明确说了这个 log 是为 Replica Set 做的 [89]。当 Primary 被 kill 掉之后,lian 会告诉你连接丢失。在后面的插入lian会明确的告诉你插入失败,并且是每一次插入就给出一个 log,而且程序会一路走下去,不会卡住。

[root@localhost ~]# node lian_test.js
add elements...
[debug] mongo://172.16.5.151:28010: Disconnected
[error] mongo://172.16.5.151:28010: Error: ECONNREFUSED, Connection refused
[debug] mongo://172.16.5.152:28010: Connected
[debug] mongo://172.16.5.153:28010: Connected
[debug] mongo://172.16.5.152:28010: Initialized as secondary
[debug] mongo://172.16.5.153:28010: Initialized as primary
[info] mongo://172.16.5.153:28010: Connected to primary
[debug] Finished scanning... primary? mongo://172.16.5.153:28010

我觉得 lian 的这种工作模式可以从它的代码编写方式里面体现出来。lian 的代码里面不存在打开一个 connection 或者 db.open 这样的概念,所以我估计 lian 是每一次 insert 就会尝试打开一次 connection。虽然他没有再次找到正确的 Primary,但至少他知道自己连接丢失了。
但是 lian 没能再次找到正确的 Primary 可能意味着他先打开了一个 ConnectionPool (你可以通过 poolSize 在 native 里面设置 pool 的大小),只有打开 ConnectionPool 的时候才会尝试去做 Primary 判断。
另外 lian 的插入速度也不错,感觉比 OSE 好,几乎和 native 一样。

实验做到后面,我极度怀疑自己的测试脚本写的不对。因为 native 是有 auto_reconnect 的参数的,但是缺没有工作。作者应该考虑了这个问题的。
而也肯定有一种方式让我在 for 里面打开 connection 、写完、关闭 connection。只是我现在没找到正确的写法。
希望有经验的朋友给予一些帮助。


推荐阅读
  • 本文探讨了在通过 API 端点调用时,使用猫鼬(Mongoose)的 findOne 方法总是返回 null 的问题,并提供了详细的解决方案和建议。 ... [详细]
  • 基于Node.js、Express、MongoDB和Socket.io的实时聊天应用开发
    本文详细介绍了使用Node.js、Express、MongoDB和Socket.io构建的实时聊天应用程序。涵盖项目结构、技术栈选择及关键依赖项的配置。 ... [详细]
  • 2023年5月20日凌晨5:20(北京时间),由区块链技术和去中心化应用平台ArcBlock(区块基石)研发的ABT钱包移动应用程序,在苹果App Store和Google Play同步上线,标志着用户在数字世界中首次能够完全掌控自己的身份。 ... [详细]
  • 本文探讨了如何解决在使用CoffeeScript定义类时,实例化后对象为空的问题,并提供了解决方案。 ... [详细]
  • 近期参与了一个旨在提高在线平台大规模查询响应速度的项目,预计处理的数据量为2-3亿条,数据库并发量约为每秒1500次,未来可能增至3000次。通过对比Redis和MongoDB,最终选择了MongoDB,因其具备优秀的横向扩展性和GridFS支持下的Map/Reduce功能。 ... [详细]
  • 为提升用户体验,我们推出了全新的多轮预订功能,允许用户在一天内的不同时间段进行NFT预订。 ... [详细]
  • 本文深入探讨了分布式文件系统的核心概念及其在现代数据存储解决方案中的应用,特别是针对大规模数据处理的需求。文章不仅介绍了多种流行的分布式文件系统和NoSQL数据库,还提供了选择合适系统的指导原则。 ... [详细]
  • 本文探讨了使用lightopenid库实现网站登录,并在用户成功登录后,如何获取其姓名、电子邮件及出生日期等详细信息的方法。特别针对Google OpenID进行了说明。 ... [详细]
  • 实践指南:使用Express、Create React App与MongoDB搭建React开发环境
    本文详细介绍了如何利用Express、Create React App和MongoDB构建一个高效的React应用开发环境,旨在为开发者提供一套完整的解决方案,包括环境搭建、数据模拟及前后端交互。 ... [详细]
  • MongoDB核心概念详解
    本文介绍了NoSQL数据库的概念及其应用场景,重点解析了MongoDB的基本特性、数据结构以及常用操作。MongoDB是一个高性能、高可用且易于扩展的文档数据库系统。 ... [详细]
  • 利用 Jest 和 Supertest 实现接口测试的全面指南
    本文深入探讨了如何使用 Jest 和 Supertest 进行接口测试,通过实际案例详细解析了测试环境的搭建、测试用例的编写以及异步测试的处理方法。 ... [详细]
  • 58同城的Elasticsearch应用与平台构建实践
    本文由58同城高级架构师于伯伟分享,由陈树昌编辑整理,内容源自DataFunTalk。文章探讨了Elasticsearch作为分布式搜索和分析引擎的应用,特别是在58同城的实施案例,包括集群优化、典型应用实例及自动化平台建设等方面。 ... [详细]
  • 使用 NDB 提升 Node.js 应用调试体验
    本文介绍了由 Google Chrome 实验室推出的新一代 Node.js 调试工具 NDB,旨在为开发者提供更加高效和便捷的调试解决方案。 ... [详细]
  • 一家位于长沙的知名网络安全企业,现面向全国诚聘高级后端开发工程师,特别欢迎具有一线城市经验的技术精英回归故乡,共创辉煌。 ... [详细]
  • Redis:缓存与内存数据库详解
    本文介绍了数据库的基本分类,重点探讨了关系型与非关系型数据库的区别,并详细解析了Redis作为非关系型数据库的特点、工作模式、优点及持久化机制。 ... [详细]
author-avatar
夏石兵
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有