I/O设备:与计算机进行输入输出数据传输的硬件设备(机械部分)
设备控制器:控制IO设备与CPI的通信的电子设备
IO设备 <-> 设备控制器 <-> CPU
设备控制器的组成
第5章
5.1 V8的内存限制
what: 只能使用部分内存,不能使用计算机的全部内存。(64位里为1.5G,32位里为0.7G)
why:因V8的垃圾回收机制的性能较低,采取了限制内存的方式避免因内存回收引发的性能问题
V8 将对象分为新生代对象和老生代对象,不同类型的对象采用不同的垃圾回收机制
新生代对象:存活时间短
老生代对象:存活时间长
5.2 提升垃圾回收效率
- 主动释放变量 - delete, 变量赋空值
delete foo
foo = undefined (推荐) - 尽量少使用
闭包内无法释放的变量、全局变量,存活周期长,无法及时回收内存,要预防此类变量的不当或过多使用 - 使用堆外内存 - Buffer, Stream
Node的对象一般通过V8在堆内进行内存分配,使用堆外内存就可以扩展Node的内存空间。
Buffer,Stream等类型变量不是通过V8分配,故不受内存限制
$ node
> process.memoryUsage() // 查看堆内存情况
{ rss: 13852672, // Node进程的常驻总内存
heapTotal: 6131200, // 堆内存总量
heapUsed: 2757120 } // 当前堆内存使用量
> os.totalmem() // 查看系统总内存
5.3 内存泄露的预防
通常,造成内存泄露的原因大概有:
- 缓存
通过key/value等形式的变量在内存中直接缓存
模块是被缓存的常驻对象,所以它的exports出的变量可能存在泄露隐患
解决方法: 限制缓存使用的大小; 外部缓存,如 redis(推荐)
- 缓存
- 队列消费不及时
队列的消费速度低于生产速度时,会出现泄露情况
解决方案:队列超长时预警;异步要包含超时机制
- 队列消费不及时
- 作用域未释放
特例:
- 作用域未释放
5.4 内存泄露排查
通过三方工具对堆内存使用情况分析
第6章 Buffer
Buffer用于处理量大的数据。由Node的C++模块负责,故而具备高性能,且不受V8引擎内存限制。其类似Array类型,元素为0到255的16进制数的两位数
Buffer与String转换
new Buffer(str, [encoding])
buffer.toString([encoding, start, end])
支持的编码方式:
- ASCII
- UTF-8
- UTF-16LE/UCS-2
- Base64
- Binary
- Hex
Buffer字符串拼接问题
用buffer.tostring进行类型转换时,如果是中文等非英文类,会出现乱码。
乱码的解决方案:
- 转换时设置encoding
buffer.tostring(encoding)
但Node默认支持的编码方式有限 - 三方转码工具
将buffer缓存到数组,之后用iconv-lite等三方解码
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');
console.log(str);
});
第8章 WEB应用
请求
请求头 request header
Node的http模块接收到请求后,通过http_parser
将以下HTTP请求头提取后附加在req对象上。如:req.method, req.url
> GET /path?foo=bar HTTP/1.1
> User-Agent: curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0 OpenSSL/0.9.8r zlib/1.2.5
> Host: 127.0.0.1:1337
> Accept: */*
>
请求体 request body
而HTTP的请求体,则需要业务代码自行提取。通过header中Content-Type
判断当前提交数据的格式。
application/x-www-form-urlencoded
:表单数据,请求体数据格式为key=value&key=value
application/json
: 请求体为JSON数据格式
multipart/form-data
:包含文件等复杂数据类型,请求头Content-Type
中会包含一个boundary
的分隔符字符串,把数据按分隔符分成多部分传输
Content-Type: multipart/form-data; boundary=AaB03x
Content-Length: 18231
Express有自带中间件
express.urlencoded()
,express.json()
将转换后的Object值附加在req.body
有三方插件,如multer
,可将含文件数据的form-data转换成可用对象,req.body
为普通数据,req.file
为文件数据
缓存
- 客户端询问是否使用缓存
请求报文包含If-Modified-Since
(时间戳)询问服务器文件有无更新。如没有,服务器返回304 not modified
,若更改,返回新文件,并在响应头中增加Last-Modified
。
If-Modified-Since
以时间戳方式标记不够准确,估引入ETag
,是由服务器根据文件内容生成的散列值。ETag
用到的请求和响应头分别是If-None-Match/ETag
- 客户端直接缓存
上面的缓存机制,客户端仍旧要发送一个请求。如果能直接使用缓存,就可以减少请求。为达到此目的,服务器可以在响应头中设置Expires
或Cache-Control
,客户端则根据这个字段直接缓存文件。
Expires
设置一个时间戳,表明在此时间前文件都有效。
Cache-Control
使用max-age
等多个字段值的方式,能更精准的描述缓存时间限制,而不受客户端服务器端时间不同步的缺陷影响
在资源url中增加版本号或hash值,可使客户端及时请求到最新资源,避免因缓存造成的同步问题
身份认证
请求报文中增加authentication
认证信息,此值由前后端协商认证方式和加密生成。
$ curl -v "http://user:pass@www.baidu.com/"
> GET / HTTP/1.1
> Authorization: Basic dXNlcjpwYXNz
> User-Agent: curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0 OpenSSL/0.9.8r zlib/1.2.5
> Host: www.baidu.com
> Accept: */*
目前更多的方案是在api调用时传递有效token
请求安全性
为方式数据上传是造成大量数据推向服务器,造成内存泄露,可通过限制上传数据大小的方式。当数据量达到阈值时禁止上传
为了防止CSRF(跨域请求伪造)造成对服务器的恶意攻击,可在请求和响应中加上一个随机数,来校验请求来源的合法性
其他
路由解析:https://www.jianshu.com/p/4d0512cd0a80
中间件:https://www.jianshu.com/p/724418fee5fb
页面渲染:https://www.jianshu.com/p/141c58b8cc0b
第9章 进程
服务器架构模式
- 单进程同步:所有请求由同一个进程响应,纯同步模式。已被废弃。
- 多进程:每个进程负责响应一个请求。但进程的花销比较大
- 多线程:每个线程负责响应一个请求,花销较小且进程内能通信。
- 事件驱动(单线程单进程):单进程单线程,基于事件的异步响应模式。但无法充分利用多核cpu,且健壮性低(一旦出错整个程序崩溃)
子进程
Node为解决无法充分利用CPU这个缺陷,提供了child_process
子进程功能,该模块提供四个不同的接口来启动子进程来执行程序。
var cp = require('child_process');
cp.spawn('node', ['worker.js']);
cp.exec('node worker.js', function (err, stdout, stderr) {
// some code
});
cp.execFile('worker.js', function (err, stdout, stderr) {
// some code
// javascript文件第一行必须添加:#!/usr/bin/env node 表明执行环境
});
cp.fork('./worker.js');
子进程通信
通常会创建一个或多个子进程去处理计算量大或IO操作,由一个主进程来管理和控制这些子进程
进程间通过message消息传递消息
send() -> 发送消息
on('message', callback) -> 接收消息
Node的父子进程间通过IPC实现进程间通信,具体采用的是管道技术。由libuv实现平台无关的封装,windows由命名管道实现,*nix由Domain Socket实现。通信为双向通信,在系统内核里实现。
IPC进程通信
网友评论