node.js
node.js 是什么
node.js 是一个用 c++ 开发的基于 Chrome V8 引擎的 js 运行环境。
我的理解,node.js 是个与浏览器平等的 js 宿主环境,加载 js 语言后使用 js 引擎解释执行,并提供了管理内存、修改注册表等能力。
为了 web 安全,运行在浏览器中的 js 会收到很多限制,比如:不能直接访问计算机文件系统(html5 上传文件的方法:web.dev/read-files/… )、同源策略、不能不经用户允许关闭用户打开的窗口、可以从历史对象(history)中获得的信息有限等。
而 node.js 补充了操作文件、操作数据库、多进程等能力,使得 js 不但能在浏览器中使用,还可以被使用在服务端、桌面型 web 程序中。
特点
- 单线程:没有线程切换和数据共享的问题;
- 非阻塞;
- 事件驱动;
事件驱动
参考:www.jianshu.com/p/d070e11ff…
在系统底层,非阻塞的I/O解决方案,只有Network I/O部分,linux底层使用epoll,windows下使用IOCP。文件等操作则没有很好的系统解决方案,因此为了模拟实现非阻塞的I/O方案,Node在libuv层启用了一个线程池,用于调用系统的阻塞式I/O。
因此,实际上,node只是在应用层属于单线程,底层其实通过libuv维护了一个阻塞I/O调用的线程池。
事件循环(event loop)
每次循环都会检查队列中(有好几个队列,如 nextTickQueue、immediateQueue )是否有事件需要处理,有则取出事件执行,没有则退出线程。
异步 i/o,网络请求,定时器等都会产生事件。
定时器产生的事件及回调函数会被放在红黑树中(?),而 process.nextTick 产生的回调函数会被放在队列中。
观察者(watcher)
观察者用于询问是否有事件要处理,node.js 有三种观察者:
- idle 观察者,如 process.nextTick();
- i/o 观察者:观察 i/o 相关事件,如网络、文件、数据库等;
- check 观察者:观察 setTimeout / setInterval / setImmediate 等;
优先级:idle > i/o > check。
process.nextTick 和 setImmediate 都是把回调函数设置在下一次循环前执行,但因为 idle 观察者优先级更高,所以 process.nextTick 会先执行。setTimeout / setInterval 会比 setImmediate 先执行。setTimeout(xxx, 0) 会被强制设定为 1。
EventEmitter 模块
node.js 内置了 EventEmitter 模块,依然是采用了订阅 - 发布模式管理事件。许多 node.js 的对象继承自 EventEmitter,所以可以在运行中广播事件,形成事件流。
node.js 的事件也是基于 event loop 实现的。
方法:
- EventEmitter.on(event, listener):添加监听函数至事件队列末尾;
- EventEmitter.prependListener(event, listener):添加监听函数至队列开头;
- EventEmitter.emit(event):触发事件;
- EventEmitter.once(event, listener):单次监听器;
- EventEmitter.removeListener(event, listener):移除监听器;
- EventEmitter.removeAllListeners([event]):移除所有监听器;
- EventEmitter.listenerCount(event):监听器个数;
流(stream)
参考:
“流(stream)是 Node.js 中处理流式数据的抽象接口。”流是一种数据传输的机制,可以指有序地(从来源到目的地)、可控制大小的(阀门)、按需获取地实现数据传输。
比如在读大文件、播放视频时,流机制可以在服务端读取数据(生产),同时数据通过管道流动到客户端(消费)。
node.js 中实现了 4 种基本流类型:
- readable:可读流;
- writable:可写流;
- deplex:双工流,维护两块缓存,读、写完全独立;
- transform:转换流,读、写有关联,将流入的数据计算后流出;
stream 继承了 EventEmitter 类,所以可以注册数据传输相关事件的处理方法。
使用:
- 可读流需要实例化 _read(),可写流需要实例化 _write();
事件:
- data:每一次消费数据后都会触发,参数是本次消费的数据;
- end:所有数据已输出;
- readable:缓冲区有新的可读取数据;
- drain:数据适合继续写入;
- finish:stream.end() 后调用;
- pipe/unpipe:可写流被 pipe/unpipe 到可读流上;
- error:流发生错误时触发;
- close:流关闭时触发;
缓存池
缓存池增加了流的效率,当数据的生产和消费都需要时间时,可以在下一次消费前把提前生产地数据缓存在缓存池内。
缓存池常位于电脑内存(RAM)中,node.js 的缓存池是个 Buffer 链表。
当缓存池中数据过多时,应该停止数据生产。
管道(pipe)
用于连接两个流,上一个流的输入会被作为下一个流的输入。
readable.pipe(writable)
暂停模式(paused)和 流动模式(flowing)
可读流的两种模式
node.js 内置的流
- zlib:压缩文件;
- crypto:加密文件;
全局对象
global 全局对象,常用 api:
- console:输出;
- global:全局命名空间;
- process:当前进程信息;
- module:对当前模块的引用;
- require() / exports:commonJS 规范下的模块导入、导出方式;
- Buffer:缓冲器,处理二进制数据;
- setTimeout / setInterval / setImmediate:定时器;
- __dirname:脚本所在目录;
- __filename:代码文件名称;
process:当前进程信息
进程退出时会返回退出码,0 代表正常退出,1 代表未捕获错误,5 代表 v8 执行错误,8 代表不正确参数;
属性:
- process.argv:命令行参数数组(包括 node 和脚本名);
- process.env:当前环境变量;
- process.installPrefix:node 安装路径;
- process.pid:进程号;
- process.platform:系统平台;
- process.title:名称,默认为 node;
- process.version:版本;
- process.execPath:操作文件路径;
- process.stdout:标准输出;
- process.stdin:标准输入;
- process.stderr:标准错误;
方法:
- process.exit(): 退出当前进程,可设置退出状态码;
- process.cwd(): 获得当前工作目录路径;
- process.chdir(): 改变当前工作路径;
- process.nextTick(): 将回调函数放在下次事件循环的顶部;
- process.hrtime():返回当前时间;
- process.on():监听事件;
- process.kill(pid, signal):发送信号(SIGTERM | SIGINT | SIGHUP)给进程,如不提供信号值默认为关闭进程(SIGTERM);
事件:
- exit:进程退出,回调函数只能执行同步操作;
- beforeExit:清空事件循环、没有待处理任务时触发,可以设置异步回调;
- uncaughtException:没有被捕获的错误;
- 信号:操作系统向 Node 进程发信号,回调可以阻止默认退出操作;
Buffer:缓冲器
参考:www.cnblogs.com/ZheOneAndOn…
Buffer 能够使 js 操作二进制数据。图片、文件等资源以二进制的形式存在磁盘上。
node.js 用 c++ 层面申请内存,js 中分配内存。node.js 采用了 slab 分配机制,slab 是一块申请好的固定大小的内存区域(8 kb),以它为单位单元进行内存分配。
根据 Buffer 大小,如果:
- 小于 8 kb:当前 slab 是否有剩余空间,如果有则分配到当前 slab,没有则重新申请一个全新的 slab,之前没被分配完的 slab 会被闲置;
- 大于 8 kb:直接分配一个对应 buffer 大小的 slab;
方法:
- Buffer.from(data):根据数据创建;
- Buffer.alloc(size):根据指定大小创建 Buffer,并初始化;
- Buffer.allocUnsafe(size):根据指定大小创建 Buffer,不初始化;
- Buffer.allocUnsafeSlow(size):根据指定大小创建 Buffer,使用全新 slab,不初始化;
- Buffer.concat():拼接 Buffer;
异步钩子(async hooks)
异步钩子涵盖了一次异步调用的整个生命周期。
属性:
- asyncId 本次异步调用 id;
- triggerAsyncId 父异步调用 id;
事件:
- init:异步请求资源准备完毕;
- before / after:执行异步请求前/后回调;
- destory:异步请求资源被销毁;
- promiseResolve:promise 调用 resolve 时;
操作文件:fs 模块
const fs = require('fs');
支持字符串、Buffer、file 协议的 URL 对象(file: ///)三种路径类型。
f开头方法表示不提供 path,而是提供文件描述符,这类方法前要先用 fs.open 打开文件。
类:
- fs.Dir:目录类;
- fs.Dirent:目录项类;
- fs.ReadStream:可读流;
- fs.WriteStream:可写流;
- fs.Stats:文件元信息;
- fs.FSWatcher / fs.StatWatcher:监听类;
方法 / 属性:
- fs.readFile / fs.readFileSync:自动 open,异步/同步读取文件;
- fs.read / fs.readSync:放在 fs.open 的回调函数中,异步/同步读取文件;
- fs.writeFile / fs.writeFileSync:自动 open,异步/同步写入文件;
- fs.write / fs.writeSync:放在 fs.open 的回调函数中,异步/同步写入文件;
- fs.access(path):检查文件可用性;
- fs.appendFile(path, data):向文件中追加数据;
- fs.chmod(path, mode):修改文件权限,读权限 + 2,写权限 + 4, 执行权限 + 1;
- fs.chown(path, uid):修改文件拥有者;
- fs.copyFile(src, desc):复制文件;
- fs.mkdir(path):创建文件夹;
- fs.open(path):打开文件;
- fs.close(fd):关闭文件;
- fs.rename(oldPath, newPath):重命名;
- fs.opendir(path):打开目录;
- dir.path:目录;
- dir.read():读取目录下文件;
- dir.close():关闭目录;
- fs.createReadStream(path):创建可读流;
- readStream.bytesRead:已读取字节数;
- readStream.path:读取文件路径;
- readStream.pending:文件已就绪;
- fs.createWriteStream(path):创建可写流;
- writeStream.bytesWritten:已写入字节数;
- writeStream.path:写入文件路径;
- writeStream.pending:文件已就绪;
- fs.stat(path):对文件元信息描述;
fs.stat:
fs.createReadStream(path):
fs.createReadStream(path):
操作数据库
非原生支持能力,需要安装相关库,如 mysql。
- 创建连接,mysql.createConnection({ ... });
- 建立连接,connection.connect();
- 数据库操作,connection.query();
http/http2/https 模块
node.js 中共有三个和 http 相关的模块,分别为 http、http2 和 https
const http = require('http');
- 创建服务,http.createServer();
- 注册 request 事件,server.on('request', (req, res)=>{ ... }),其中 req 是请求内容,res 是响应内容,回调函数内、req 和 res 上也可以绑定事件,data 事件在数据接受时触发,end 事件在数据处理完时触发,res 上有.write()、.writeHead() 和 .end() 等方法;
- 服务监听端口,server.listen(8080);
子进程和集群
node.js 与浏览器对比
- 全局 this 指向不一样:浏览器中 this 指向 window,node.js 中 this 指向 global 对象;
- 引擎不一样:浏览器中有各类引擎,所以会有兼容性差异,node.js 只使用 v8 引擎;
- 可操作对象不一样:浏览器可操作 dom 和 bom,node.js 可操作文件;
- 模块标准不一样:浏览器执行 ES 6 标准,node.js 执行 commonJS 标准;
- 事件循环不一样:blog.csdn.net/z591102/art… ;
node.js 与传统服务端语言的对比
基于 node.js 单线程和非阻塞的特点,node.js善于I/O,擅长任务调度,不善于计算。
koa
express、koa1、koa2
express 封装、内置了很多中间件,原生支持路由功能,而 koa 框架更加轻量级,几乎所有功能都通过自行配置中间件(middleware)实现。
三者对比:
- express 自身集成较多能力,koa 需要配置中间件;
- express 采用回调控制异步流程,koa1 采用 generator + yield 控制,koa2 采用 async + await 控制;
- express 采用线性模型执行中间件,koa 采用洋葱圈模型执行中间件;
- koa 在 request 和 response 基础上,增加了 context 这个对象,作为一次请求的上下文;
其他框架:
- egg.js 在 koa 基础上,提供了路由、文件上传、日志管理等功能,实现开箱即用,并且处理了一些 koa 的问题;
- midway.js 在 egg.js 基础上支持了 typescript;
- nest.js 在 express 基础上支持了 typescript;
application
koa 的实例对象,
- const app = new Koa({ ... });
- app.use(...) 使用中间件(异步回掉函数),可以多次 app.use,使用多个中间件;
- app.listen(...) 监听端口;
context
context 封装了 request 和 response 两个对象,是 app.use 中回调函数的第一个参数。可以使开发者更快捷地操作 request 和 response。
api:
- ctx.req / ctx.request:request;
- ctx.response:response;
- ctx.state:命名空间;
- ctx.app:application 引用;
- ctx.app.emit(event):触发事件;
- ctx.cookies.get(name) / ctx.cookies.set(name, value):获取 / 设置 cookie;
- ctx.throw():抛出错误;
- ctx.assert(value):当 !value 时抛出错误;
洋葱圈模型
每一个请求进入,所有中间件都会被执行两次。
use 中的异步回调函数,在 next() 前调用的,会在第一次执行时执行,next() 后的会在全部中间件执行完后执行。因为在中间件里执行的大多是异步操作,在执行完后再调用一次,可以实现:
- 统计中间件操作耗时;
- 对后续中间件的结果再处理;
前后端通信
ajax
异步通讯,页面局部刷新
XMLHttpRequest(xhr)
axios
通过 promise 对原生 xhr 封装的网络请求库
fetch
ES 6 中新的 ajax 方法,也会返回 promise 对象,用法与 axios 基本一致;
websocket
双向交互通信会话;
developer.mozilla.org/en-US/docs/…
http2 服务端主动推送和 websocket
eventSource
developer.mozilla.org/zh-CN/docs/…
持久化连接,服务端单向推送;
被用于处理社交媒体状态更新,新闻提要或将数据传递到客户端存储机制(如 IndexedDB 或 Web 存储)等;
token
token 的用法
- 用户登录校验,校验成功后返回 token 给客户端;
- 客户端保存 token;
- 下次访问时携带 token,请求头的 Authorization;
- 服务端校验,成功则返回数据、失败返回错误码;
token 的优点
- 无状态、可扩展;
- 安全性,防止 CSRF;
- 支持跨域,适用CDN;
- 适用移动端等不支持 cookie 的场景;
token 生成方案
- JSON Web Token(jwt):后端将 jwt header(描述 jwt 元数据、如签名算法等) + payload(json 格式的用户信息) + signature (前两部分的哈希字符串,防止被篡改),分别编码后拼接,其中 payload 可以加密;
- 直接用 session;
- 直接用设备号 / mac 地址;
其他
- 请求头 referer 和 origin:www.cnblogs.com/home-of-qic… ;
- 加密算法:zhuanlan.zhihu.com/p/434532109 ;
mysql
参考:
关系型数据库和非关系型数据库
- 关系型数据库是一个结构化的数据库,创建在关系模型(二维表格模型)基础上,一般面向于记录,如 Oracle、MySQL、SQL Server、Microsoft Access、DB2;
- 除了主流的关系型数据库外的数据库,都认为是非关系型,如 Redis、MongBD、Hbase、CouhDB;
mysql 架构
InnoDB 引擎
b+ 树和 hash 索引
b + 树
数据存储在叶子节点中,叶子节点间有链指针。
hash 索引
hash 索引就是 hash 表
对比
- 等值查询时,hash 索引更快,不需要逐层寻找;
- hash 索引无法用于范围查询;
- hash 索引无法利用索引排序;
- hash 索引可能存在 hash 碰撞问题,但 b+ 树性能稳定;
锁
如何创建高效索引
因为存储的数据结构是 b+ 树,每一层可以根据索引查到下一层节点,直到查到叶子节点(数据),所以索引能加快查找效率。
聚集索引和非聚集索引
- 聚集索引:数据和索引一起存放;
- 非聚集索引:数据和索引分开存放;