[杂七杂八的学习记录] node.js & koa & 前后端通信 & mysql

371 阅读12分钟

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 程序中。

特点

  1. 单线程:没有线程切换和数据共享的问题;
  2. 非阻塞;
  3. 事件驱动;

事件驱动

参考:www.jianshu.com/p/d070e11ff…

在系统底层,非阻塞的I/O解决方案,只有Network I/O部分,linux底层使用epoll,windows下使用IOCP。文件等操作则没有很好的系统解决方案,因此为了模拟实现非阻塞的I/O方案,Node在libuv层启用了一个线程池,用于调用系统的阻塞式I/O。

因此,实际上,node只是在应用层属于单线程,底层其实通过libuv维护了一个阻塞I/O调用的线程池。

image.png

事件循环(event loop)

每次循环都会检查队列中(有好几个队列,如 nextTickQueue、immediateQueue )是否有事件需要处理,有则取出事件执行,没有则退出线程。

异步 i/o,网络请求,定时器等都会产生事件。

定时器产生的事件及回调函数会被放在红黑树中(?),而 process.nextTick 产生的回调函数会被放在队列中。

观察者(watcher)

观察者用于询问是否有事件要处理,node.js 有三种观察者:

  1. idle 观察者,如 process.nextTick();
  2. i/o 观察者:观察 i/o 相关事件,如网络、文件、数据库等;
  3. 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 实现的。

方法:

  1. EventEmitter.on(event, listener):添加监听函数至事件队列末尾;
  2. EventEmitter.prependListener(event, listener):添加监听函数至队列开头;
  3. EventEmitter.emit(event):触发事件;
  4. EventEmitter.once(event, listener):单次监听器;
  5. EventEmitter.removeListener(event, listener):移除监听器;
  6. EventEmitter.removeAllListeners([event]):移除所有监听器;
  7. EventEmitter.listenerCount(event):监听器个数;

流(stream)

参考:

  1. nodejs.org/api/stream.…
  2. juejin.cn/post/692717…

“流(stream)是 Node.js 中处理流式数据的抽象接口。”流是一种数据传输的机制,可以指有序地(从来源到目的地)、可控制大小的(阀门)、按需获取地实现数据传输。

比如在读大文件、播放视频时,流机制可以在服务端读取数据(生产),同时数据通过管道流动到客户端(消费)。

node.js 中实现了 4 种基本流类型:

  1. readable:可读流;
  2. writable:可写流;
  3. deplex:双工流,维护两块缓存,读、写完全独立;
  4. transform:转换流,读、写有关联,将流入的数据计算后流出;

stream 继承了 EventEmitter 类,所以可以注册数据传输相关事件的处理方法。

使用:

  1. 可读流需要实例化 _read(),可写流需要实例化 _write();

事件:

  1. data:每一次消费数据后都会触发,参数是本次消费的数据;
  2. end:所有数据已输出;
  3. readable:缓冲区有新的可读取数据;
  4. drain:数据适合继续写入;
  5. finish:stream.end() 后调用;
  6. pipe/unpipe:可写流被 pipe/unpipe 到可读流上;
  7. error:流发生错误时触发;
  8. close:流关闭时触发;

image.png

缓存池

缓存池增加了流的效率,当数据的生产和消费都需要时间时,可以在下一次消费前把提前生产地数据缓存在缓存池内。

缓存池常位于电脑内存(RAM)中,node.js 的缓存池是个 Buffer 链表。

当缓存池中数据过多时,应该停止数据生产。

管道(pipe)

用于连接两个流,上一个流的输入会被作为下一个流的输入。

readable.pipe(writable)

暂停模式(paused)和 流动模式(flowing)

可读流的两种模式

image.png

image.png

image.png

node.js 内置的流

image.png

  1. zlib:压缩文件;
  2. crypto:加密文件;

全局对象

