We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
首发于微信公众号《前端成长记》,写于 2019.05.06
Node缓存编辑和执行后的对象
文件拓展名分析,会调用fs模块同步阻塞式判断文件是否存在,依次.js、.node、.json。
如果是.node和.json,在传递给require时带上拓展名,会加快一点速度。同步配合缓存,可以大幅度缓解阻塞式调用的缺陷
javascript核心模块通过 process.binding('natives') 取出,编译成功的模块缓存到NativeModule._cache对象,文件模块缓存到Module._cache
;(function (name, definition) { // 检测上下文环境是否为AMD或CMD var hasDefine = typeof define === 'function' // 检测上下文环境是否为Node var hasExports = typeof module !== 'undefined' && module.exports if (hasDefine) { // 如果是AMD或CMD环境 define(definition) } else if (hasExports) { // 如果是Node环境,定义为Node模块 module.exports = definition() } else { // 执行结果挂载window上 this[name] = definition() } })('hello', function () { var hello = function () {} return hello })
事件循环,观察者,请求对象,I/O线程池构成了Node异步I/O模型的基本要素
idle观察者先于I/O观察者,I/O观察者先于check观察者。 idle观察者
check观察者
Node通过事件驱动的方式处理请求,无须为每一个请求创建额外的对应线程,可以省掉创建线程和销毁线程的开销,同时操作系统在调度任务时因为线程较少,上下文切换的代价很低。这使得服务器能够有条不紊地处理请求,即使在大量连接的情况下,也不受线程上下文切换开销的影响。
多异步之间协作方案
使用事件发布/订阅
var emitter = new events.Emitter() emitter.on('data', function (msg) { console.log(msg) }) emitter.emit('data', 'I'm a message')
也可再次封装,可参考 EventProxy 模块
通过队列去判断,是否达到阀值,达到就从队列中去取出执行
目前ES6 ES7 已经支持 Promise Async/Await 来实现,不需要再自行编写
分为新生代(存活时间较短)和老生代。 新生代采用 Scavenge 算法,内存一分为二,From - To。通过复制存活对象再进行交换,空间换时间。 老生代采用 Mark-Sweep & Mark-Compact ,Sweep 标记清除,Compact 对象移动(无内存碎片)
Buffer 内存不通过V8分配,而是从Node自行分配(突破大小限制)。所以V8回收的主要适合V8的堆内存。
大文件读取使用 createReadStream/createWriteStream 代替 readFile/writeFile ,pipe来通过文件流进行操作。
采用slab动态内存管理机制,固定大小的内存区域。
+= 内隐藏了 toString() 操作,在宽字节中文可能会出现问题。
var fs = require('fs') var iconv = require('iconv-lite') var res = fs.createReadStream('test.md') var chunks = [] var size = 0 res.on('data', function (chunk) { chunks.push(chunk) size += chunk.length }) res.on('end', function () { var buf = Buffer.concat(chunks, size) var str = iconv.decode(buf, 'utf8') })
设置 highWaterMark 值可以提高二进制文件的读取速度
在OSI模型上属于传输层协议,由物理层(网络物理硬件)、数据链结层(网络特有的链路接口)、网络层(IP)、传输层(TCP/UDP)、会话层(通信连接/维持会话)、表示层(加密/解密等)、应用层(HTTP/SMTP/IMAP等)组成。
TCP是面向连接的,典型的3次握手行程会话, 客户端请求连接 -> 服务端响应 -> 客户端开始传输,通过套接字连接。
TCP中会话基于连接完成,如果客户端需要与另一个TCP服务通信需要另创建一个套接字(socket)。UDP一个套接字可以与多个UDP服务通信。目前广泛应用,DNS服务基于此实现。
socket.send(buf, offset, length, port, address, [callback])
HTTP请求报文和响应报文都包括报文头和报问题。HTTP服务继承自TCP服务器,可以与多个客户端保持连接,由于采用事件驱动,不会为每个连接创建额外的线程或进程,所以可以保持很低的内存占用,进而满足高并发。
WebSocket客户端基于事件的编程模型与Node中自定义事件相差无几;WebSocket实现了客户端与服务器端之间的长连接,而Node事件驱动的方式十分擅长与大量的客户端保持高并发连接。
优势:
Node中父进程在实际创建子进程之前,会创建IPC通道并监听它,然后才真正创建出子进程,并通过环境变量(NODE_CHANNEL_FD)告诉子进程这个IPC通道的文件描述符。子进程在启动的过程中,根据文件描述符去连接这个已存在的IPC通道,从而完成父子进程之间的链接。
只有启动的子进程是Node进程时,子进程才会根据环境变量去连接IPC通道,对于其他类型的子进程则无法实现进程间通信,除非其他进程也按约定去连接这个已经创建好的IPC通道
// 主进程代码 var fork = require('child_process').fork var cpus = require('os').cpus() var server = require('net').createServer() server.listen(1337) var limit = 10 var during = 60000 var restart = [] var isTooFrequently = function(){ var time = Date.now() var length = restart.push(time) if (length > limit) { restart = restart.slice(limit * -1) } return restart.length >= limit && restart[restartl.length - 1] - restart[0] < during } var workers = {} var createWorker = function () { if (isTooFrequently()) { process.emit('giveup', length, during) return; } var worker = fork(__dirname + '/worker.js') worker.on('message', function (message) { if (message.act === 'suicide') { createWorker() } }) worker.on('exit', function () { console.log('Worker ' + worker.pid + ' exited.') delete workers[worker.pid] }) worker.send('server', server) workers[worker.pid] = worker console.log('Create Worker. pid: ' + worker.pid) } for(var i = 0; i < cpus.length; i++) { createWorker() } process.on('exit', function (){ console.log('process exiting...') for(var pid in workers) { workers[pid].kill() } })
// 业务逻辑 worker.js var http = require('http') var server = http.createServer(function(req, res){ res.writeHead(200, {'Content-Type': 'text/plain'}) res.end('handled by child, pid is ' + process.pid + '\n') throw new Error('throw exception') }) var worker process.on('message', function (m, tcp) { if (m === 'server') { worker = tcp worker.on('connection', function (socket) { server.emit('connection', socket) }) } }) process.on('uncaughtException', function () { process.send({act: 'suicide'}) worker.close(function () { process.exit(1) }) setTimeout(() => { process.exit(1) }, 5000); })
通过 assert 模块做断言
测试用例、测试报告、测试覆盖率、mock、工程化和自动化(travis-ci)
RT:响应时间 TPS:吞吐量,在单位时间内处理请求的数量 QPS:每秒查询率 并发数:并发用户数是指系统可以同时承载的正常使用系统功能的用户的数量
|—— History.md // 项目改动历史 |—— INSTALL.md // 安装说明 |—— Makefile // Makefile文件 |—— benchmark // 基准测试 |—— controllers // 控制器 |—— lib // 没有模块化的文件牡蛎 |—— middlewares // 中间件 |—— package.json // 包描述文件,项目依赖配置等 |—— proxy // 数据代理目录,类似MVC中的M |—— test // 测试目录 |—— tools // 工具目录 |—— views // 视图目录 |—— routes.js // 路由注册表 |—— dispatch.js // 多进程管理 |—— README.md // 项目说明文件 |—— assets // 静态文件目录 |—— assets.json // 静态文件与CDN路径的映射文件 |—— bin // 可执行脚本 |—— config // 配置目录 |—— logs // 日志目录 |—— app.js // 工作进程
可以在主进程启动时将进程ID写到pid文件中,在脚本停止或者重启应用时通过kill给进程发SIGTERM信号,收到信号删除该pid文件并退出进程。
# 完整应用启动、停止和重启脚本 #!/bin/sh DIR=`PWD` NODE=`which node` # get action ACTION = $1 # help usage() { echo "Usage: ./appct1.sh {start|stop|restart}" exit 1; } get_pid() { if [ -f ./run/app.pid]; then echo `cat ./run/app.pid` fi } # start app start() { pid = `get_pid` if [ l -z $pid]; then echo 'server is already running' else $NODE $DIR/app.js 2>&1 & echo 'server is running' fi } # stop app stop() { pid=`get_pid` if [ -z $pid ]; then echo 'server not running' else echo 'server is stopping...' kill -1$ $pid echo "server stopped !" fi } # restart app restart() { stop sleep 0.5 echo ==== start } case "$ACTION" in start) start ;; stop) stop ;; restart) restart ;; *( usage ;; esac
拆分原则:做专一的事、让擅长的工具做擅长的事情、将模型简化、将风险分离、缓存
可以将静态资源和请求让nginx或者cdn处理,node只处理动态内容
redis或memcached
cluster/pm/forever
因为读取数据库速度远远高于写入的速度,通常会进行数据库的读写分离,将数据库进行主从设计,这样读数据操作不再受到写入的影响
访问日志
异常日志
日志格式化记录
日志写入可以在线写,但建议进行离线数据分析,分析完成后同步到数据库。因为数据库写入会有一系列处理,消费速度低于写入速度,导致内存泄漏
nodemailer可实现
通过短信服务平台
目前尽量部署在虚拟机上,避免实体机上多服务器同时停止服务
Node能够通过协议与已有的系统很好地异构共存。
(完)
本文为原创文章,可能会更新知识点及修正错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验 如果能给您带去些许帮助,欢迎 ⭐️star 或 ✏️ fork (转载请注明出处:https://chenjiahao.xyz)
The text was updated successfully, but these errors were encountered:
No branches or pull requests
Node简介
Node应用场景
模块机制
Node模块实现
文件定位
文件拓展名分析,会调用fs模块同步阻塞式判断文件是否存在,依次.js、.node、.json。
编译过程
javascript核心模块通过 process.binding('natives') 取出,编译成功的模块缓存到NativeModule._cache对象,文件模块缓存到Module._cache
兼容多种模块规范
异步I/O
Node异步I/O模型基本要素
事件循环,观察者,请求对象,I/O线程池构成了Node异步I/O模型的基本要素
观察者模式
idle观察者先于I/O观察者,I/O观察者先于check观察者。
idle观察者
check观察者
Node高性能原因之一
Node通过事件驱动的方式处理请求,无须为每一个请求创建额外的对应线程,可以省掉创建线程和销毁线程的开销,同时操作系统在调度任务时因为线程较少,上下文切换的代价很低。这使得服务器能够有条不紊地处理请求,即使在大量连接的情况下,也不受线程上下文切换开销的影响。
异步编程
EventEmitter
多异步之间协作方案
使用事件发布/订阅
也可再次封装,可参考 EventProxy 模块
异步并发控制
通过队列去判断,是否达到阀值,达到就从队列中去取出执行
内存控制
V8内存分类
V8的垃圾回收算法
分为新生代(存活时间较短)和老生代。
新生代采用 Scavenge 算法,内存一分为二,From - To。通过复制存活对象再进行交换,空间换时间。
老生代采用 Mark-Sweep & Mark-Compact ,Sweep 标记清除,Compact 对象移动(无内存碎片)
内存泄漏常见原因
内存泄漏排查
Buffer
Buffer内存分配
采用slab动态内存管理机制,固定大小的内存区域。
正确拼接buffer
网络编程
TCP 传输控制协议
在OSI模型上属于传输层协议,由物理层(网络物理硬件)、数据链结层(网络特有的链路接口)、网络层(IP)、传输层(TCP/UDP)、会话层(通信连接/维持会话)、表示层(加密/解密等)、应用层(HTTP/SMTP/IMAP等)组成。
UDP 用户数据包协议
TCP中会话基于连接完成,如果客户端需要与另一个TCP服务通信需要另创建一个套接字(socket)。UDP一个套接字可以与多个UDP服务通信。目前广泛应用,DNS服务基于此实现。
HTTP 超文本传输协议
HTTP请求报文和响应报文都包括报文头和报问题。HTTP服务继承自TCP服务器,可以与多个客户端保持连接,由于采用事件驱动,不会为每个连接创建额外的线程或进程,所以可以保持很低的内存占用,进而满足高并发。
WebSocket
WebSocket客户端基于事件的编程模型与Node中自定义事件相差无几;WebSocket实现了客户端与服务器端之间的长连接,而Node事件驱动的方式十分擅长与大量的客户端保持高并发连接。
优势:
进程
Node中父进程在实际创建子进程之前,会创建IPC通道并监听它,然后才真正创建出子进程,并通过环境变量(NODE_CHANNEL_FD)告诉子进程这个IPC通道的文件描述符。子进程在启动的过程中,根据文件描述符去连接这个已存在的IPC通道,从而完成父子进程之间的链接。
集群稳定,利用上CPU多核,多工作进程存活状态管理,平滑重启,重启限量阀值
测试
单元测试
通过 assert 模块做断言
测试用例、测试报告、测试覆盖率、mock、工程化和自动化(travis-ci)
性能测试
RT:响应时间
TPS:吞吐量,在单位时间内处理请求的数量
QPS:每秒查询率
并发数:并发用户数是指系统可以同时承载的正常使用系统功能的用户的数量
产品化
项目工程化
|—— History.md // 项目改动历史
|—— INSTALL.md // 安装说明
|—— Makefile // Makefile文件
|—— benchmark // 基准测试
|—— controllers // 控制器
|—— lib // 没有模块化的文件牡蛎
|—— middlewares // 中间件
|—— package.json // 包描述文件,项目依赖配置等
|—— proxy // 数据代理目录,类似MVC中的M
|—— test // 测试目录
|—— tools // 工具目录
|—— views // 视图目录
|—— routes.js // 路由注册表
|—— dispatch.js // 多进程管理
|—— README.md // 项目说明文件
|—— assets // 静态文件目录
|—— assets.json // 静态文件与CDN路径的映射文件
|—— bin // 可执行脚本
|—— config // 配置目录
|—— logs // 日志目录
|—— app.js // 工作进程
部署流程
可以在主进程启动时将进程ID写到pid文件中,在脚本停止或者重启应用时通过kill给进程发SIGTERM信号,收到信号删除该pid文件并退出进程。
性能
拆分原则:做专一的事、让擅长的工具做擅长的事情、将模型简化、将风险分离、缓存
可以将静态资源和请求让nginx或者cdn处理,node只处理动态内容
redis或memcached
cluster/pm/forever
因为读取数据库速度远远高于写入的速度,通常会进行数据库的读写分离,将数据库进行主从设计,这样读数据操作不再受到写入的影响
日志
访问日志
异常日志
日志格式化记录
监控报警
报警实现
nodemailer可实现
通过短信服务平台
稳定性
目前尽量部署在虚拟机上,避免实体机上多服务器同时停止服务
异构共存
Node能够通过协议与已有的系统很好地异构共存。
(完)
The text was updated successfully, but these errors were encountered: