In diesem Artikel werden einige Interviewfragen zu NodejsModulmechanismus, asynchroner E/A, V8-Garbage-Collection-Mechanismus, Puffermodul, WebSocket, Prozesskommunikation, Middleware usw. mit Ihnen geteilt. Können Sie diese beantworten? Wie viele können Sie richtig beantworten? Probieren wir es unten gemeinsam aus!
nodejs Tutorial
“]1.2 Bitte stellen Sie den Modullademechanismus von require vor
Diese Frage kann grundsätzlich das Verständnis des Interviewers für den Node-Modulmechanismus verstehen Im Grunde das erwähnte Interview4
function Module(id, parent) { this.id = id; this.exports = {}; this.parent = parent; this.filename = null; this.loaded = false; this.children = []; } module.exports = Module; var module = new Module(filename, parent);
// require 其实内部调用 Module._load 方法 Module._load = function(request, parent, isMain) { // 计算绝对路径 var filename = Module._resolveFilename(request, parent); // 第一步:如果有缓存,取出缓存 var cachedModule = Module._cache[filename]; if (cachedModule) { return cachedModule.exports; // 第二步:是否为内置模块 if (NativeModule.exists(filename)) { return NativeModule.require(filename); } /********************************这里注意了**************************/ // 第三步:生成模块实例,存入缓存 // 这里的Module就是我们上面的1.1定义的Module var module = new Module(filename, parent); Module._cache[filename] = module; /********************************这里注意了**************************/ // 第四步:加载模块 // 下面的module.load实际上是Module原型上有一个方法叫Module.prototype.load try { module.load(filename); hadException = false; } finally { if (hadException) { delete Module._cache[filename]; } } // 第五步:输出模块的exports属性 return module.exports; };
1.4 Das wissen wir Es gibt zwei Knoten-Exportmodule. Eine davon ist exports.xxx=xxx und Module.exports={}. Gibt es tatsächlich einen Unterschied? Problem hat das Problem bereits erklärt. Ich hoffe, es kann klarer erklärt werden. Die meisten Ideen zur Beantwortung von Fragen Dieses Kapitel ist Pu Lings „NodeJS auf einfache Weise“ entlehnt Sobald der Schleifenkörperprozess ausgeführt wird, werden wir zu einem Tick.
2.3 Bitte beschreiben Sie den gesamten asynchronen I/O-Prozess
// 上面(1.2部分)的第四步module.load(filename) // 这一步,module模块相当于被包装了,包装形式如下 // 加载js模块,相当于下面的代码(加载node模块和json模块逻辑不一样) (function (exports, require, module, __filename, __dirname) { // 模块源码 // 假如模块代码如下 var math = require('math'); exports.area = function(radius){ return Math.PI * radius * radius } });
在V8中,主要将内存分为新生代和老生代两代。新生代中的对象存活时间较短的对象,老生代中的对象存活时间较长,或常驻内存的对象。
新生代中的对象主要通过Scavenge算法进行垃圾回收。这是一种采用复制的方式实现的垃圾回收算法。它将堆内存一份为二,每一部分空间成为semispace。在这两个semispace空间中,只有一个处于使用中,另一个处于闲置状态。处于使用状态的semispace空间称为From空间,处于闲置状态的空间称为To空间。
当开始垃圾回收的时候,会检查From空间中的存活对象,这些存活对象将被复制到To空间中,而非存活对象占用的空间将会被释放。完成复制后,From空间和To空间发生角色对换。
应为新生代中对象的生命周期比较短,就比较适合这个算法。
当一个对象经过多次复制依然存活,它将会被认为是生命周期较长的对象。这种新生代中生命周期较长的对象随后会被移到老生代中。
老生代主要采取的是标记清除的垃圾回收算法。与Scavenge复制活着的对象不同,标记清除算法在标记阶段遍历堆中的所有对象,并标记活着的对象,只清理死亡对象。活对象在新生代中只占叫小部分,死对象在老生代中只占较小部分,这是为什么采用标记清除算法的原因。
主要问题是每一次进行标记清除回收后,内存空间会出现不连续的状态
闭包和全局变量
什么是内存泄漏
一、全局变量
a = 10; //未声明对象。 global.b = 11; //全局变量引用 这种比较简单的原因,全局变量直接挂在 root 对象上,不会被清除掉。
二、闭包
function out() { const bigData = new Buffer(100); inner = function () { } }
闭包会引用到父级函数中的变量,如果闭包未释放,就会导致内存泄漏。上面例子是 inner 直接挂在了 root 上,那么每次执行 out 函数所产生的 bigData 都不会释放,从而导致内存泄漏。
需要注意的是,这里举得例子只是简单的将引用挂在全局对象上,实际的业务情况可能是挂在某个可以从 root 追溯到的对象上导致的。
三、事件监听
Node.js 的事件监听也可能出现的内存泄漏。例如对同一个事件重复监听,忘记移除(removeListener),将造成内存泄漏。这种情况很容易在复用对象上添加事件时出现,所以事件重复监听可能收到如下警告:
emitter.setMaxListeners() to increase limit
例如,Node.js 中 Agent 的 keepAlive 为 true 时,可能造成的内存泄漏。当 Agent keepAlive 为 true 的时候,将会复用之前使用过的 socket,如果在 socket 上添加事件监听,忘记清除的话,因为 socket 的复用,将导致事件重复监听从而产生内存泄漏。
原理上与前一个添加事件监听的时候忘了清除是一样的。在使用 Node.js 的 http 模块时,不通过 keepAlive 复用是没有问题的,复用了以后就会可能产生内存泄漏。所以,你需要了解添加事件监听的对象的生命周期,并注意自行移除。
排查方法
想要定位内存泄漏,通常会有两种情况:
对于只要正常使用就可以重现的内存泄漏,这是很简单的情况只要在测试环境模拟就可以排查了。
对于偶然的内存泄漏,一般会与特殊的输入有关系。想稳定重现这种输入是很耗时的过程。如果不能通过代码的日志定位到这个特殊的输入,那么推荐去生产环境打印内存快照了。
需要注意的是,打印内存快照是很耗 CPU 的操作,可能会对线上业务造成影响。 快照工具推荐使用 heapdump 用来保存内存快照,使用 devtool 来查看内存快照。
使用 heapdump 保存内存快照时,只会有 Node.js 环境中的对象,不会受到干扰(如果使用 node-inspector 的话,快照中会有前端的变量干扰)。
PS:安装 heapdump 在某些 Node.js 版本上可能出错,建议使用 npm install heapdump -target=Node.js 版本来安装。
不会,Buffer属于堆外内存,不是V8分配的。
Buffer.allocUnsafe创建的 Buffer 实例的底层内存是未初始化的。 新创建的 Buffer 的内容是未知的,可能包含敏感数据。 使用 Buffer.alloc() 可以创建以零初始化的 Buffer 实例。
为了高效的使用申请来的内存,Node采用了slab分配机制。slab是一种动态的内存管理机制。 Node以8kb为界限来来区分Buffer为大对象还是小对象,如果是小于8kb就是小Buffer,大于8kb就是大Buffer。
例如第一次分配一个1024字节的Buffer,Buffer.alloc(1024),那么这次分配就会用到一个slab,接着如果继续Buffer.alloc(1024),那么上一次用的slab的空间还没有用完,因为总共是8kb,1024+1024 = 2048个字节,没有8kb,所以就继续用这个slab给Buffer分配空间。
如果超过8kb,那么直接用C++底层地宫的SlowBuffer来给Buffer对象提供空间。
例如一个份文件test.md里的内容如下:
床前明月光,疑是地上霜,举头望明月,低头思故乡
我们这样读取就会出现乱码:
var rs = require('fs').createReadStream('test.md', {highWaterMark: 11}); // 床前明???光,疑???地上霜,举头???明月,???头思故乡
一般情况下,只需要设置rs.setEncoding('utf8')即可解决乱码问题
首先,WebSocket连接必须由浏览器发起,因为请求协议是一个标准的HTTP请求,格式如下:
GET ws://localhost:3000/ws/chat HTTP/1.1 Host: localhost Upgrade: websocket Connection: Upgrade Origin: http://localhost:3000 Sec-WebSocket-Key: client-random-string Sec-WebSocket-Version: 13
该请求和普通的HTTP请求有几点不同:
随后,服务器如果接受该请求,就会返回如下响应:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: server-random-string
该响应代码101表示本次连接的HTTP协议即将被更改,更改后的协议就是Upgrade: websocket指定的WebSocket协议。
密钥:密钥是一种参数,它是在明文转换为密文或将密文转换为明文的算法中输入的参数。密钥分为对称密钥与非对称密钥,分别应用在对称加密和非对称加密上。
对称加密:对称加密又叫做私钥加密,即信息的发送方和接收方使用同一个密钥去加密和解密数据。对称加密的特点是算法公开、加密和解密速度快,适合于对大数据量进行加密,常见的对称加密算法有DES、3DES、TDEA、Blowfish、RC5和IDEA。
非对称加密:非对称加密也叫做公钥加密。非对称加密与对称加密相比,其安全性更好。对称加密的通信双方使用相同的密钥,如果一方的密钥遭泄露,那么整个通信就会被破解。而非对称加密使用一对密钥,即公钥和私钥,且二者成对出现。私钥被自己保存,不能对外泄露。公钥指的是公共的密钥,任何人都可以获得该密钥。用公钥或私钥中的任何一个进行加密,用另一个进行解密。
摘要: 摘要算法又称哈希/散列算法。它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。算法不可逆。
如果不签名会存在中间人攻击的风险,签名之后保证了证书里的信息,比如公钥、服务器信息、企业信息等不被篡改,能够验证客户端和服务器端的“合法性”。
简要图解如下
面对node单线程对多核CPU使用不足的情况,Node提供了child_process模块,来实现进程的复制,node的多进程架构是主从模式,如下所示:
var fork = require('child_process').fork; var cpus = require('os').cpus(); for(var i = 0; i < cpus.length; i++){ fork('./worker.js'); }
在linux中,我们通过ps aux | grep worker.js查看进程
这就是著名的主从模式,Master-Worker
创建子进程的方法大致有:
var createWorker = function(){ var worker = fork(__dirname + 'worker.js') worker.on('exit', function(){ console.log('Worker' + worker.pid + 'exited'); // 如果退出就创建新的worker createWorker() }) }
我自己没用过Kafka这类消息队列工具,问了java,可以用类似工具来实现进程间通信,更好的方法欢迎留言
const Koa = require('koa') const app = new Koa() app.use((ctx, next) => { console.log(1) next() console.log(3) }) app.use((ctx) => { console.log(2) }) app.listen(3001)
执行结果是
1=>2=>3
koa中间件实现源码大致思路如下:
// 注意其中的compose函数,这个函数是实现中间件洋葱模型的关键 // 场景模拟 // 异步 promise 模拟 const delay = async () => { return new Promise((resolve, reject) => { setTimeout(() => { resolve(); }, 2000); }); } // 中间间模拟 const fn1 = async (ctx, next) => { console.log(1); await next(); console.log(2); } const fn2 = async (ctx, next) => { console.log(3); await delay(); await next(); console.log(4); } const fn3 = async (ctx, next) => { console.log(5); } const middlewares = [fn1, fn2, fn3]; // compose 实现洋葱模型 const compose = (middlewares, ctx) => { const dispatch = (i) => { let fn = middlewares[i]; if(!fn){ return Promise.resolve() } return Promise.resolve(fn(ctx, () => { return dispatch(i+1); })); } return dispatch(0); } compose(middlewares, 1);
现在在重新过一遍node 12版本的主要API,有很多新发现,比如说
const util = require('util'); const fs = require('fs'); const stat = util.promisify(fs.stat); stat('.').then((stats) => { // 处理 `stats`。 }).catch((error) => { // 处理错误。 });
更多编程相关知识,请访问:编程入门!!
Das obige ist der detaillierte Inhalt vonKönnen Sie diese Nodejs-Interviewfragen beantworten?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!