global 全局对象,常用 api:

  1. console:输出;
  2. global:全局命名空间;
  3. process:当前进程信息;
  4. module:对当前模块的引用;
  5. require() / exports:commonJS 规范下的模块导入、导出方式;
  6. Buffer:缓冲器,处理二进制数据;
  7. setTimeout / setInterval / setImmediate:定时器;
  8. __dirname:脚本所在目录;
  9. __filename:代码文件名称;

process:当前进程信息

进程退出时会返回退出码,0 代表正常退出,1 代表未捕获错误,5 代表 v8 执行错误,8 代表不正确参数;

属性:

  1. process.argv:命令行参数数组(包括 node 和脚本名);
  2. process.env:当前环境变量;
  3. process.installPrefix:node 安装路径;
  4. process.pid:进程号;
  5. process.platform:系统平台;
  6. process.title:名称,默认为 node;
  7. process.version:版本;
  8. process.execPath:操作文件路径;
  9. process.stdout:标准输出;
  10. process.stdin:标准输入;
  11. process.stderr:标准错误;

方法:

  1. process.exit(): 退出当前进程,可设置退出状态码;
  2. process.cwd(): 获得当前工作目录路径;
  3. process.chdir(): 改变当前工作路径;
  4. process.nextTick(): 将回调函数放在下次事件循环的顶部;
  5. process.hrtime():返回当前时间;
  6. process.on():监听事件;
  7. process.kill(pid, signal):发送信号(SIGTERM | SIGINT | SIGHUP)给进程,如不提供信号值默认为关闭进程(SIGTERM);

事件:

  1. exit:进程退出,回调函数只能执行同步操作;
  2. beforeExit:清空事件循环、没有待处理任务时触发,可以设置异步回调;
  3. uncaughtException:没有被捕获的错误;
  4. 信号:操作系统向 Node 进程发信号,回调可以阻止默认退出操作;

Buffer:缓冲器

参考:www.cnblogs.com/ZheOneAndOn…

Buffer 能够使 js 操作二进制数据。图片、文件等资源以二进制的形式存在磁盘上。

node.js 用 c++ 层面申请内存,js 中分配内存。node.js 采用了 slab 分配机制,slab 是一块申请好的固定大小的内存区域(8 kb),以它为单位单元进行内存分配。

根据 Buffer 大小,如果:

  1. 小于 8 kb:当前 slab 是否有剩余空间,如果有则分配到当前 slab,没有则重新申请一个全新的 slab,之前没被分配完的 slab 会被闲置;
  2. 大于 8 kb:直接分配一个对应 buffer 大小的 slab;

方法:

  1. Buffer.from(data):根据数据创建;
  2. Buffer.alloc(size):根据指定大小创建 Buffer,并初始化;
  3. Buffer.allocUnsafe(size):根据指定大小创建 Buffer,不初始化;
  4. Buffer.allocUnsafeSlow(size):根据指定大小创建 Buffer,使用全新 slab,不初始化;
  5. Buffer.concat():拼接 Buffer;

image.png

异步钩子(async hooks)

异步钩子涵盖了一次异步调用的整个生命周期。

属性:

  1. asyncId 本次异步调用 id;
  2. triggerAsyncId 父异步调用 id;

事件:

  1. init:异步请求资源准备完毕;
  2. before / after:执行异步请求前/后回调;
  3. destory:异步请求资源被销毁;
  4. promiseResolve:promise 调用 resolve 时;

操作文件:fs 模块

参考:blog.csdn.net/qq_41694291…

const fs = require('fs');

