春节前闲来无事,整理了一下以前学习的Node.js和NoSql数据库的资料,总结了自己一路踩过来的坑,希望能对初学者有帮助。
在开发环境搭建和实例编写之前,还是需要先了解一下基本概念。
参考文章:
1. 目前流行的几个NoSql数据库对比
2. 深入了解MongoDB的mmap(内存映射文件)的原理
3. MongoDB数据库命令
4. Node.js的概念
5. 事件循环,Node.js的核心概念
6. JavaScript:彻底理解同步,异步和事件循环
7. nodejs async库使用
长篇大论的概念我就不复述了,用几句简单的话来概括一下重点:
1. NoSql数据库
NoSql=not only sql,与传统关系型数据库的最大区别在于,不能JOIN,且保存的数据没有固定的格式,常见的如MongoDB以key-value形式进行存储。
与传统数据库如MySql是硬盘数据库+关系型数据库相反,NoSql是内存数据库+非关系型数据库。
硬盘数据库的数据可靠性高,因为数据是直接写入硬盘。内存数据库读写速度更快,因为是直接通过内存进行存取,但是需要定期写回到硬盘,这就导致在存入内存到写入硬盘的时间内,如果系统崩溃,会导致数据丢失,可靠性会降低,虽然有通过日志在启动后进行恢复的方法,但是多少还是会影响体验。
所以到底是用关系型数据库还是NoSql数据库,还是要根据场景进行分析,不能一概而论。
2. MongoDB
内存映射文件是OS通过mmap在内存中创建一个文件,并直接映射到虚拟内存。在数据操作的时候,OS会把需要操作的数据直接映射到物理内存中。
MongoDB通过journal进行故障恢复和持久化,系统在内存中分配一块区域给journal使用,称为private view。每隔100ms会刷新privateview到journal。因此发生故障时,丢失的也只是100ms的数据而已。但是开启journal后,虚拟内存的使用也会倍增。
3. Node.js
Node是服务器程序。本身运行的是Google的V8引擎,用于解释Javascript。它可以直接用Javascript写后台程序,使前台开发者也能快速开发后台代码,即HTML+Javascript+MongoDB,而无需像Tomcat的前台(HTML+Javascript)+后台(Java)+数据库(MySql)。
与Java等不同的是它连接到服务器的方式:
Java是多线程的,一个连接就是一个新线程,每个线程需要2M的配套内存的话,8G内存就只能维持4000个用户,想要达到更高的连接数只能增加增加服务器。
Node.js是单线程的,它支持数万的并发,在处理非计算型高密集I/O请求时,可以有更好的表现。
为什么是非计算型?
因为Node.js虽然称为单线程,但并不意味着只有一个线程在进行处理,单的是主线程,还有其他如Ajax线程,MongoDB线程进行其他的处理。
如当主线程调用Ajax线程时,主线程并不是等待Ajax线程返回结果,而是直接跳到下一步操作,Ajax线程结束后会去执行它的回调函数,这是同步和异步的差异。
此时,主线程可以去做其他的请求。
借一张图来说明处理的流程:
但是如果主线程的计算量很大,由于是单线程的关系,其他请求就无法进入主线程了。
为什么用内存数据库(MongoDB)?
用Node.js就是追求性能和并发,而传统的关系型数据库(如MySql)在读写性能方面不如内存数据库,如果是Node.js+关系型数据库的话,数据库这边势必将称为I/O的瓶颈。
--------------------------------------------------------------------------------分割线-----------------------------------------------------------------------------------
接下来是环境搭建和实例
一. MongoDB环境搭建:
1. MongoDB下载地址:http://dl.mongodb.org/dl/win32/x86_64
2. 解压在任意路径, D:\mongodb
3. 在mongodb目录下创建data文件夹,在data文件夹下创建db和log文件夹,在log文件夹下创建MongoDB.log文件。
目录结构如下:
4. 在cmd下进行安装:
d:\mongodb\bin>mongod -dbpath "d:\mongodb\data\db"
6.DB操作:
双击bin下的mongod.exe(启动mongodb)
然后运行mongo.exe,界面如下:
7. MongoDB中的collection相当于table,与关系型数据库的db和table不同的是,mongodb的db和collection如果不存在的话,会自动创建,而无需create table。
简单介绍几个命令:
7.1 指定当前DB:use {dbName};
如:use sun;(指定sun为当前db)
7.2 查询collection下的所有数据:db.{collection}.find();
如:db.test.find();(显示test这个collection下所有的数据)
7.3 删除当前数据库:db.dropDatabase();(注意是当前库哦!)
7.4 删除指定collection:db.{collection}.drop();
7.5 显示所有db:show dbs;
7.6 显示当前db下的所有collection:show collections;
7.7 插入数据:db.{collection}.insert({'{key1}':'{value1}', '{key2}':'{value2}'});
如:db.test.insert({'name':'aaa', 'age':10});
7.8 更新数据:db.{collection}.update({'{type1}':'{value1}'}, {$set:{'{type2}':'{value2}'}});
如:db.test.update({'name':'aaa'}, {$set, {'age':20}});
7.9 删除数据:db.{collection}.remove({'{key1}':'{value1}', '{key2}':'{value2}'});
如:db.test.remove({'name':'aaa'});
7.10 查询指定条件的数据:db.{collection}.find({'{key1}':'{value1}', '{key2}':'{value2}'});
如:db.test.find({'name':'aaa'});
其他命令可查询Mongo教程
例:
二. 开发环境WebStorm搭建:
去官网下载最新版的WebStorm并安装
三. Node.js环境搭建:
去官网下载最新的Node.js并安装
四. 创建Node.js+Express项目并启动服务器:
1. 启动WebStorm,File->New->Project,选择Node.js Express App,Location是项目路径,Template是页面模板和解析引擎。
2. 创建后的项目结构如下(下图有我增加的文件和文件夹,但是整体结构改动不大),可直接在左下角Run窗口按绿三角启动,显示如下图的文字则表示服务启动成功。
3. 输入http://localhost:3000/,显示如下(显示内容我也改成自己的了):
4. 至此,简单的Express项目创建成功,但是有些文件的内容还是需要说明一下。
bin\www:
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require('../app'); // 服务启动后,需要用到的Module在app下面定义
var debug = require('debug')('nodejsproject:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000'); // 端口3000就是在这里定义的
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
var express = require('express'); // require类似于Java的import,需要用到其他包里面的东西,就把它引用过来
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
///=======路由信息 (接口地址)开始 存放在./routes目录下===========//
var index = require('./routes/index'); //home page接口
var users = require('./routes/users'); //用户接口
var sun = require('./routes/sun'); //sun接口
var mongoDelete = require('./routes/mongodb/delete'); //mongo接口
var mongoFind = require('./routes/mongodb/find'); //mongo接口
var mongoInsert = require('./routes/mongodb/insert'); //mongo接口
var mongoUpdate = require('./routes/mongodb/update'); //mongo接口
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', index); //在app中注册routes该接口,当输入根目录(http://localhost:3000)的时候,调用index.js
app.use('/users', users); //在app中注册users接口
app.use('/sun', sun); //在app中注册sun接口
app.use('/mongo', mongoDelete); //在app中注册mongo接口
app.use('/mongo', mongoFind); //在app中注册mongo接口
app.use('/mongo', mongoInsert); //在app中注册mongo接口
app.use('/mongo', mongoUpdate); //在app中注册mongo接口
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
{
"name": "nodejsproject",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"assert": "^1.4.1",
"async": "^2.6.0",
"body-parser": "~1.18.2",
"cookie-parser": "~1.4.3",
"debug": "~2.6.9",
"ejs": "~2.5.7",
"express": "~4.15.5",
"mongodb": "~3.0.2",
"morgan": "~1.9.0",
"serve-favicon": "~2.4.5"
}
}
既然说到了引用Module,那就顺便把模块的添加也说一下,比如添加mongodb模块,命令:
npm install --save -dev mongodb
“--save -dev”的用处是把引用模块的信息在安装成功后自动写入package.json,如果你想自己写,那么只要用
npm install mongodb
即可。
<%= title %>
<%= title %>
Welcome to <%= title %>
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Sun test Express' }); // 打开index.ejs页面,并将指定内容赋予变量title
});
module.exports = router;
五. 创建一个Node.js的接口:
1. 创建user.js,定义user对象:
function User() {
this.name;
this.city;
this.age;
}
module.exports = User;
var URL = require('url');
var User = require('./user'); // 用到其他文件的时候,必须require引用过来
var express = require('express');
var router = express.Router();
/* GET users listing. */
router.get('/', function(req, res, next) {
res.send('respond with a resource');
});
//http://localhost:3000/users/getUserInfo?id=1
router.get('/getUserInfo', function(req, res, next){
var user = new User();
var params = URL.parse(req.url, true).query;
if (params.id = '1'){
user.name = "test";
user.age = "10";
user.city = "上海";
}else{
user.name = "sun";
user.age = "20";
user.city = "东京";
}
var response = {status:1, data:user};
res.send(JSON.stringify(response));
})
module.exports = router;
var users = require('./routes/users'); //用户接口
app.use('/users', users); //在app中注册users接口
后台接口调用:getUserInfo
六. 使用Node.js+MongoDB,实现增删改查:
这个例子是基于上面的接口实例,通过前台页面使用jquery异步调用node.js接口的方式实现的。
1. 项目结构说明:
先说明一下MongoDB的结构,
DB名:sun
Collection有两个,test和other,test中保存name和age,other中保存name和job。
把他们分开是为了说明async.map的时候更方便。
这里会详细说明一下查询find的实现,增删改相对容易。
在创建Collection的时候,可以指定唯一索引。默认是只有id唯一。
db.collection.createIndex( , { unique: true } )
db.test.createIndex({name:1},{unique:true})
下面是两个Collection里面准备的数据,在新增做好以后就可以自己插入了。
项目结构如下:
新增:
routes/mongodb/find.js <- 查询
routes/mongodb/insert.js <- 新增
routes/mongodb/update.js <-更新
routes/mongodb/delete.js <-删除
routes/mongodb/collectionTest.js <-Collection对象
routest/sun.js <-sun.ejs对应的js文件(类似于index.js)
views/sun.ejs <-页面(类似于index.ejs)
2. collectionTest.js:
function CollectionTest() {
this.id;
this.name;
this.age;
this.job;
}
module.exports = CollectionTest;
3. find.js:
var mongodb = require('mongodb')
var MongoClient = require('mongodb').MongoClient;
var DB_CONN_STR = 'mongodb://localhost:27017/sun';
var URL = require('url');
var CollectionTest = require('./collectionTest');
var express = require('express');
var router = express.Router();
var async = require('async');
var selectData = function(client, params, callback) {
//连接到表
var db = client.db('sun');
var collectionTest = db.collection('test');
var collectionOther = db.collection('other');
//查询数据
var whereStr;
if (params.name) whereStr = {"name" : params.name};
collectionTest.find(whereStr, function(error, results){
if (error) throw error;
results.toArray(function(err, arr){
if (error) throw error;
// async.map会将循环处理完以后,统一执行callback而不像其他异步调用执行分别执行
async.map(arr, function(item, callback){
var whereStr = {"name": item.name};
collectionOther.find(whereStr, function(error, result){
result.toArray(function(err, arrJob){
console.log(arrJob);
console.log("------------------------");
if (arrJob){
item.job = arrJob[0].job;
console.log(item);
console.log("*************************");
}
callback(null, item);
});
})
}, function(err, result){
// result是上表面item组成的数组
callback(result);
})
});
});
}
router.get('/find', function(req, res, next){
var params = URL.parse(req.url, true).query;
MongoClient.connect(DB_CONN_STR, function(err, client) {
console.log("连接成功!");
selectData(client, params, function(result) {
res.send(JSON.stringify(result));
client.close();
console.log("连接断开!");
});
});
})
module.exports = router;
增删改和页面的jquery之类的没啥好说的,跟前台开发差不多,详细说一下在find.js中遇到的问题。
初用nodes.js的时候可能对异步调用的处理顺序有点不太习惯。
比如我在接口中,一开始是这样写的:
router.get('/find', function(req, res, next){
var params = URL.parse(req.url, true).query;
MongoClient.connect(DB_CONN_STR, function(err, client) {
console.log("连接成功!");
selectData(client, params, function(result) {
});
});
res.send(JSON.stringify(result));
client.close();
console.log("连接断开!");
})
想象中的输出结果:
连接成功!
连接断开!
连接断开!
连接成功!
由此想到,原来node.js在调用其他线程时,不是等待他们执行完,而是做自己的下一步,等到其他线程的处理完成后,再去调用回调函数。
简单的处理当然没问题,但是遇到复杂的循环调用其他线程时,就很麻烦了。
比方说在find.js中,我需要将test中的name和age取到以后,再循环所有数据,以name为key,去检索other并取出job后一起返回,不可能是每次检索都去调用callback回调函数。
因此我用到了async中的map函数。
安装async的命令:
npm install --save -dev async;
async库的命令的解析可以参照这篇文章:http://blog.csdn.net/dai_jing/article/details/47058579
我这里用到了map方法,map内循环的结果以数组形式保存后,一并返回给callback。
async.map的使用:
// async.map会将循环处理完以后,统一执行callback而不像其他异步调用执行分别执行
async.map(arr, function(item, callback){
var whereStr = {"name": item.name};
collectionOther.find(whereStr, function(error, result){
result.toArray(function(err, arrJob){
console.log(arrJob);
console.log("------------------------");
if (arrJob){
item.job = arrJob[0].job;
console.log(item);
console.log("*************************");
}
callback(null, item);
});
})
}, function(err, result){
// result是上表面item组成的数组
callback(result);
})
var mongodb = require('mongodb')
var MongoClient = require('mongodb').MongoClient;
var DB_CONN_STR = 'mongodb://localhost:27017/sun';
var URL = require('url');
var express = require('express');
var router = express.Router();
function insertData(client, params, callback)
{
var db = client.db("sun");
var connectionTest = db.collection("test");
var testData = {"name":params.name,"age":params.age};
connectionTest.insert(testData,function(error, result){
if(error){
console.log('Error:'+ error);
}else{
console.log(result.result.n);
}
callback(result.result.n);
});
var connectionOther = db.collection("other");
var otherData = {"name":params.name,"job":params.job};
connectionOther.insert(otherData,function(error, result){
if(error){
console.log('Error:'+ error);
}else{
console.log(result.result.n);
}
});
}
router.get('/insert', function(req, res, next){
var params = URL.parse(req.url, true).query;
MongoClient.connect(DB_CONN_STR, function(err, client) {
console.log("连接成功!");
insertData(client, params, function(result) {
res.send("Delete Success:" + result);
client.close();
console.log("连接断开!");
});
});
})
module.exports = router;
var mongodb = require('mongodb')
var MongoClient = require('mongodb').MongoClient;
var DB_CONN_STR = 'mongodb://localhost:27017/sun';
var URL = require('url');
var express = require('express');
var router = express.Router();
function updateData(client, params, callback)
{
var db = client.db('sun');
var connectionTest = db.collection('test');
var whereData = {"name":params.oldName}
var updateDat = {$set: {"name":params.newName}}; //如果不用$set,替换整条数据
connectionTest.update(whereData, updateDat, function(error, result){
if (error) {
console.log('Error:'+ error);
}else{
console.log(result);
}
callback(result.result.n);
});
var connectionOther = db.collection('other');
connectionOther.update(whereData, updateDat, function(error, result){
if (error) {
console.log('Error:'+ error);
}else{
console.log(result);
}
});
}
router.get('/update', function(req, res, next){
var params = URL.parse(req.url, true).query;
MongoClient.connect(DB_CONN_STR, function(err, client) {
console.log("连接成功!");
updateData(client, params, function(result) {
res.send("Delete Success:" + result);
client.close();
console.log("连接断开!");
});
});
})
module.exports = router;
6. delete.js:
var mongodb = require('mongodb')
var MongoClient = require('mongodb').MongoClient;
var DB_CONN_STR = 'mongodb://localhost:27017/sun';
var URL = require('url');
var express = require('express');
var router = express.Router();
function deleteData(client, params, callback)
{
var db = client.db('sun');
var devices = db.collection('test');
var data = {"name":params.name};
devices.remove(data, function(error, result){
if (error) {
console.log('Error:'+ error);
}else{
console.log(result.result.n);
}
callback(result.result.n);
})
}
router.get('/delete', function(req, res, next){
var params = URL.parse(req.url, true).query;
MongoClient.connect(DB_CONN_STR, function(err, client) {
console.log("连接成功!");
deleteData(client, params, function(result) {
res.send("Delete Success:" + result);
client.close();
console.log("连接断开!");
});
});
})
module.exports = router;
7. sun.js:
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('sun', { title: 'Sun'});
});
module.exports = router;
8. sun.ejs:
<%= title %>
<%= title %>'s Test for nodeJS + MongoDB + Express + Async
Welcome to <%= title %>
-
后台接口调用:getUserInfo
-
MongoDB新增数据:Name: Age: Job:
-
MongoDB修改数据:NameOld: NameNew:
-
MongoDB删除数据:Name:
-
从MongoDB检索数据:Name: Find
整个实例在这里下载:NodeJS+MongoDB实例