teng-boy / notes Goto Github PK
View Code? Open in Web Editor NEWwelcome
welcome
问题:如果有个请求在处理复杂运算,发生阻塞了,后续请求还能进node服务吗?
一起来看下面的案例
const http = require('http');
const longComputation = () => {
let sum = 0;
for (let i = 0; i < 1e10; i++) {
sum += i;
};
return sum;
};
const server = http.createServer();
server.on('request', (req, res) => {
if (req.url === '/compute') {
console.info('计算开始',new Date());
const sum = longComputation();
console.info('计算结束',new Date());
return res.end(`Sum is ${sum}`);
} else {
res.end('Ok')
}
});
server.listen(3000);
启动后,先请求http://localhost:3000/compute
,再请求http://localhost:3000/test
,会发现在/compute
接口响应之前,/test
没有进入node服务,也不会正常响应。因为/compute
接口在做复杂运算,阻塞了后续请求。所以如果还有人问你【如果有个请求在处理复杂运算,发生阻塞了,后续请求还能进node服务吗?】,你可以肯定的回答他【No】。
我们定义一个函数,经常会这样:
function a(name){
this.name = name;
}
如果想得到a的实例,new a()
即可。给a添加say方法,可以这样做:
function a(name){
this.name = name;
this.say = function(){
console.log(`hello `, this.name);
}
}
var v = new a('teng-boy');
v.say();
上面代码,会输出hello teng-boy
。
疑问:如果有很多子类继承了a中的say方法,会发生什么呢?
function a(name){
this.name = name;
this.say = function(){
console.log(`hello `, this.name);
}
}
function b(name){
a.call(this, name);
}
//用循环模拟1000000个子类
for(let i=0; i<1000000; i++){
var v = new b('teng-boy');
}
console.log(process.memoryUsage());
在node环境中打印内存使用情况:
{ rss: 35753984,
heapTotal: 15204352,
heapUsed: 10977504,
external: 8252 }
疑问:如果将say方法定义在a的原型上呢?
function a(name){
this.name = name;
}
a.prototype.say = function(){
console.log(`hello `, this.name);
}
function b(name){
a.call(this, name);
}
b.prototype = Object.create(a.prototype);
//用循环模拟1000000个子类
for(let i=0; i<1000000; i++){
var v = new b('teng-boy');
}
console.log(process.memoryUsage());
在node环境中打印内存使用情况:
{ rss: 27287552,
heapTotal: 8912896,
heapUsed: 4204088,
external: 8252 }
由上面两种内存的使用情况来看,把公用的方法定义在原型链上,内存占用是优于构造函数的。
原因分析,直接上代码
//第一种,构造函数call继承方法
function a(name){
this.name = name;
this.say = function(){
console.log(`hello `, this.name);
}
}
function b(name){
a.call(this, name);
}
var one = new b('teng-boy1');
var two = new b('teng-boy2');
console.log(one.name === two.name); //false
console.log(one.say === two.say); //false
//第二种,原型链继承方法
function a(name){
this.name = name;
}
a.prototype.say = function(){
console.log(`hello `, this.name);
}
function b(name){
a.call(this, name);
}
b.prototype = Object.create(a.prototype);
var one = new b('teng-boy1');
var two = new b('teng-boy2');
console.log(one.name === two.name); //false
console.log(one.say === two.say); //true
可见,使用原型链继承,利用js沿原型链查找的特性,让a的所有子类的say,都指向a的say。
而不使用原型链继承,每继承一次,就声明一次say函数,造成了内存的浪费。所以在保证性能的
情况下,原型链的继承在内存占用方面,要优于构造函数call的继承。
说明:这是作者的随记,本着互相学习的心态写的,如果有什么不对的地方,欢迎指正。另,欢迎star
示例
while (true) {
console.log("Hello, World!");
}
不久之后输出
...
Hello, World!
Hello, World!
Hello, World!
Hello, World!
Hello, World!
<--- Last few GCs --->
710976 ms: Mark-sweep 698.3 (737.7) -> 698.3 (737.7) MB, 15343.7 / 0 ms [allocation failure] [GC in old space requested].
726188 ms: Mark-sweep 698.3 (737.7) -> 698.3 (737.7) MB, 15212.6 / 0 ms [allocation failure] [GC in old space requested].
741831 ms: Mark-sweep 698.3 (737.7) -> 698.3 (737.7) MB, 15642.7 / 0 ms [last resort gc].
757723 ms: Mark-sweep 698.3 (737.7) -> 698.3 (737.7) MB, 15892.2 / 0 ms [last resort gc].
<--- JS stacktrace --->
==== JS stack trace =========================================
Security context: 0x37c258b9 <JS Object>
1: nextTick [node.js:~465] [pc=0x341fac9c] (this=0x37c7f939 <a process with map 0x563136b1>,callback=0x3e7bf5e1 <JS Function afterWrite (SharedFunctionInfo 0x3e777679)>)
2: arguments adaptor frame: 5->1
3: onwrite(aka onwrite) [_stream_writable.js:~314] [pc=0x341f3f50] (this=0x37c08099 <undefined>,stream=0x3e7bf6b1 <a WriteStream with map 0x56322bc9>,er=0x37c08099 <undefined>)
4: _w...
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - process out of memory
Aborted
在node环境,仅仅是输出个string,为什么会内存不足?这是一个已知的"问题",因为在tty / console的情况下写入stdout是异步的。因此,如果tty / console无法跟上,那么非常快速地记录大量数据很可能会导致大量写入缓冲在内存中。
console的关键代码如下
// internal/console/constructor.js
Console.prototype[kBindStreamsLazy] = function(object) {
let stdout;
let stderr;
Object.defineProperties(this, {
'_stdout': {
enumerable: false,
configurable: true,
get() {
if (!stdout) stdout = object.stdout;
return stdout;
},
set(value) { stdout = value; }
},
'_stderr': {
...
}
});
};
着重观察下 _stdout
对象。当我们运行js代码,执行console.log
的时候,就会调用_stdout
对象。
那么,内容在输出时,是缓存在哪里呢?
请继续看下面的代码
// _http_common.js
const parsers = new FreeList('parsers', 1000, function parsersCb() {
const parser = new HTTPParser();
...
return parser;
});
当请求进来时,上面的new FreeList
会申请大小为1000的缓冲区,我们再看看freelist
干了什么
// freelist.js
constructor(name, max, ctor) {
this.name = name;
this.ctor = ctor;
this.max = max;
this.list = [];
}
...
free(obj) {
if (this.list.length < this.max) {
this.list.push(obj);
return true;
}
return false;
}
调用free的时候,会将obj压入list。
当程序在运行时,如果使用console.log
输出内容很频繁,还没有gc前,内存占用会很高。
结论:生产环境最好不要console.log
,可以将内容输出到文件,就可以解决console.log
带来的内存占用问题。
本文只讨论node事件循环
什么是事件循环?
事件循环使Node.js可以通过将操作转移到系统内核中来执行非阻塞I/O操作。
由于大多数现代内核都是多线程的,因此它们可以处理在后台执行的多个操作。当这些操作之一完成时,内核会告诉Node.js,以便可以将适当的回调添加到轮询队列中以最终执行。
有点抽象,简单暴力上图,简化概述事件循环操作顺序
注意:每个框将被称为事件循环的“阶段”。
每个阶段都有一个要执行的回调FIFO队列。尽管每个阶段都有其自己的特殊方式,但是通常,当事件循环进入给定阶段时,它将执行该阶段特定的任何操作,然后在该阶段的队列中执行回调,直到队列耗尽或回调的最大数量为止已执行。当队列已用完或达到回调限制时,事件循环将移至下一个阶段,依此类推。
阶段概述
setTimeout()
和setInterval()
的回调setImmediate()
在这里调用回调socket.on('close', ...)
下面我们通过一个案例来理解消化一下事件循环的执行过程
假设计划在100毫秒阈值后执行超时,然后脚本开始异步读取耗时95毫秒的文件
const fs = require('fs');
function someAsyncOperation(callback) {
// Assume this takes 95ms to complete
fs.readFile('/path/to/file', callback);
}
const timeoutScheduled = Date.now();
setTimeout(() => {
const delay = Date.now() - timeoutScheduled;
console.log(`${delay}ms have passed since I was scheduled`);
}, 100);
// do someAsyncOperation which takes 95 ms to complete
someAsyncOperation(() => {
const startCallback = Date.now();
// do something that will take 10ms...
while (Date.now() - startCallback < 10) {
// do nothing
}
});
当事件循环进入轮询阶段时,它有一个空队列(fs.readFile()
尚未完成),因此它将等待剩余的毫秒数,直到达到最快的计时器阈值为止。在等待95 ms过去时,fs.readFile()
完成读取文件并将其需要10 ms完成的回调添加到轮询队列并执行。回调完成后,队列中不再有回调,因此事件循环将看到已达到最快计时器的阈值,然后返回到计时器阶段以执行计时器的回调。
现在,我们来看看经典面试题
async function async1(){
console.log('async1 start')
await async2() // 2
console.log('async1 end') // 8
}
async function async2(){
console.log('async2') // 3
}
async1(); // 1
new Promise(function(resolve){
console.log('promise1') // 4
resolve();
console.log('promise2') // 5
}).then(function(){
console.log('promise3') // 9
})
process.nextTick(() => console.log('nextTick')); // 7
console.log('script end') // 6
熟悉的朋友肯定能很快得出答案,输出结果是:
async1 start
async2
promise1
promise2
script end
nextTick
async1 end
promise3
代码执行过程如代码块 1-9 所示。其中函数async1()
里,先输出async1 start
和async2
,遇到await返回,继续寻找下一个同步代码new Promise
执行输出promise1
和promise2
,之后的同步代码块是script end
。到这里,同步代码执行完了。下面开始执行异步代码,而process.nextTick
是在事件循环之前执行,所以异步代码块最先输出nextTick
。最后剩async1 end
和promise3
,因为函数async1()
在队列中的位置比Promise
前,所以先输出async1 end
,再输出promise3
。
总结一下,分清哪些是macrotask,哪些是microtask,哪些的执行优先级比较高,再结合event loop的原理,再分析类似问题,会更加得心应手。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.