在 MongoDB 的故障转移情况下,对 Node.js 程序进行控制和确认操作
由于有机会在生产环境中使用MongoDB,我想要引入复制和故障转移机制,并记录了当时采取的措施的备忘录。
由于上一篇文章已经涵盖了MongoDB的复制和故障转移设置,所以这一次主要进行程序方面的处理和行为确认。
总结
请确认需要重新验证的内容与上次相同。
-
- MongoDB的复制和故障转移设置
-
- 测试MongoDB的故障转移操作
-
- 处理和设置程序端在故障转移时的操作
- 连接数的管理
请参考前一篇文章中有关MongoDB周边设置和操作的内容,这次我们将对程序的处理和行为进行确认,具体包括3和4的内容。
环境
设置
首先,在package.json中添加所需的包。
本次连接到MongoDB将使用mongoose库。
"dependencies": {
"body-parser": "^1.15.2",
"config": "^1.21.0",
"js-yaml": "^3.6.1",
"cors": "^2.7.1",
"express": "^4.13.4",
"mongodb": "^2.2.31",
"mongoose": "^4.11.7"
}
下面是Node.js程序连接到数据库的代码。
var mongoose = require('mongoose');
mongoose.Promise = global.Promise;
// 複数ある場合はhost:portを,区切りで記載する
var url = 'mongodb://localhost.localdomain:50000,localhost.localdomain:50001/sampledb?replicaSet=LocalRep';
// 任意で自由に設定
var options = {
server: {
poolSize: 10, autoReconnect: true, monitoring: true, reconnectTries: 10, reconnectInterval: 10
},
replset:{
poolSize: 10, autoReconnect: true, monitoring: true, reconnectTries: 10, reconnectInterval: 10
}
};
// 接続
var db = mongoose.createConnection(url, options);
只需在URL中以逗号分隔的形式编写主机:端口即可(在测试中,由于mongoose版本的不同可能无法进行多个连接,但由于忘记了具体的版本号,所以在成功的版本(v4.11.7)下进行了编写)。
确认在故障转移时,程序的行为。
我们将根据上一篇文章中构建的服务器,验证连接切换是否成功。
在本地环境下启动三个数据库,并连接到DB01和DB02。
准备一个简单的API,用于用户搜索和记录登录日志,
验证参考和写入是否正常运行。
Database的启动和配置
# DB01の起動
mongod --port=50000 --dbpath=/var/lib/mongodb/db01 --logpath=/var/log/mongodb/db01.log --replSet=LocalRep --fork
# DB02の起動
mongod --port=50001 --dbpath=/var/lib/mongodb/db02 --logpath=/var/log/mongodb/db02.log --replSet=LocalRep --fork
# DB03の起動
mongod --port=50002 --dbpath=/var/lib/mongodb/db03 --logpath=/var/log/mongodb/db03.log --replSet=LocalRep --fork
如果在执行启动命令时出现以下错误…
$ mongod --port=50000 --dbpath=/root/mongod/db01 --logpath=/root/mongod/logs/db01.log --replSet=LocalRep --fork
about to fork child process, waiting until server is ready for connections.
forked process: 11203
ERROR: child process failed, exited with error number 1
由于存在名为“mongod.lock”的文件,可能无法启动。
因此,如果在指定的dbpath文件夹及其子文件夹中存在“mongod.lock”文件,
则可以在执行启动命令之前将其删除,以便能够成功启动。
登录到DB01,并使用程序创建一个名为”sampledb”的数据库,然后创建”users”和”login.log”集合。
$ mongo --port 50000
LocalRep:PRIMARY> use sampledb;
LocalRep:PRIMARY> db.createCollection('users')
{ "ok" : 1 }
LocalRep:PRIMARY> db.createCollection('login.log')
{ "ok" : 1 }
创建测试数据
LocalRep:PRIMARY> db.users.insert({loginCode:'12345', userName:'sample'})
WriteResult({ "nInserted" : 1 })
程序的处理
-
- 搜索用户(搜索loginCode为12345的用户)
- 如果存在,则记录登录日志
※ 您可以在GitHub上查看验证过程的完整程序。
- MongoDBのコレクションスキーマの定義
'use strict';
var userSchema = {
loginCode:{type:String},
userName:{type:String}
};
module.exports = userSchema;
'use strict';
var mongoose = require('mongoose');
var LoginLogSchema = {
userSchemaId: mongoose.Schema.Types.ObjectId,
loginDate:{type:Date},
};
module.exports = LoginLogSchema;
通过在「src/models/index.js」中加载上述的Schema,可以方便地进行数据库连接管理和从各个Action中进行调用。
// 一部抜粋) Schemaの定義を呼び出す処理
setModels: function() {
var self = this;
Config.models.forEach(function(config){
if (!self.models[config.model]) {
var Schema = require('./schema/' + config.fileName);
self.models[config.model] = self.createModel(config.collection, new self.mongoose.Schema(Schema, {collection: config.collection}));
}
});
},
createModel: function(collectionName, Scheme) {
return this.db.model(collectionName, Scheme);
},
- ロジック部分の処理抜粋
// Modelを定義(詳しくはGitHubのコードを参考)
const BaseModel = require(path + '/src/models/');
const UserModel = BaseModel.models.UserModel;
const LoginLogModel = BaseModel.models.LoginLogModel;
return new Promise((resolve, reject) => {
// ユーザを検索
UserModel.findOne({loginCode: '12345'}, function(error, res){
if (error) {
return reject(error);
}
return resolve(res);
});
}).then(function(user) {
// ユーザが存在していればログインログを残す
if (user) {
var loginLog = new LoginLogModel({userSchemaId: user['_id'], loginDate: new Date()});
return new Promise((resolve, reject) => {
loginLog.save(function(error, res){
if (error) {
return reject(error);
}
return resolve(res);
})
});
}
});
由于上述处理被设置在名为「/api/v1/login」的API上,
可以通过本地环境访问「 http://localhost:8080/api/v1/login 」。
$ curl http://localhost:8080/api/v1/login
{"statusCode":0,"message":"Success."}
请确认数据库中已经录入了日志!
$ mongo --port 50000
LocalRep:PRIMARY> use sampledb;
LocalRep:PRIMARY> db.users.find()
{ "_id" : ObjectId("59bb33b17748fa4368bf8874"), "loginCode" : "12345", "userName" : "sample" }
LocalRep:PRIMARY> db.login.log.find()
{ "_id" : ObjectId("59bb34d1f969de2d2a4ae19c"), "userSchemaId" : ObjectId("59bb33b17748fa4368bf8874"), "loginDate" : ISODate("2017-09-15T02:02:57.803Z"), "__v" : 0 }
我试着关闭DB01。
- 落とすコマンド
sudo ps aux | grep db01 | grep -v 'grep db01' | awk '{print $2}' | xargs kill -9
- プロセス確認
$ ps aux | grep mongo
root 11292 1.8 14.8 1558696 93952 ? Sl 02:42 0:39 mongod --port=50001 --dbpath=/var/lib/mongodb/db02 --logpath=/var/log/mongodb/db02.log --replSet=LocalRep --fork
root 11370 1.5 7.9 1078072 49952 ? Sl 02:42 0:34 mongod --port=50002 --dbpath=/var/lib/mongodb/db03 --logpath=/var/log/mongodb/db03.log --replSet=LocalRep --fork
root 11605 0.0 0.1 103304 884 pts/1 R+ 03:19 0:00 grep mongo
- curlコマンド実行
$ curl http://localhost:8080/api/v1/login
{"statusCode":0,"message":"Success."}
没有发生任何连接错误,可以执行!
- DBの中身確認
# DB01は落ちているのでつながらないことを確認
$ mongo --port 50000
exception: connect failed
# DB02にアクセス
$ mongo --port 50001
LocalRep:PRIMARY> use sampledb;
# 最新のログインログを1件を取得する
LocalRep:PRIMARY> db.login.log.find().sort({'loginDate':-1}).limit(1)
{ "_id" : ObjectId("59bb34d1f969de2d2a4ae19c"), "userSchemaId" : ObjectId("59bb33b17748fa4368bf8874"), "loginDate" : ISODate("2017-09-15T02:02:57.803Z"), "__v" : 0 }
嗯,看起来可以正常地将数据写入到DB02!
将DB01重生
- DB01のプロセスを起動
$ mongod --port=50000 --dbpath=/var/lib/mongodb/db01 --logpath=/var/log/mongodb/db01.log --replSet=LocalRep --fork
- プロセス確認
$ ps aux | grep mongo
root 11292 1.8 14.8 1594552 93660 ? Sl 02:42 0:45 mongod --port=50001 --dbpath=/var/lib/mongodb/db02 --logpath=/var/log/mongodb/db02.log --replSet=LocalRep --fork
root 11370 1.6 8.2 1078072 52004 ? Sl 02:42 0:39 mongod --port=50002 --dbpath=/var/lib/mongodb/db03 --logpath=/var/log/mongodb/db03.log --replSet=LocalRep --fork
root 11616 3.0 7.6 1590748 48012 ? Sl 03:23 0:00 mongod --port=50000 --dbpath=/var/lib/mongodb/db01 --logpath=/var/log/mongodb/db01.log --replSet=LocalRep --fork
root 11706 0.0 0.1 103304 888 pts/1 R+ 03:23 0:00 grep mongo
- curlコマンド実行
$ curl http://localhost:8080/api/v1/login
{"statusCode":0,"message":"Success."}
这个也能够无错误地执行。
- DBの中身確認
# とりあえずDB02に接続
$ mongo --port 50001
LocalRep:SECONDARY>
## DB01が起動したのでPRIMARYからSECONDARYに変わっている
# DB01に接続
$ mongo --port 50000
LocalRep:PRIMARY> use sampledb;
# 最新のログインログを1件を取得する
LocalRep:PRIMARY> db.login.log.find().sort({'loginDate':-1}).limit(1);
{ "_id" : ObjectId("59bb39c0f969de2d2a4ae19f"), "userSchemaId" : ObjectId("59bb33b17748fa4368bf8874"), "loginDate" : ISODate("2017-09-15T02:24:00.067Z"), "__v" : 0 }
嗯,数据正确地输入了!
我试着关闭DB02。
顺便检查一下当意外关闭DB02时的影响。
- DB02落とすコマンド
sudo ps aux | grep db02 | grep -v 'grep db02' | awk '{print $2}' | xargs kill -9
- プロセス確認
$ ps aux | grep mongo
root 11616 1.9 8.6 1591776 54304 ? Sl 03:23 0:04 mongod --port=50000 --dbpath=/var/lib/mongodb/db01 --logpath=/var/log/mongodb/db01.log --replSet=LocalRep --fork
root 11370 1.6 8.2 1078072 52004 ? Sl 02:42 0:42 mongod --port=50002 --dbpath=/var/lib/mongodb/db03 --logpath=/var/log/mongodb/db03.log --replSet=LocalRep --fork
root 11726 0.0 0.1 103304 876 pts/1 R+ 03:26 0:00 grep mongo
- curlコマンド実行
$ curl http://localhost:8080/api/v1/login
{"statusCode":0,"message":"Success."}
这也可以无错误地执行。
- DBの中身確認
# とりあえずDB02に接続し、落ちていることを確認
$ mongo --port 50001
exception: connect failed
# DB01に接続
$ mongo --port 50000
LocalRep:PRIMARY> use sampledb;
# 最新のログインログを1件を取得する
LocalRep:PRIMARY> db.login.log.find().sort({'loginDate':-1}).limit(1);
{ "_id" : ObjectId("59bb3aeaf969de2d2a4ae1a0"), "userSchemaId" : ObjectId("59bb33b17748fa4368bf8874"), "loginDate" : ISODate("2017-09-15T02:28:58.602Z"), "__v" : 0 }
嗯,看起来进展顺利!不论哪个数据库断开,程序都能正常运行。
关于连接事项
我在一个不太熟悉的情况下使用,但实际运行时连接数无限增加,虽然程序处理已经结束,但连接却没有断开,出现了无限增加的问题。(最终导致了数据库崩溃的问题…)
原因是因为在 Model 类的构造函数中进行了数据库连接,并且没有进行单例管理,因此每次实例化 Model 时都会建立连接,而且没有断开连接的处理,所以处于最糟糕的情况…
// 以下実装イメージ
class BaseModel {
constructor() {
// ここでDBへのコネクションを作成
this.db = xxxxxx;
}
}
class UserModel extends BaseModel {
get() {
// ここでDBからデータを取得
return this.db.model.find(xxxxxx);
}
}
这就像是一个形象。嗯,不行不行呢…
这次的动作中,通过使用单例模式来管理数据库连接,当Node进程启动时会一直保持连接,但实际上不确定这样做是否好…
对于连接管理等方面还不是很了解,暂时只知道它能够工作…
嗯,真是令人无法言喻。
暂时先将用于验证的示例应用程序上载到了以下位置。
GitHub: megadreams14/mongoDB_failover_test
总结
這次我們使用Node.js/Express架構來簡單驗證了MongoDB的故障轉移功能。
在設定和編寫程式時遇到了一些困難,但總體上可以輕鬆地進行連接。
我仍然不太清楚程式端是如何判斷主要和次要的,以及在處理諮詢和寫入資料庫時如何管理連線以及如何對應故障轉移。
還有很多我還不了解的事情,所以希望能繼續學習…