v11.10 nodejs 学习 (二)
Cluster
Node.js 的单个实例运行在单线程中。要利用多核,需要开启 node.js 进程的集群,来处理负载。使用 cluster 模块,很容易创建共享 server 端口的子进程。
1 | const cluster = require('cluster'); |
工作原理
worker 子进程是使用 child_process.fork 生成的,所以子进程可以通过 IPC 通道和父进程往返传递 server 句柄。
cluster 支持两种分发请求方式:
- 默认的方式(windows 除外),循环。主进程监听一个端口,接收新的请求,利用一些内建的方式来负载均衡,循环分发这些请求到 workers。
- 主进程创建侦听套接字并将其发送给感兴趣的 worker。然后 worker 直接接受传入的连接。
第二种方案,讲道理应该是性能最优的,但由于操作系统的调度不可预测,会导致负载极其不均。假设 8 个进程,会出现 2 个进程占据了 70% 的请求的情况。
因为 server.listen () 把大部分工作都扔给了主进程,普通的 Node.js 进程和集群的进程有 3 种 case 不一样:
- server.listen ({fd: 7}) 因为消息发送给了主进程,父进程的文件描述符 7 会被监听,句柄被传递给 worker 进程,而不是监听 worker 对文件文件描述符 7 的想法。
- server.listen (handle) Listening on handles explicitly will cause the worker to use the supplied handle, rather than talk to the master process. 明确监听句柄会导致 worker 使用提供的句柄,而不会与主进程交流。
- server.listen (0) 通常这会让服务器监听一个随机的端口。然而在集群模式下,每一个 worker 会收到相同的 “随机” 端口。端口一开始是随机的,但那之后便是可预测的。监听一个唯一的端口,基于集群 worker ID 生成一个端口号。
Console
该模块暴露了 2 个组件:
- Class Console。具有如 log、error、warn 等方法可以用于向 nodejs streeam 输出
- global console instance,已被默认配置输出到 process.stdout 和 process.stderr。
Class: Console
new Console(stdout[,stderr][,ignoreErrors])
new Console(options)
- options
- stdout: <stream.Writable>
- stdeer: <stream.Writable>
- ignoreErrors: boolean. Default: true
- colorMode: boolean | string true, ‘auto’(default)
- inspectOptions
可用自己实例化一个 console,然后替换默认的。1
2
3
4
5
6
7const output = fs.createWriteStream('./stdout.log');
const errorOutput = fs.createWriteStream('./stderr.log');
// custom simple logger
const logger = new Console({ stdout: output, stderr: errorOutput });
// use it like console
const count = 5;
logger.log('count: %d', count);
console 常用方法
log/assert/count/countReeset/debug/dir/dirxml/error/group/info/table/time/timeEnd/trace/warn 等。
v9.3.0 中,console.debug is now an alias for console.log.
Crypto(TODO)
ECMAScript Modules
- 开启: –experimental-modules。开启后能够加载.mjs 后缀的 es 模块文件
特性:
- 支持:
只有 CLI 进入的主入口点能成为 ESM 依赖的入口。在运行时动态引入也能用于建立 ESM 入口点。
File System
fs 模块提供了接近于标准 POSIX 函数与文件系统交互的 API 接口。
所有 api 均提供了同步和异步的方式。
异步的方式,最后一个参数接收一个完成时回调函数,传递给该回调的参数取决于方法,但第一个参数总是一个异常值。如果操作成功执行,第一个值就是 null 或者 undefined。
使用同步方法产生的异常,可以用 try/catch 捕获,或者让其冒泡。
使用异步方法不能保证谁先完成。如果对时序有要求,需要将其他代码放入回调中。
在复杂的进程中,优先考虑异步方法。同步的会 block 住主进程,直到它们完成。
File paths
大多数的 fs 操作接收这些形式的文件路径:字符串,Buffer,使用 file: 协议的 URL 对象。
字符串形式的路径被解释为 UTF8 字符序列,可标识绝对或者相对路径。相对路径会相对于当前工作目录(proceess.cwd ())。其他两种形式的 path 不做介绍。
## 文件描述符
在 POSIX 系统上,对于每一个进程来说,内核维护了一张表示当前打开的文件和资源的数据表。每一个打开的文件被赋予了一个简单的数字标志符,称为文件描述符 fd。在系统级别,所有文件系统的操作都使用那些 fd 来识别、追踪具体文件。Windows 操作系统使用了一种不一样,但概念上相似的机制来追踪资源。为了方便使用者,Node.js 抽象了在不同平台的差异,都通过分配一个数字 fd 来标志打开的资源。
fs.open () 方法用来分配一个新的文件描述符(后文统称为 fd)。一旦分配好了,该 fd 可能被用于从文件中读取数据、写入输入或者获取关于文件的信息等。
1 | fs.open('/open/some/file.txt', 'r', (err, fd) => { |
大多数的操作系统限制 fd 的数量,因为它会在任意时间打开,所以当操作完毕的时候,需要及时关闭 fd。不这样做的话,会导致内存泄露,最终让应用拉闸。
线程池使用
除 fs.FSWatcher () 之外的所有文件系统 API 以及明确的同步文件系统 API,它们都使用 libuv 的线程池,这对某些应用程序可能会产生令人惊讶的负面性能影响。有关更多信息,请参阅 UV_THREADPOOL_SIZE 文档。
Class 定义
Class: fs.Dirent
V10.10 新增,当 fs.readdir 或者同步方法以 withFileTypes: true 调用时,返回的结果不再是字符串或者 Buffers,而是 fs.Dirent 对象。
dirent.isBlockDevice()
是否是块设备。
dirent.isCharacterDevice()
dirent.isDirectory()
dirent.isFIFO()
dirent.isFile()
dirent.isSocket()
dirent.isSymbolicLink()
dirent.name
文件名。与 encoding 有关。
Class: fs.FSWatcher
成功的 fs.watch () 调用会返回一个 fs.FSWatcher 对象。当监控的文件发生变化后,‘change’事件会被触发。
Event: ‘change’
- eventType: string. 事件的类型。
- filename: string. 发生改变的文件名。
当监控的目录或者文件发生变化时触发该事件。
Event: ‘close’
V10.0 新增,当 watcher 停止监听变化时触发。
Event: ‘error’
当在监听一个文件时出错后出发。
watcher.close()
停止监听变化。一旦停止,fs.FSWatcher 对象不再可用。(close、error 事件同样)
Class: fs.ReadStream
成功的 fs.createReadStream () 调用会返回一个 fs.ReadStream 对象。所有 fs.ReadStream 对象都是可读流。
Event: ‘close’
当 fs.ReadStream 的底层 fd 被关闭时触发。
Event: ‘open’
- fd
当 fs.ReadStream 的 fd 被打开时触发。
Event: ‘ready’
当 fs.ReadStream 可以使用时触发。在’open’触发之后立即触发。
readStream.bytesRead
- number
到目前为止读取了的字节数。
readStream.path
- string | Buffer
传递给 fs.createReadStream () 的第一个参数。
readStream.pending
v11.2 新增
- boolean
如果底层 fd 还没有打开,会返回 true。比如,在’ready’事件被触发前。
Class: fs.Stats
一个 fs.Stats 的对象提供关于一个文件的信息。
调用 fs.stat/lstat/fstat 以及同步方法返回的对象是 fs.Stats 的实例。如果 options 中的 bigint 被设为 true,那么数字值将会是 bigint 而不是 number。
stats.isBlockDevice()
stats.isCharacterDevice()
stats.isDirectory()
stats.isFIFO()
stats.isFile()
stats.isSocket()
stats.isSymbolicLink()
stats.dev
- number | bigint
包含文件的设备的数字标志符。
stats.ino
- number | bigint
文件系统具体的’Inode’数量。
stats.mode
比特位的描述文件类型及权限。
stats.nlink
- number | biginit
硬链接到该文件的数量。
stats.uid/gid
文件的拥有者 id、组 id
stats.size
文件的总大小(字节)。
stats.atimeMs
文件最后一次访问的毫秒时间。
stats.mtimeMs#
文件最后一次修改的毫秒时间。
stats.ctimeMs
文件状态最后一次被修改的的毫秒时间。
stats.birthtimeMs
文件的创建时间。
stats.atime/mtime/ctime/birthtime
同上。但是返回一个 Date 对象。
Class: fs.WriteStream
可写流。
Event: ‘close’
Event: ‘open’
Event: ‘ready’
writeStream.bytesWritten
writeStream.path
writeStream.pending
fs.access(path[, mode], callback)
- path: string | Buffer | URL
- mode: integer. 默认:fs.constants.F_OK
- callback: Function
- err: Error
以指定 mode 来测试用户对于一个给定 path 的文件或者目录的权限。mode 参数是一个可选的整数,它指定了要检查的具体权限。可以用或运算符来添加多个要检查的权限。比如 (fs.constants.W_OK | fs.constants.R_OK)
最后一个参数,callback,以一个 err 为参数的回调。如果检查中出现任何错误,err 就是 Error 的对象。否则为 null 或者 undefined。
1 | const file = 'package.json'; |
在 fs.open/readFile/writeFile 之前使用 fs.access 来检查可访问性是不推荐的。这样做会导致竞态出现:两个操作之间可能有其他处理改变了文件的状态。相反,用户应该直接使用 open/readFile/writeFile 等方法,在回调中去检查文件是否可访问。
通常来说,只有当不直接使用文件时,才会使用 fs.access 来检查可访问性。比如,一个文件是否存在,作为一个开关控制程序的运行。
Windows 上,目录的访问控制政策 access-control policies (ACLs) 也许限制访问文件或者目录。然而 fs.access 函数并不检查 ACL,因此也许会返回该路径可访问的状态,即使 ACL 限制了用户读取或者写入。
fs.accessSync(path[, mode])
无返回值。如果有错,会 throw 一个错误。需要 try、catch 捕获。
1 | try { |
fs.appendFile(path, data[, options], callback)
- path: string | Buffer | URL | number(fd)
- data: string | Buffer
- options: object | string。如果是字符串,则指定编码格式。
- encoding: string | null. Default: ‘utf8’
- mode: 整数。默认 0o666.
- flag: string。详见文件系统支持的 flag。默认: ‘a’
- callback: function
- err: Error
异步地增加数据到一个文件。如果文件不存在则创建。
fs.appendFileSync(path, data[, options])
同样用 try/catch 捕获错误。
fs.chmod(path, mode, callback)
改变一个文件的权限。
File modes
mode 参数被 fs.chmod 和 fs.chmodSync 方式使用。
Constant | Octal | Description |
---|---|---|
fs.constants.S_IRUSR | 0o400 | read by owner |
fs.constants.S_IWUSR | 0o200 | write by owner |
fs.constants.S_IXUSR | 0o100 | execute/search by owner |
fs.constants.S_IRGRP | 0o40 | read by group |
fs.constants.S_IWGRP | 0o20 | write by group |
fs.constants.S_IXGRP | 0o10 | execute/search by group |
fs.constants.S_IROTH | 0o4 | read by others |
fs.constants.S_IWOTH | 0o2 | write by others |
fs.constants.S_IXOTH | 0o1 | execute/search by others |
fs.chmodSync(path, mode)
fs.chown(path, uid, gid, callback)
- uid: integer
- gid: integer
fs.chownSync(path, uid, gid)
fs.close(fd, callback)
- fd: integer
- callback: Function
- err
关闭 fd。
fs.closeSync(fd)
fs.constants
- Object
返回一个只包含文件系统操作的常量对象。定义在 FS Constants (见官网).
fs.copyFile(src, dest[, flags], callback)
- src: string | Buffer | URL
- dest: string | Buffer | URL
- flags: number。复制的修改符。默认:0
- callback
默认上,dest 如果已经存在,会覆盖。Node.js 不保证复制操作的原子性。如果在已打开 dest 文件准备写入而发生错误,Node.js 会尝试清除 dest 文件。
flags 是个可选的整型,它指定了复制操作的行为。可以使用 OR 来包含更多标志位。(e.g. fs.constants.COPYFILE_EXCL | fs.constants.COPYFILE_FICLONE)
1 | const fs = require('fs'); |
fs.createReadStream(path[, options])
- options: string | object
- flags: default: ‘r’
- encoding: string. default: ‘null’
- fd: integer. 默认 null
- mode: integer。 default:0o666
- autoClose: boolean. default: true
- start: integer
- end: integer. default: Inifinity
- highWaterMark: integer. default: 64 * 1024
- returns: fs.ReadStream
不像 readable stream 一样默认的 16kb highWaterMark 值,该返回的 stream 默认为 64kb。
opions 中的 start 和 end 可以读取某个范围内地数据,而不是整个文件。start 和 end 都是包含临界点地数据,同时以 0 为开始。如果 fd 指定了,start 忽略了,fs.createReadStream 会从文件的当前位置开始读取。如果 fd 指定了,ReadStream 会忽略 path 参数而使用具体的 fd。这意味着将没有 open 事件触发。fd 应该是阻塞的;非阻塞的 fd 应该传递给 net.Socket.
如果 fd 指向了只支持阻塞读取的字符设备,比如键盘或者声卡,在数据可用之前,读操作不会完成。这避免了进程退出和流关闭。
1 | const fs = require('fs'); |
fs.createWriteStream(path[, options])
fs.fchmod(fd, mode, callback)
和 chomd 差不多,不过第一个参数不是 path 了,而是 fd。
fs.fchown(fd, uid, gid, callback)
fs.fsync(fd, callback)
刷新对该 fd 的内核数据到外部永久介质上。
fs.fdatasync(fd, callback)
fdatasync 函数类似于 fsync 函数,但它仅仅影响文件数据部分。
fs.fstat(fd[, options], callback)
fs.ftruncate(fd[, len], callback)
缩短文件到指定 len 长度。
fs.link(existingPath, newPath, callback)
fs.lstat(path[, options], callback)
与 stat 差不多。不过,如果 path 是一个 link 的话,它返回该 link 的信息,而不是该 link 所指向的文件。
fs.mkdir(path[, options], callback)
- path
- options: integer | object
- recursive: boolean. default: false
- mode: integer. Windows 不支持。Default: 0o777
- callback:
- err
fs.mkdtemp(prefix[, options], callback)
- prefix: string
- options: string | object
- encoding: string. Default: ‘utf8’
- callback:
- err
- folder
创建一个唯一的临时文件夹。
fs.open(path[, flags[, mode]], callback)
- path
- flags: string | number. Default: ‘r’
- mode: integer. Default: 0o666 (可读可写)
- callback
- err
- fd
返回一个 fd。
fs.read(fd, buffer, offset, length, position, callback)
- buffer: 将读取的数据放入到 buffer 里
- offset: 写入 buffer 时的偏移
- length: 读取的字节数。
- position: 读取的开始位置。
- callback
- err
- bytesRead: integer
- buffer
fs.readdir(path[, options], callback)
- options
- encoding: string. Default: ‘utf8’
- withFileTypes: boolean. Default: false
- callback
- err
- files: string[] | Buffer[] | fs.Dirent[]
读取目录的内容。若 encoding 为’buffer’,则 files 为 Buffer []; 若 withFileTypes 为 true,则 files 为 fs.Dirent []。
fs.readFile(path[, options], callback)
- path: string | Buffer | URL | integer. 文件名或者 fd
File Descriptors
- 所有具体的文件描述符都必须支持读取;
- 如果一个文件描述符以 path 创建,它不会自动关闭;
- 读取会从当前位置开始。比如:如果文件已经有‘Hello World’内容,而且以 fd 方式已经读取了 6 个字节,对 fs.readFile 使用该 fd 继续读取,数据将是 World,而不是 Hello World。
fs.readlink(path[, options], callback)
读取 link 文件的信息。