支持字符串、Buffer、file 协议的 URL 对象(file: ///)三种路径类型。

f开头方法表示不提供 path,而是提供文件描述符,这类方法前要先用 fs.open 打开文件。

类:

  1. fs.Dir:目录类;
  2. fs.Dirent:目录项类;
  3. fs.ReadStream:可读流;
  4. fs.WriteStream:可写流;
  5. fs.Stats:文件元信息;
  6. fs.FSWatcher / fs.StatWatcher:监听类;

方法 / 属性:

  1. fs.readFile / fs.readFileSync:自动 open,异步/同步读取文件;
  2. fs.read / fs.readSync:放在 fs.open 的回调函数中,异步/同步读取文件;
  3. fs.writeFile / fs.writeFileSync:自动 open,异步/同步写入文件;
  4. fs.write / fs.writeSync:放在 fs.open 的回调函数中,异步/同步写入文件;
  5. fs.access(path):检查文件可用性;
  6. fs.appendFile(path, data):向文件中追加数据;
  7. fs.chmod(path, mode):修改文件权限,读权限 + 2,写权限 + 4, 执行权限 + 1;
  8. fs.chown(path, uid):修改文件拥有者;
  9. fs.copyFile(src, desc):复制文件;
  10. fs.mkdir(path):创建文件夹;
  11. fs.open(path):打开文件;
  12. fs.close(fd):关闭文件;
  13. fs.rename(oldPath, newPath):重命名;
  14. fs.opendir(path):打开目录;
  15. dir.path:目录;
  16. dir.read():读取目录下文件;
  17. dir.close():关闭目录;
  18. fs.createReadStream(path):创建可读流;
  19. readStream.bytesRead:已读取字节数;
  20. readStream.path:读取文件路径;
  21. readStream.pending:文件已就绪;
  22. fs.createWriteStream(path):创建可写流;
  23. writeStream.bytesWritten:已写入字节数;
  24. writeStream.path:写入文件路径;
  25. writeStream.pending:文件已就绪;
  26. fs.stat(path):对文件元信息描述;

fs.stat:

image.png

fs.createReadStream(path):

image.png

fs.createReadStream(path):

image.png

操作数据库

非原生支持能力,需要安装相关库,如 mysql。

  1. 创建连接,mysql.createConnection({ ... });
  2. 建立连接,connection.connect();
  3. 数据库操作,connection.query();

http/http2/https 模块

node.js 中共有三个和 http 相关的模块,分别为 http、http2 和 https

const http = require('http');

  1. 创建服务,http.createServer();
  2. 注册 request 事件,server.on('request', (req, res)=>{ ... }),其中 req 是请求内容,res 是响应内容,回调函数内、req 和 res 上也可以绑定事件,data 事件在数据接受时触发,end 事件在数据处理完时触发,res 上有.write()、.writeHead() 和 .end() 等方法;
  3. 服务监听端口,server.listen(8080);

子进程和集群

参考:blog.csdn.net/wz_coming/a…

node.js 与浏览器对比

  1. 全局 this 指向不一样:浏览器中 this 指向 window,node.js 中 this 指向 global 对象;
  2. 引擎不一样:浏览器中有各类引擎,所以会有兼容性差异,node.js 只使用 v8 引擎;
  3. 可操作对象不一样:浏览器可操作 dom 和 bom,node.js 可操作文件;
  4. 模块标准不一样:浏览器执行 ES 6 标准,node.js 执行 commonJS 标准;
  5. 事件循环不一样:blog.csdn.net/z591102/art…

node.js 与传统服务端语言的对比

基于 node.js 单线程和非阻塞的特点,node.js善于I/O,擅长任务调度,不善于计算。

koa

express、koa1、koa2

express 封装、内置了很多中间件,原生支持路由功能,而 koa 框架更加轻量级,几乎所有功能都通过自行配置中间件(middleware)实现。

三者对比:

  1. express 自身集成较多能力,koa 需要配置中间件;
  2. express 采用回调控制异步流程,koa1 采用 generator + yield 控制,koa2 采用 async + await 控制;
  3. express 采用线性模型执行中间件,koa 采用洋葱圈模型执行中间件;
  4. koa 在 request 和 response 基础上,增加了 context 这个对象,作为一次请求的上下文;

其他框架:

  1. egg.js 在 koa 基础上,提供了路由、文件上传、日志管理等功能,实现开箱即用,并且处理了一些 koa 的问题;
  2. midway.js 在 egg.js 基础上支持了 typescript;
  3. nest.js 在 express 基础上支持了 typescript;

application

koa 的实例对象,

  1. const app = new Koa({ ... });
  2. app.use(...) 使用中间件(异步回掉函数),可以多次 app.use,使用多个中间件;
  3. app.listen(...) 监听端口;

context

context 封装了 request 和 response 两个对象,是 app.use 中回调函数的第一个参数。可以使开发者更快捷地操作 request 和 response。

api:

  1. ctx.req / ctx.request:request;
  2. ctx.response:response;
  3. ctx.state:命名空间;
  4. ctx.app:application 引用;
  5. ctx.app.emit(event):触发事件;
  6. ctx.cookies.get(name) / ctx.cookies.set(name, value):获取 / 设置 cookie;
  7. ctx.throw():抛出错误;
  8. ctx.assert(value):当 !value 时抛出错误;

洋葱圈模型

每一个请求进入,所有中间件都会被执行两次。

image.png

use 中的异步回调函数,在 next() 前调用的,会在第一次执行时执行,next() 后的会在全部中间件执行完后执行。因为在中间件里执行的大多是异步操作,在执行完后再调用一次,可以实现:

  1. 统计中间件操作耗时;
  2. 对后续中间件的结果再处理;

image.png

前后端通信

ajax

异步通讯,页面局部刷新

XMLHttpRequest(xhr)

image.png

axios

通过 promise 对原生 xhr 封装的网络请求库

image.png

fetch

ES 6 中新的 ajax 方法,也会返回 promise 对象,用法与 axios 基本一致;

image.png

websocket

双向交互通信会话;

developer.mozilla.org/en-US/docs/…

http2 服务端主动推送和 websocket

image.png

eventSource

developer.mozilla.org/zh-CN/docs/…

持久化连接,服务端单向推送;

被用于处理社交媒体状态更新,新闻提要或将数据传递到客户端存储机制(如 IndexedDB 或 Web 存储)等;

token

参考:blog.csdn.net/weixin_4507…

token 的用法

  1. 用户登录校验,校验成功后返回 token 给客户端;
  2. 客户端保存 token;
  3. 下次访问时携带 token,请求头的 Authorization;
  4. 服务端校验,成功则返回数据、失败返回错误码;

token 的优点

  1. 无状态、可扩展;
  2. 安全性,防止 CSRF;
  3. 支持跨域,适用CDN;
  4. 适用移动端等不支持 cookie 的场景;

token 生成方案

  1. JSON Web Token(jwt):后端将 jwt header(描述 jwt 元数据、如签名算法等) + payload(json 格式的用户信息) + signature (前两部分的哈希字符串,防止被篡改),分别编码后拼接,其中 payload 可以加密;
  2. 直接用 session;
  3. 直接用设备号 / mac 地址;

其他

  1. 请求头 referer 和 origin:www.cnblogs.com/home-of-qic…
  2. 加密算法:zhuanlan.zhihu.com/p/434532109

mysql

参考:

  1. blog.csdn.net/qq_15505769…
  2. blog.csdn.net/weixin_4468…
  3. blog.csdn.net/weixin_5146…

关系型数据库和非关系型数据库

  1. 关系型数据库是一个结构化的数据库,创建在关系模型(二维表格模型)基础上,一般面向于记录,如 Oracle、MySQL、SQL Server、Microsoft Access、DB2;
  2. 除了主流的关系型数据库外的数据库,都认为是非关系型,如 Redis、MongBD、Hbase、CouhDB;

mysql 架构

image.png

InnoDB 引擎

image.png

b+ 树和 hash 索引

b + 树

数据存储在叶子节点中,叶子节点间有链指针。

image.png

image.png

hash 索引

hash 索引就是 hash 表

对比

  1. 等值查询时,hash 索引更快,不需要逐层寻找;
  2. hash 索引无法用于范围查询;
  3. hash 索引无法利用索引排序;
  4. hash 索引可能存在 hash 碰撞问题,但 b+ 树性能稳定;

image.png

如何创建高效索引

因为存储的数据结构是 b+ 树,每一层可以根据索引查到下一层节点,直到查到叶子节点(数据),所以索引能加快查找效率。

image.png

聚集索引和非聚集索引

  1. 聚集索引:数据和索引一起存放;
  2. 非聚集索引:数据和索引分开存放;