使用Node.js和MongoDB进行架构设计考虑

我打算写一下使用Node.js + MongoDB组合的系统架构。几乎没有任何代码。

非同步的非阻塞式,網絡服務的整體。

MongoDB是主要的主题,标题起源于Node.js。这是因为Node.js是本次架构设计的起点。谈到Node.js的特点,

    サーバーサイド javascriptでそれなりに高速
    非同期ノンブロッキング主義
    対話型インタープリタで色々試しながら開発できる

这是我个人喜欢的一个点,因为我一直认为这对于撰写关于Web服务的文章来说非常适合。为什么呢?因为通常来说,当我们提到Web服务时,

    不特定多数在不特定的时间使用。为多数用户提供个性化处理。有多数用户共享的处理。最好的情况是尽早做出回应。

具有这样的特点,

    1と2はまさに非同期で、ノンブロッキングにすることで、限りある資源で最大限に処理を行えるし
    3はきちんと識別をして共有する空間に対する処理を非同期ノンブロッキングで作ればよさそうだし
    4は上記をやればそれなりに向上できるな

因为可以这样说。

在整个网络服务中共享的对象空间

在使用PHP之类的语言编写Web服务时,每次向Apache发送请求时,PHP脚本会被执行,执行的流程包括读取会话、连接数据库、执行查询、执行逻辑、生成HTML、生成响应等。由于存在缓存等机制,代码的编译等工作将被最小化,但每个请求都是独立的事件,需要从数据库中创建模型,并进行逻辑操作,生成输出。因此,每次都需要读取并解释会话文件或数据库中的数据,创建和操作对象。

每次读取都很慢的原因是什么呢~我考虑着,好吧,当我想把它装载到内存中时,我灵光一闪。

如果我们将可以共有的东西和每个个体的东西都展开到内存中,那么速度会更快,如果那些应该共享的东西都被看作是内存中的一个实例,那么也许我们不需要锁之类的东西吗?

正如您所知,JavaScript可以在对象上随意添加任何内容。因此,很容易在内存中的对象上添加必要的数据或功能。我们可以将这些对象放在内存中,并根据用户的查询请求调用所需的对象。

如果这个Object空间的重点是性能,而且要将生成HTML之类的任务外置化,那么最好将此实现为内部API服务器进程。如果要定义一个名为TObject的基础对象,可能会像下面这样。

var TObject = function( om, oid ) {
    var myself = this;
    myself.className = 'TObject';

    myself.objectManager = om;

    if ( oid !== undefined ) {
        myself.oid = oid;
    } else {
        myself.oid = myself.objectManager._genStrIdSync();
    }

    myself.meta = {};
    myself.metaHide = {};
    myself.body = {};
    myself.status = {};
    myself.statusHide = {};
    myself.cache = {};
    myself.__initMetaSync();
    myself.__initMetaHideSync();
    myself.__initBodySync();
    myself.__initStatusSync();
    myself.__initCacheSync();

    myself.__initEventListenerSync();
}

util.inherits( TObject, events.EventEmitter );

/* 〜色々な処理〜 */

/* APIに公開するメソッドの例 */
TObject.prototype.getNameSync = function() {
    var myself = this;

    return myself.meta.name;
}

module.exports = TObject;

这个ObjectManager管理着这个和它的派生类的对象,感觉像是这样吧?

var META_DB = 'meta';
var BODY_DB = 'body';
var DB_SERVER_HOST = '127.0.0.1';
var DB_SERVER_PORT = 27017;

var ObjectManager = function( callback ) {
    var myself = this;
    events.EventEmitter.call( this );
    myself.class_name = 'ObjectManager';

    myself.parentCallback = callback;

    // Object空間はoidをキーにしたdict
    myself.objects = {};
    myself.classNames = Objects;

    myself.metaDb = new mongodb.Db(
        META_DB,
        new mongodb.Server(
            DB_SERVER_HOST,
            DB_SERVER_PORT,
            {
                auto_reconnect : true,
                poolSize : 10,
                socketOptions : {
                    timeout : 0,
                    keepAlive : 0,
                    encoding : 'utf8'
                }
            }
        ),
        { w : 1 }
    );

    myself.bodyDb = new mongodb.Db(
        BODY_DB,
        new mongodb.Server(
            DB_SERVER_HOST,
            DB_SERVER_PORT,
            {
                auto_reconnect : true,
                poolSize : 10,
                socketOptions : {
                    timeout : 0,
                    keepAlive : 0,
                    encoding : 'utf8'
                }
            }
        ),
        { w : 1 }
    );

    myself.metaDb_is_opened = false;
    myself.bodyDb_is_opened = false;

    myself.on( 'db_opened', myself.start );

    myself.metaDb.open( function( err, client ) {
        //var myself = this;
        if ( err ) {
            myself.exit( err );
        } else {
            myself.metaDb_is_opened = true;
            myself.emit( 'db_opened' );
        }
    });

    myself.bodyDb.open( function( err, client ) {
        //var myself = this;
        if ( err ) {
            myself.exit( err );
        } else {
            myself.bodyDb_is_opened = true;
            myself.emit( 'db_opened' );
        }
    });
}

util.inherits( ObjectManager, events.EventEmitter );

