为什么是js
大佬目标是高性能web服务器, 高性能web服务器有几个要点:事件驱动、非阻塞I/O js满足高性能(v8)、符合事件驱动、没历史包袱
node特点
- 异步I/O 多文件读取耗时非和的关系,而是最慢的那个
- 事件与回调 jser的异步编程思维
- 单线程
- 与其它线程无法共享状态
- 好处
- 无死锁
- 无线程上下文交换带来对性能开销
- 缺点
- 无法利用多核 (有方案)
- 错误会引起整个应用的退出 (健壮性)(pm2)
- 大量计算占用CPU无法继续调用异步I/O (子进程) 和浏览器端js和UI共用一个线程一样,js长时间执行UI渲染和响应会被中断 web worker创建工作线程计算解决大计算UI渲染问题 不阻塞主线程 通过消息传递传递运行结果,即工作线程不能访问主线程中的UI node 后续的I/O发不出调用,已完成异步的I/O回调无法执行, 子进程 将计算分发到子进程 分解掉 通过子进程掉事件消息传递结果
- 跨平台
基于libuv,最上层是node,下层是libuv,再往下是 *nix和windows
应用场景
- I/O密集型 node利用事件循环处理而不是启动每个线程为每个请求服务,占用资源极少
- CPU密集型 单论效率 V8毋庸置疑,主要挑战是由于js单线程,遇到长时间计算(大循环深克隆)将导致CPU片不能释放,后续I/O无法发起 即若是纯计算无I/O肯定多线程适合,否则node的异步I/O配合子进程或编写C/C++扩展 二者并没高低之分
- 和谐共处 node做中间层 发挥善于处理高并发的优点
模块机制
- commonJS规范
- 愿景 js能运行在任何地方 涵盖了模块 二进制 buffer 字符集编码 I/O流 进程环境 文件系统 套接字 单元测试 web服务器网关接口 包管理等
- 出发点
- 解决了js 无模块系统
- 标准库少
- 无标准接口 对web服务器 数据库操作
- 缺乏包管理系统
让js除了玩服务端,还可以做命令行工具 桌面应用 混合应用
对模块的规范
- 引用
var math = require('math')
- 定义 node中一个文件就是一个模块 module对象代表模块自身,exports是module的属性 exports对象用于导出当前模块的方法或变量,且是唯一导出的出口
- 标识 是传递给require方法的参数,是必须符合小驼峰命名的字符串 可以没有文件名后缀.js
- 意义 将类聚的方法和变量等限定在私有作用域中,支持导入导出,每个模块都有独立的空间互不干扰,使用户不用考虑变量污染。
node的模块实现 非完全按照规范 有一定取舍
- 分类
- 核心模块(node提供) 在node源码编译过程中,编译进了二进制执行文件,在node进程启动时部分模块直接加载进内存(即核心模块引入时文件定位和编译执行可以省略掉,加载速度最快)
- 文件模块(用户编写) 运行时动态加载,需完整的路径分析 文件定位 编译执行 速度比核心模块慢
- 加载顺序 二次加载优先从缓存加载 核心模块的缓存检查优先于文件模块
- 路径分析
- 标识符分析
- 核心模块
- .或..开头的相对路径文件模块
- 以/开头的绝对路径文件模块
- 非路径形式的文件模块 如自定义的connect模块 核心模块 试图加载一个与核心模块标识符相同的自定义模块不会成功,想成功必须选一个不同的标识符或路径方式 路径形式的文件模块 .或..和/开头的都会被当作文件模块处理,require时会被转为真实路径,并以真实路径为索引,将编译后的结果放到缓存中 自定义模块 以一个文件或包的形式 查找最费时 最慢,具体不细写了 node会逐个尝试模块路径中的路径, 即路径越深,查找耗时越多
- 标识符分析
- 文件定位
- 扩展名分析 逐个补.js .json .node。尝试逐个补充后缀会调用fs模块同步阻塞判断文件是否存在。技巧.json .node在require时带上扩展名 会减少些性能浪费
- 目录分析 找不到文件却得到个目录时,node会将目录当成包处理 若最后找不到文件,会抛出查找失败异常
- 编译执行 细节参考书籍
- js文件 node会对之头尾包装 保证模块文件间的作用域隔离 而module.exports和exports同时存在原因是exports对象是通过形参的方式传入,直接赋值会改变形参的引用,但不能改变作用域外的值
- c/c++模块的编译(具体细节:转编码过程会有位运算提升性能) node会调用process.dlopen()方法加载执行 具体windows和*nix有不同实现,libuv兼容层进行了封装 .node文件时c/c++编译后生成的 只有加载和执行的过程
- json文件 用fs模块同步读取调用JSON.parse()得到对象然后赋值给模块对象的exports
- npm 谁对commonJS规范的一种实现 npm和node相当于gem和ruby 让node与第三方模块有个很好的生态系统
- AMD 是commonJS规范的延伸 依赖前置,提前执行 requireJS
- CMD 玉伯提的 就近依赖,延迟执行 seaJS
异步I/O
- 吊打会单台服务器的同步阻塞PHP node利用单线程远离死锁状态同步等问题,利用异步I/O让单线程远离阻塞更好的使用CPU
- 异步I/O的细节问题 立即返回的不是业务层期望的数据都是调用状态,为获得完整数据,还有轮询(重复调用I/O确认操作是否完成)
- 具体轮询的实现主要有
细节有兴趣自查
- read 最原始的 得到数据前 CPU一直耗用在等待上
- select read基础的改进 通过对文件描述符上的事件状态进行判断
- poll select上的改进 1采用了链表方式避免数组长度限制 2能避免不需要的检查 缺点 文件描述符过多时 性能还是不高
- epoll Linux下效率最高的I/O事件通知机制
- kqueue 与epoll类似 但仅在FreeBSD系统存在
- 理想的非阻塞异步I/O
- 应用程序发起调用,无须通过遍历或事件唤醒等方式轮询,可直接处理下一个任务,只需在I/O完成后通过信号或回调将数据传递给应用即可
- 幸运的是linux下还真有该方式 原生提供的AIO 不幸的是只有linux下有且缺陷是仅支持内核I/O中的O_DIRECT方式获取,导致无法利用系统缓存
- 现实的异步I/O node中自行实现了线程池来完成异步I/O windows下的IOCP也是线程池原理 不同的地方在于线程池是系统内核管理的 注意的点 常说的node单线程仅仅是js执行在单线程上 node中无论*nix还是windows 内部完成I/O的另有线程池
- 具体轮询的实现主要有
- node的异步I/O
主要由事件循环 观察者 请求对象 I/O线程池来配合完成
- windos通过IOCP向系统内核发送I/O调用和从内核获取已完成的I/O操作,再配合时间循环完成
- linux下通过epoll
- FreeBSD下kqueue
- Solaris Event ports 不同点是windows下线程池内核(IOCP)直接提供 *nix下libuv自行实现
- 小结 js是单线程,但不能说node不能充分利用多核 node自身是多线程,只是I/O线程使用CPU较少,除了用户代码无法并行执行外,所有I/O(网络I/O 磁盘I/O)是可以并行的
- 非I/O异步的异步API
- 定时器(精度不够 需动用红黑树浪费性能)
- process.nextTick 对以上缺点优化
- setImmediate node9.1前类似功能是由process.nextTick承担的
- 优先级 231 2属于idle观察者 3属于check观察者 idle观察者先于I/O观察者,I/O观察者先于check观察者
- 具体实现:
- process.nextTick的回调保存在一数组中
- setImmediate的结果保存中链表中
- 行为上:
- process.nextTick在每轮循环中会将数组中的回调全部执行完
- setImmediate中每轮循环中执行链表中的一个回调函数
- 结果上 第一个setImmediate回调执行后,并没立即执行第二个回调,而是进入了下一轮循环,再次按process.nextTick优先 setImmediate次后的顺序。这么设计是为了保证每轮循环较快结束,防止CPU占用过多而阻塞后续I/O调用
- 事件驱动与高性能服务器
本质是通过主循环增加事件触发的方式来运行程序 是异步实现多核心
- 几种服务器模型
- 同步式 一次只能处理一个请求,其余都在等待状态
- 每进程每请求 不具备扩展性,系统资源就这么多
- 每线程每请求 线程比进程要轻量,但每个线程都要占用一定内存,大并发请求时,内存会很快被用光,导致服务器缓慢,大型站点不适用(目前apache还在用该方式)
- 事件驱动 node nginx(纯c写的) 无需为每个请求创建额外多对应线程,可以省掉创建和销毁线程的开销,同时操作系统在调度任务时因为线程少,上下文切换代价就低,大量连接下也能不受线程上下文切换开销影响 (node高性能的一个原因)
- ruby的Event Machine python的Twisted也都是事件驱动,不过他们都是同步I/O
- 几种服务器模型
异步编程
- 函数式编程 js异步编程的基础
- 高阶函数 普通函数参数只接受基本数据类型或对象的引用,返回值也是基本数据类型和对象引用 高阶是可以把函数作为参数或将函数作为返回值多函数 常用的有forEach map filter reduce some等等
- 优势与难点
- node的事件驱动和非阻塞I/O模型让CPU和I/O不相互依赖等待,资源更好等利用,对于网络而言并行延展开就是分布式和云 事件模型应对海量请求要注意点就是,海量请求作用到单线程上防止计算耗费过多CPU时间片,建议对CPU的耗用不要超过10ms
- 难点
- 异常捕获 异步I/O包含两个阶段 提交请求和处理结果 try catch只能捕获当次事件循环内的异常,对callback的异常无能为力。node形成了约定 将异常作为回调的第一个实参传回,空值,表明异步调用无异常。
- 嵌套过深 async await + promise
- 阻塞代码 js无sleep 定时器是异步 不能阻塞后面代码到执行
- jser无多线程编程经验
- 解决方案
- 事件发布订阅
一个事件与多个回调关联,这些回调也叫事件侦听器
- node对订阅发布做了些处理
- 一个事件超过10个侦听器 会有警告 主要还是单线程问题 侦听器过多 可能会过多占用CPU及造成内存泄漏
- 异常处理 EventEmitter对象对error事件进行了特殊对待,运行期间发生error事件,EventEmitter对象会检查对error事件是否添加过侦听器,加了,错误会交给侦听器处理,否则作为异常抛出,若外部没有捕获,将会引起线程退出
- node对订阅发布做了些处理
- 事件发布订阅
内存控制
- V8垃圾回收与内存限制
node基于V8构建,与别的后端语言不同,V8对内存使用会有限制64位可用约1.4G,32位0.7G(老生代1400MB和700MB,新生代32MB和16MB)。V8在浏览器端用起来绰绰有余,但服务端要是操作单个1.4G以上的文件到内存中就会引起进程退出了
- V8为何限制内存用量
表面是V8最初为浏览器设计,深层次的原因是V8的垃圾回收机制所限制
V8中所有js对象都是通过堆来分配的,声明变量并赋值时,所使用的对象的内存就分配在堆中,若申请的堆空闲内存不够分配新的对象,就继续申请堆内存,直到超过V8的限制。 按照官方说法,1.5G的垃圾回收堆内存,V8需要50ms以上,非增量则要1s以上,这是垃圾回收中引起js线程暂停执行的时间。这样的话性能和响应能力都会直线下降,浏览器端都无法接受,因此当时的考虑下直接限制了堆内存。 当然可以在node启动时调整限制。node --max-old-space-size=2048 test.js // 单位MB 老生代 node --max-new-sapce-size=1024 test.js // 单位KB 新生代 二者大小只和就是V8堆的整体大小
上述参数在V8初始化时生效,一旦生效不能再动态改变。
- V8的主要垃圾回收算法
- 分代式
按对象的存活时间将内存的垃圾回收进行不同的分代
,然后分别对不同的分代的内存用更高效的算法(因为没有一种算法能搞定所有场景)- 新生代 存活时间较短的对象 Scavenge算法做垃圾回收缺点是只能使用一半的堆内存(牺牲空间换时间,无法大规模用非常适合存活周期较短的新生代) 具体实现是Cheney(jvm gc)算法(复制方式将堆内存一分为二)一个闲置(To空间)一个使用中(Form空间)
- 老生代 长或常驻内存的
- Mark-Sweep和Scavenge只复制存活的对象不同,这哥们只清除死亡对象 问题是一次标记清除后,
内存空间会出现不连续的状态
。 这种内存碎片化会对后续的内存分配造成问题,(大对象无法分配,就会提前触发此次并不必要的垃圾回收) - 和Mark-Compact 解决上面问题标记整理,但是移动对象速度就慢下来了,所以V8主要用清除,空间不够时才用
- Mark-Sweep和Scavenge只复制存活的对象不同,这哥们只清除死亡对象 问题是一次标记清除后,
- 对象的晋升
- 当一个新生代对象多次复制依然存活时,它就会被认为是生命周期较长的对象,就会被移动到老生代
- 还有个就是To空间的使用占比 超过25%(这个值是因为回收完成后To空间将变成Form空间,占比过高会影响后续内存分配)
- 全停顿
为了避免js应用逻辑和垃圾回收器看到的不一致,上面的三种算法都会暂停应用逻辑,回收完成后才执行,小的垃圾回收只回收新生代,影响不大,但老生代影响就大了。
- 增量标记 将一步停顿完成的动作拆分成多个 应用逻辑->回收->再执行->回收 改进后最大可减少到原来的1/6耗时
- 深入还有延迟清理和增量式清理等 还有并行标记和并行清理来利用多核
- 日志收集
- 启动时添加–trace_gc gc.log
- –prof 可得到V8执行时的性能分析数据
- linux-tick-processor工具
- 分代式
buffer对象不同于其他对象 不受V8限制 即可以利用堆外内存突破限制 具体见buffer
- V8为何限制内存用量
- 高效使用内存 去触发垃圾回收
- 作用域
- 函数调用 调用时会创建作用域 执行后销毁
- 变量主动释放
- with
- 全局作用域 不是var或定义在global上的变量 可通过delete或赋值为undefined或null delete删除对象可能会干扰V8的优化
- 闭包 解除引用
- 作用域
- 内存泄漏
应回收的对象意外没回收,变成了老生代对象常驻
- 常见的一些工具 node-heapdump等
- 缓存
牛逼但不宜滥用
慎将内存当缓存 进程间无法共享内存 要有过期 大小策略 对象一旦缓存就常驻老生代,越多垃圾回收时就越多做无用功,且jser喜欢用对象的键值对来缓存东西,而严格意义上的缓存是要有完善的过期策略 高效缓存就是LRU算法了 模块的缓存 - 队列消费不及时
- 作用域未释放
- stream 有序字节序列 继承自EventEmitter专门处理大文件 V8内存限制不能直接用fs.readFile和fs.writeFile搞大文件,改用fs.createReadStream和fs.createWriteStream通过流的方式 需要字符串层面操作时用buffer 但也要注意物理内存
Buffer对象 细节理解很重要
- 定义
类似数组 16进制的两位数 即只能表示0-255的数值
若给的值是非0-255的整数
- 小于0 逐次+256 直到满足
- 大于 逐次-256
- 小数 舍弃小数部分 只保留整数
- 内存分配
- 不在V8堆内存中,在C++层面申请,js分配创建时确定大小且无法调整 因为处理大量的字节数据不能采用需要一点就向操作系统申请一点的方式,会造成大量的内存申请调用,给操作系统造成压力
- 很重要的细节slab分配机制动态管理 简单理解slab就是申请好的固定大小的内存区域 有三种状态
- full 全分配
- partial 部分分配
- empty 没被分配
需要一buffer对象时可用构造函数形式来分配指定大小的buffer对象
new Buffer(size) 为了更安全可靠new Buffer已经被弃用 Buffer.from()、Buffer.alloc()、和 Buffer.allocUnsafe()
node以8k为界限区分大对象还是小对象
- 分配过程 简单理解 大块buffer 使用c++层提供的内存,小而频繁的buffer slab动态机制先申请后分配,减少js到操作系统间的频繁系统调用 主要使用一局部变量poll作为中间处理对象,处于分配状态的slab单元都指向它
- buffer转换 buffer对象可以和字符串互转 支持的字符串编码类型有ASCII UTF-8 Base64等
- 字符串转buffer对象 主要通过构造函数完成 new Buffer(str, [encoding]) 构造函数转换的buffer对象存储类型只能是一种编码类型 encoding参数不传递默认utf8 buffer可以存储不同编码类型的字符串转码等值,但转出来需要注意,因为不同编码的字节长度不同
- buffer转字符串 buf.toString([encoding], [start]. [end])
- isEncoding判断是否是node支持的编码类型
- buffer拼接
- 乱码问题 buffer对象长度引起的 中文在u8占3个字节
- 避免办法
var rs = fs.createReadStream('test.md', {highWaterMark:11}); //限定buffer长度为11 rs.setEncoding('utf8') // 能避免大部分 正确拼接buffer 用一个数组存储接收的所有buffer片段并记录所有片段总长,然后调用buffer.concat()生成一合并的buffer对象 该方法封装了从小buffer对象到大buffer的对象复制过程
- 运用 主要在文件I/O和网络I/O中运用 二进制传输 预先转换静态内容为buffer对象,可有效减少CPU的重复使用。在页面中可将动态内容与静态内容分离,静态内容部分通过预先转换buffer的方式。文件自身是二进制数据,所以在不需要改变内容的场景下尽量只读取buffer,然后直接传输不做额外转换 highWaterMark缓存池 越大越快
- 未能理解的点
网络编程
- 强化node理解 是一面向网络而生,事件驱动 无阻塞 单线程
- node提供了net dgram http https4个模块分别处理TCP UDP HTTP HTTPS
- TCP
面向连接(建立通信线路 可靠 电话)
显著特征是传输前3次握手 会话形成后才能互相发送数据,创建会话的过程中服务端和客户端分别提供一套接字,两个套接字共同形成一个链接。-
OSI七层模型 ISO网络五层模型 把上三层统一成应用层 应用层 补充协议smtp imap
-
TCP默认启用了Nagle算法 小数据包会有Nagle算法合并优化 缓冲区数据达到一定数量或时间才发出,即可能被延迟发送可通过socket.setNoDelay(true)去掉,立即发送 但另一端可能会收到多个小数据包合并,但data事件只触发一次。是可写可读Stream对象
-
- UDP (用户数据包协议)
无链接(不需建立通信线路 邮政)
- TCP连接建立需要新建的两个套接字完成,UDP不是,一个套接字可与多个UDP服务通信 提供面向事务的简单不可靠消息传输
- 无需连接,资源消耗低,处理快速灵活,网络差时丢包严重,适用音频视频等偶尔丢包不产生重大影响。 DNS也是基于它实现的。
- UDP套接字只是EventEmitter的实例
- http
基于TCP会话,请求响应式
只做两件事处理请求和发送响应 node事件驱动 无需为每个请求都创建新的线程或进程 curl -v baidu.com 三次握手 请求报文(字节序列在网络的环境中称作报文)(请求头和(get无体)请求体) 响应报文(头体)- 与TCP区别 TCP基于stream 开启keep-alive后一个TCP可用于多次请求和响应
- 可多次调用setHeader设置 但只有writeHead后报头才写入到连接中
- 无论服务在处理业务逻辑时是否发生异常,必须在结束时调用res.end()结束请求,否则客户端一直在等待状态
- 客户端发送大数据时不会直接发送 而是先发个头部带Expect:100-continue请求到服务器,服务允许后才重新发起请求出发request事件
- maxSockets 设置最大并发数 但支持后还要浏览器支持 或设置agent为false以脱离链接池不受并发限制
- WebScoket
从h5新特性混到了RFC规范
- 特点
- 基于事件的编程模型
- 实现客户端和服务端长链接双向通信 只建立一个TCP连接
- 更轻量级的协议头 减少数据传送量
- 构成
- 握手(在HTTP完成,TCP上定的独立协议) 传输
与普通http请求区别
- Upgrade:websocket // 请求服务端协议升级为websocket
- Connection: Upgrade // 请求服务端协议升级为websocket
- Wec-WebSocket-Ket:’xxxx’ // 安全校验 一随机生成的base64编码的字符串 服务端收到后与拼接再通过sha1算法算出结果在base64编码返回给客户端
细节没吃透
具体业务
- 握手(在HTTP完成,TCP上定的独立协议) 传输
与普通http请求区别
- 特点
- 请求方法判断
get请求使用缓存
- url路径解析
- url查询字符串解析
- cookie解析
- basic认证
- 表单数据解析
- 任意格式文件的上传处理
玩转进程
- node在V8引擎之上,即js运行在单个进程的单个线程上。
- 好处:程序状态单一,无锁线程同步等问题,操作系统调度时也少了上下文切换,可提高CPU利用率
- 缺点:多核的利用,进程的健壮性和稳定性如何保证
- 服务器模型的演变 同步php(单线程)->复制进程->多线程apache->事件驱动node ng
- 多进程架构 理想状态下每个进程各利用一个CPU
- node提供了child_process模块并提供了child_process.fork实现进程的复制 fork复制的都是独立的进程,它需要至少30ms启动时间和10m内存 虽然fork进程比较昂贵 但这块只是为了充分利用多核 并发是事件驱动搞的
- Master-Worker 主从模式 主进程负责调度和管理子进程 子进程负责具体的业务
- 进程间通信
- js主线程和UI渲染线程是公用的一个 一个执行另一个暂停 互相阻塞 h5的webworker允许创建工作线程并在后台运行 通过onmessage和postMessage传递消息 但不共享或操作资源
- 子进程和主进程通过send发和message接受传递消息
- fork进程后子进程和父进程通过IPC通道 message send传递消息
- 进程间通信原理
IPC(进程间通信 目的就是为了让不同进程能互相访问资源并协调工作) node间只有消息传递(字符串),不会真正的传输对象
- 实现
- 命名管道、匿名管道 、socket、信号量、共享内存、消息队列、Domain Socket等都可以
- node用的是pipe 但此管非彼管道 具体实现是libuv window下命名管道实现 *nix下 unix domain socket实现(与socket(网络层)类似 双向通信,不同的是该方向通信是在内核中完成的,不用经过网络层 非常高效)
- 父进程创建子进程前会先创建IPC通道并监听它,然后才创建子进程,并通过环境变量告诉子进程该IPC通道的文件描述符,子进程在启动中根据改IPC通道的文件描述符去连接这个已经存在的通道,从而完成父子进程的连接
- 只有启动的子进程是node进程才根据环境变量创建通道,其他的无法实现通信 除非其他的也按照这个约定实现
- 实现
- 多个进程监听一个端口会报错 思路 代理主进程监听80 子进程监听别的 甚至可以做子进程的负载均衡 但代理会用掉两个描述符 描述符优先 影响了扩展力 node使用句柄(是一种可以用来标识资源的引用)解决 send除了通过IPC发送数据外 第二个参数发送句柄 即主进程接收到socket请求后讲socket直接发送给工作进程
- 集群稳定之路
充分利用多核的细节考虑:性能、多个工作进程的存货状态管理、工作进程的平滑重启、配置或静态数据的动态重新载入等
- 负载均衡 抢占式 谁闲置谁抢 但这个繁忙度是CPU的繁忙,对node而言它的繁忙由CPU和I/O两部分构成,sonode提出了一种策略–Round-RObin 轮叫调度
- 状态共享 一般用第三方进行数据存储,放磁盘 redis等 这里的同步机制又涉及到轮询和主动通知
- Cluster
前面具体细节的实现
- 原理 cluster模块就是child_process和net模块的组合应用 内部启动TCP服务器,在fork子进程时TCP服务器端socket的文件描述符发送给工作进程。如果进程是通过cluster.fork复制出来的,那么他的环境变量里就存在node_unique_id,如果工作进程中存在listen监听,它就能拿到该文件描述符通过so_reuseaddr端口重用,从而实现多个子进程共享端口。普通方式启动,同样不存在文件描述符传递共享等
- 虽然cluster可以搞集群了,进程间通信也符合unix的设计理念,每个进程只做一件事,并做好一件事,将复杂分解为简单,将简单组合成强大,但主进程挂了嗯哼
产品化
- 工程化
- 目录结构
- 构建工具
- 编码规范
- 代码审查
- 部署流程
- 性能
- 动静分离
- 缓存
- 多进程架构
- 读写分离
- 日志
- 访问日志
- 异常日志
- 日志分割
- 日志与数据库 分析和记录分开 记录可在线写 分析借助工具同步到数据库中
- 监控报警
- 监控
- 日志
- 异常日志
- 访问日志 qps
- 响应时间
- 进程监控
- 磁盘监控
- 内存监控
- CPU占用监控
- CPU load(cpu平均负载)
- I/O负载
- 网络流量监控
- 应用状态监控
- DNS监控
- 日志
- 报警
- 邮件
- 短信或电话
- 监控系统的稳定性
- 多机器
- 多机房
- 容灾备份
- 监控
搭建局域NPM仓库
- 安装Erlang和CouchDB(noSQL 基于Erlang写的)
- 新建npm数据库
未深入理解的点
- buffer不受V8限制 内存的申请是在C++层面完成 js分配 但又受V8垃圾回收,再但buffer内部的parent属性指向的SlowBuffer对象却又来自Node自身C++中的定义,是C++层面上的buffer对象。不在V8的堆中。??
- V8的垃圾回收利用多核并发??
- 子进程通过句柄同时监听一端口??
node底层对每个端口都设置了SO_REUSERADDR,即不同进程可以监听相同的网卡和端口,该服务的套接字可以被不同进程复用
之前是独立启动的进程相互间不知道文件描述符,但对于send发送的句柄还原出来的服务而言,他们的文件描述符是相同的,so。多个应用监听相同端口时,文件描述符同一时间只能被一个进程所用,即进程服务是抢占式的。