Node.js® 是一个基于 Chrome V8 引擎 的 JavaScript 运行时。
Node.js 是一个能够在服务器端运行JavaScript 的开放源代码 、跨平台 JavaScript 运行环境 。
Node.js采用了事件驱动 、非阻塞 和 异步输入输出 模型等技术来提高性能 ,可优化应用程序的传输量和规模。这些技术通常用于数据密集的实时应用程序。
Node.js以单线程运行,使用非阻塞I/O调用,这样既可以支持数以万计的并发连线,又不会因多线程本身的特点而带来麻烦。众多请求只使用单线程的设计意味着可以用于创建高并发应用程序。 Node.js应用程序的设计目标是任何需要操作I/O的函数都使用回调函数 。
这种设计的缺点是,如果不使用cluster 、StrongLoop Process Manager 或pm2 等模块,Node.js就难以处理多核或多线程等情况。
关键词 nvm:Node.js版本控制器,用于切换多版本的Node.js环境。
npm:(全称 Node Package Manager,即“node包管理器”)是Node.js 默认的、以JavaScript 编写的软件包管理系统 。
Node.js Express 框架: 相当于Python的Flask框架
Node.js多进程:cluster 、pm2
Node.js核心内置模块:
1 2 3 4 5 6 7 8 http:提供HTTP服务器功能。 url:解析URL。 fs:与文件系统交互。 querystring:解析URL的查询字符串。 child_process:新建子进程。 util:提供一系列实用小工具。 path:处理文件路径。 crypto:提供加密和解密功能,基本上是对OpenSSL的包装。
阻塞对比非阻塞一览 本概论涵盖了在 Node.js 中 阻塞 and 非阻塞 的区别,同时也会牵涉到时间轮询和 libuv 方面,不需要先行了解这些方面的知识也可以继续阅读。我们假定读者对于 JavaScript 语言和 Node.js 的回调机制有一个基本的了解。
“I/O” 指的是系统磁盘和由 libuv 支持的网络之间的交互。
阻塞 阻塞 是说 Node.js 中其它的 JavaScript 命令必须等到一个非 JavaScript 操作完成之后才可以执行。这是因为当 阻塞 发生时,事件机制无法继续运行JavaScript。
在 Node.js 中,JavaScript由于 CPU 密集操作而表现不佳。而不是等待非 JavaScript操作 (例如I/O)。这被称为 阻塞 。在 Node.js 基本类库中,使用 libuv 的同步方法大多数都是 阻塞 的。原生方法也可能是 阻塞 的。
所有在 Node.js 中提供的 I/O 方法也包括异步版本,它们都是 非阻塞 的,接受回调函数。一些方法同时也具备 阻塞 功能,它们的名字结尾都以 Sync
结尾。
代码比较 阻塞 方法执行起来是 同步地 ,但是 非阻塞 方法执行起来是 异步地 。 如果你使用文件系统模块读取一个文件,同步方法看上去如下:
1 2 const fs = require ('fs' );const data = fs.readFileSync('/file.md' );
这是一个与之功能等同的 异步 版本示例:
1 2 3 4 const fs = require ('fs' );fs.readFile('/file.md' , (err, data) => { if (err) throw err; });
第一个示例看上去比第二个似乎简单些,但是有一个缺陷:第二行语句会 阻塞 其它 JavaScript 语句的执行直到整个文件全部读取完毕。注意在同步版本的代码中,任何异常都会抛出,会导致整个程序崩溃。在异步版本示例代码中,它由作者来决定是否抛出异常。
让我们扩展一点我们的同步代码:
1 2 3 4 const fs = require ('fs' );const data = fs.readFileSync('/file.md' ); console .log(data);
这是一个类似的,但是功能上不等同的异步代码示例版本:
1 2 3 4 5 6 const fs = require ('fs' );fs.readFile('/file.md' , (err, data) => { if (err) throw err; console .log(data); });
第一个示例代码中, console.log
将在 moreWork()
之前被调用。在第二个例子中, fs.readFile()
因为是 非阻塞 的,所以 JavaScript 会继续执行, moreWork()
将被首先调用。moreWork()
无需等待文件读完而先行执行完毕,这对于高效吞吐来说是一个绝佳的设计。
并行和吞吐 在 Node.js 中 JavaScript 的执行是单线程的,所以并行与事件轮询能力(即在完成其它任务之后处理 JavaScript 回调函数的能力)有关。任何一个企图以并行的方式运行的代码必须让事件轮询机制以非 JavaScript 操作来运行,像 I/O 操作。
举个例子,让我们思考一个案例:案例中每个对服务器的请求消耗 50 毫秒完成,其中的 45 毫秒又是可以通过异步操作而完成的数据库操作。选择 非阻塞 操作可以释放那 45 毫秒用以处理其它的请求操作。这是在选择 阻塞 和 非阻塞 方法上的重大区别。
Node.js 中的事件轮询机制和其它语言相比而言有区别,其它语言需要创建线程来处理并行任务。
把阻塞和非阻塞代码混在一起写的危险 在处理 I/O 问题时,有些东西必须避免。下面让我们看一个例子:
1 2 3 4 5 6 const fs = require ('fs' );fs.readFile('/file.md' , (err, data) => { if (err) throw err; console .log(data); }); fs.unlinkSync('/file.md' );
在以上的例子中, fs.unlinkSync()
极有可能在 fs.readFile()
之前执行,所以在真正准备开始读取文件前此文件就已经被删除了。一个更好的处理方法就是彻底让使它变得 非阻塞化 ,并且保证按照正确顺序执行:
1 2 3 4 5 6 7 8 const fs = require ('fs' );fs.readFile('/file.md' , (readFileErr, data) => { if (readFileErr) throw readFileErr; console .log(data); fs.unlink('/file.md' , (unlinkErr) => { if (unlinkErr) throw unlinkErr; }); });
以上代码在 fs.readFile()
用异步方式调用 fs.unlink()
,这就保证了执行顺序的正确。
第一个应用 app.js # require命令用于指定加载模块,加载时可以省略脚本文件的后缀名。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const http = require ('http' );const hostname = '127.0.0.1' ;const port = 3000 ;const server = http.createServer((req, res ) => { res.statusCode = 200 ; res.setHeader('Content-Type' , 'text/plain' ); res.end('Hello World\n' ); }); server.listen(port, hostname, () => { console .log(`Server running at http://${hostname} :${port} /` ); });
然后使用 node app.js
运行程序,访问 http://localhost:3000,你就会看到一个消息,写着“Hello World”。
Run Node.js in Docker 创建 Node.js 应用 首先,创建一个新文件夹以便于容纳需要的所有文件,并且在此其中创建一个 package.json
文件,描述你应用程序以及需要的依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 { "name" : "docker_web_app" , "version" : "1.0.0" , "description" : "Node.js on Docker" , "author" : "Neo <Neo42@mail.com>" , "main" : "server.js" , "scripts" : { "start" : "node app2mongo.js" }, "dependencies" : { "express" : "^4.16.3" , "mongodb" : "^3.1.6" } }
配合着你的 package.json
请运行 npm install
。如果你使用的 npm
是版本 5 或者之后的版本,这会自动生成一个 package-lock.json
文件,它将一起被拷贝进入你的 Docker 镜像中。
安装 MongoDB driver for Node.js. 1 npm install mongodb --save
App2mongo.js # 操作测试Mongodb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 const insertDocuments = function (db, callback ) { const collection = db.collection('documents' ); collection.insertMany([ {Neo : 42 }, {a : 2 }, {a : 3 } ], function (err, result ) { assert.equal(err, null ); assert.equal(3 , result.result.n); assert.equal(3 , result.ops.length); console .log("Inserted 3 documents into the collection Neo" ); callback(result); }); } const findDocuments = function (db, callback ) { const collection = db.collection('documents' ); collection.find({}).toArray(function (err, docs ) { assert.equal(err, null ); console .log("Found the following records" ); console .log(docs) callback(docs); }); } const MongoClient = require ('mongodb' ).MongoClient;const assert = require ('assert' );const url = 'mongodb://192.168.0.103:27017' ;const dbName = 'Neo42' ;MongoClient.connect(url, function (err, client ) { assert.equal(null , err); console .log("Connected correctly to server Neo42" ); const db = client.db(dbName); insertDocuments(db, function ( ) { findDocuments(db, function ( ) { client.close(); }); }); });
创建Dockerfile 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 FROM node:9 WORKDIR /usr/src/app COPY package*.json ./ RUN npm install COPY . . EXPOSE 8080 CMD [ "npm" , "start" ]
构建DockerImage 1 docker build -t Neo:42/node-web-app .
Run Docker 1 docker run -p 8080:8080 -d Neo:42/node-web-app
运行输出 1 2 3 4 5 6 7 8 9 10 11 12 Connected correctly to server Neo42 # 连接数据库 Inserted 3 documents into the collection Neo # 插入doc Found the following records # 查询 [ { _id: 5bb1e06dc2bf97000f0e1886, Neo: 42 }, { _id: 5bb1e06dc2bf97000f0e1887, a: 2 }, { _id: 5bb1e06dc2bf97000f0e1888, a: 3 }, { _id: 5bb1e2bb0c25dc000fa94345, Neo: 42 }, { _id: 5bb1e2bb0c25dc000fa94346, a: 2 }, { _id: 5bb1e2bb0c25dc000fa94347, a: 3 }, { _id: 5bb1e30bb78d09000f1ff341, Neo: 42 }, { _id: 5bb1e30bb78d09000f1ff342, a: 2 }, { _id: 5bb1e30bb78d09000f1ff343, a: 3 } ]
Node.js PM2 Runtime PM2 is a Production Runtime and Process Manager for Node.js applications with a built-in Load Balancer. It allows you to keep applications alive forever, to reload them without downtime and facilitate common Devops tasks.
Advanced, production process manager for Node.js
PM2 Features:高效管理多进程、日志分析记录简单的监控、集群扩展、部署方便等…..
PM2群集模式和重新加载操作可以避免停机时间。
Using PM2 with Docker 1 2 3 4 5 6 7 neo_42 ├── Dockerfile ├── ecosystem.config.js ├── package.json └── src 1 directory, 3 files
生成ecosystem.config.js
app.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 'use strict'; const express = require('express'); // Constants const PORT = 8080; const HOST = '0.0.0.0'; // App const app = express(); app.get('/', (req, res) => { res.send('Hello world\n'); }); app.listen(PORT, HOST); console.log(`Running on http://${HOST}:${PORT}`);
Dockerfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 FROM keymetrics/pm2:latest-alpine WORKDIR src/ # Bundle APP files COPY src src/ COPY package.json . COPY ecosystem.config.js . # Install app dependencies ENV NPM_CONFIG_LOGLEVEL warn RUN npm install --production # Expose the listening port of your app EXPOSE 8080 # Show current folder structure in logs #RUN ls -al -R CMD [ "pm2-runtime", "start", "ecosystem.config.js" ] #CMD ["pm2-runtime", "ecosystem.config.js", "--web"]
构建
1 docker build -t 'neo/pm2:v1' .
Run
1 docker run -p 80:8080 -d neo/pm2:v1
Pm2 Commands
1 2 docker exec -it 051 pm2 ls docker exec -it 051 pm2 monit
Pm2 Commands参考http://pm2.keymetrics.io/docs/usage/quick-start/
参考资料
Last updated: 2018-10-14 17:38:36