/* 〜色々な処理〜 */

/*
 * メソッド: createObject
 * API用 Objectを新規に作成する
 */
ObjectManager.prototype.createObject = function( className, params, callback ) {
    var myself = this;

    // まずは与えられたクラス名のクラスがあるかどうか
    // 内部APIなので基本的に入力パラメータを信じる
    var myclass = eval( 'myself.classNames.' + className );
    if ( myclass == undefined || myclass == null ) {
        callback( new Error( 'Class not found' ), {} );
        return false;
    }

    // Objectをとりあえず作成
    var obj = new myclass( myself );
    var oid = obj.oid;

    // paramsの内容で初期化
    // paramsはkey + valueだが、valueはURLクエリパラメータなので基本文字列
    // Name
    if ( params.name !== undefined && params.name !== null ) {
        if ( params.name.constructor == String ) {
            obj.setNameSync( params );
        }
    }
    // Dir
    if ( params.dir !== undefined && params.dir !== null ) {
        if ( params.dir.constructor == String ) {
            obj.setDirSync( params );
        }
    }
    // Owner
    if ( params.owner !== undefined && params.owner !== null ) {
        if ( params.owner.constructor == String ) {
            obj.setOwnerSync( params );
        }
    }

    // Objectを空間に配置
    myself.objects[ oid ] = obj;

    // ここでコールバックしておく
    callback( null, Tool.copy( obj.meta ) );

    // コールバック後にobjectをflush
    myself.objects[ oid ]._flushChain();

    return true;
}

/*
 * メソッド: cmdObject
 * API用 Objectのメソッドをコールする(準備をする)
 */
ObjectManager.prototype.cmdObject = function( oid, method, params, callback ) {
    var myself = this;

    // Objectがオンメモリにあるかどうか確認 無ければ読んでから、有ればすぐに次の処理へ
    if ( myself.objects[ oid ] !== undefined ) {
        myself.__callObjectMethod( myself.objects[ oid ], method, params, callback );
    } else {
        myself.__loadObject( oid, function( err, meta ) {
            if (err) {
                callback( err, {} );
            } else {
                myself.__callObjectMethod( myself.objects[ oid ], method, params, callback );
            }
        });
    }

    return true;
}

以上代码是我正在写的内容摘要。缺少很多细节。如果能够传达出我的想法就好了…。

当我们从使用express等编写的API服务器中加载ObjectManager并实例化时,API服务器上将创建出一个包含多个Object的空间,并且通过ObjectManager可以向这些Object发送指令。

MongoDB被用于数据持久化和搜索。

顺便问一下,您有没有注意到上面的代码中调用了MongoDB的类?

在这个ObjectManager中,我们使用MongoDB对Object进行持久化。在TObject的初始化时,实际上将Object的内部分为多个”部分”。这个示例目前仍在开发中,因此未来可能会发生很多变化,但现在有如下意图。请先看看作为参考。以下的Meta、MetaHide和Body会在MongoDB中进行持久化,并用于搜索。

元数据

这是用于Object的列表和搜索等用途的领域。它必须存储在内存中。在MongoDB上,它也会保存在名为meta的数据库中。这是用户最常见的部分,也会在MongoDB上使用查询。

MetaHide: 元数据隐藏

这是Meta的隐藏属性版本。它被放在MongoDB上。例如,存储了用于仅通过关键字而不明确指定Object属性进行搜索的单词列表等。通过这种方式,可以在不知道类名的情况下尝试搜索包含”hoge”的任何内容。在获取Object时,它不会被显示出来。

状态

这与Meta类似,但不存储在MongoDB中。只记录与不需要持久化的对象相关的概要信息。例如,记录与该对象相关的用户的在线状态等。在获取Meta对象时,它将一同返回。由于不存储在MongoDB中,因此无法在MongoDB中进行搜索。将不需要作为搜索目标的内容存放在这里。

状态隐藏

这是Status的隐藏属性版本。如果拥有setTimeout的timeoutId等等,就会变得幸福。

身体

这是该Object应包含的主要数据。如果是简单的话,可以全部放到Meta里面。但是如果是较大的数据就放到这边。但是作为交换条件,它不会成为搜索对象。

缓存

除了Body的内存镜像之外,放置一些其他未知用途所需的东西。

最终

我在上面简要介绍了我目前正在开发的一个API服务器的要点。MongoDB的优点包括可以几乎直接保存JSON,同时结构的变化也非常自由灵活。这与Node.js的异步非阻塞的思路恰好与MongoDB的特性相匹配,使得它变成了这种形式。真的要感谢MongoDB让开发变得如此轻松。

嗯,说到“架构”,我想IT行业中的“架构师”工作就是将我刚才所解释的内容想象并实现的工作。主要包括解析问题结构的大部分、理解使用软件的特性结构的中等程度,以及个别部件的结构的小部分。这样的解决方案实际上有无数种,但是需要在相应的时间内进行筛选并提供。我们就是从事这样的工作。

最初我打算写一个使用express的例子,但是因为已经有人发布了,所以我临时改变主意了。

因为我们仍在继续开发和验证,所以计划在不久的将来进行一些公开。

非常感谢你。那么我们再见。

bannerAds