From 5ce9caf052086cd323f00fb9366c9c68ada742c8 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Wed, 1 Mar 2017 16:22:21 +0800 Subject: [PATCH 01/98] Add network.md --- README.md | 34 ++++-- assets/socket-backlog.png | Bin 0 -> 214725 bytes assets/tcpfsm.png | Bin 0 -> 53284 bytes sections/js-basic.md | 6 +- sections/module.md | 14 +-- sections/network.md | 224 ++++++++++++++++++++++++++++++++++++++ sections/process.md | 1 - 7 files changed, 259 insertions(+), 20 deletions(-) create mode 100644 assets/socket-backlog.png create mode 100644 assets/tcpfsm.png create mode 100644 sections/network.md diff --git a/README.md b/README.md index 3f56082..714b187 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ # 如何通过饿了么 Node.js 面试 -Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过饿了么大前端的面试, 职位是 2~3 年经验的 Node.js 服务端程序员, 如果你对这个职位感兴趣或者学习 Node.js 一些进阶的内容, 那么欢迎阅读. +Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过饿了么大前端的面试, 职位是 2~3 年经验的 Node.js 服务端程序员 (并不是全栈), 如果你对这个职位感兴趣或者学习 Node.js 一些进阶的内容, 那么欢迎围观. 需要注意的是, 本文针对的并不是零基础的同学, 你需要有一定的 JavaScript/Node.js 基础, 并且有一定的工作经验. 另外本教程的重点更准确的说是服务端基础中 Node.js 程序员需要了解的部分. -如果你觉得大多不了解, 就不用投简历了 (这样两边都节约了时间), 如果你觉得大都有了解或者***光看大纲都都觉得很简单那么欢迎投递简历至 ElemeFe (fe.job@ele.me)***. +如果你觉得大多不了解, 就不用投简历了 (这样两边都节约了时间), 如果你觉得大都有了解或者**光看大纲都都觉得很简单那么欢迎投递简历至 ElemeFe (fe.job@ele.me)**. ### 导读 @@ -18,6 +18,8 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 ## [Js 基础问题](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md) +> 与前端 Js 不同, 后端是直面服务器的, 更加偏向内存方面. + ### 覆盖点 * [`[Basic]` 类型判断](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#类型判断) @@ -37,8 +39,6 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 ## [模块](https://github.com/ElemeFE/node-interview/blob/master/sections/module.md) -> 与前端 Js 不同, 后端是直面服务器的, 更加偏向内存方面, 对于一些更基础的问题也会更加关注. - * [`[Basic]` 模块机制](https://github.com/ElemeFE/node-interview/blob/master/sections/module.md#模块机制) * [`[Basic]` 热更新](https://github.com/ElemeFE/node-interview/blob/master/sections/module.md#热更新) * [`[Basic]` 上下文](https://github.com/ElemeFE/node-interview/blob/master/sections/module.md#上下文) @@ -106,22 +106,29 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 * Stream 的 pipe 的作用是? 在 pipe 的过程中数据是引用传递还是拷贝传递? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#pipe) * 什么是文件描述符? 输入流/输出流/错误流是什么? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#file) * console.log 是同步还是异步? 如何实现一个 console.log? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#console) -* 如何同步的获取用户的输入? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#如何同步的获取用户的输入) +* 如何* `[Doc]` HTTP同步的获取用户的输入? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#如何同步的获取用户的输入) * Readline 是如何实现的? (有思路即可) [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#readline) [阅读更多](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md) ## Network +* [`[Doc]` Net (网络)](https://github.com/ElemeFE/node-interview/blob/master/sections/network.md#net) +* [`[Doc]` UDP/Datagram](https://github.com/ElemeFE/node-interview/blob/master/sections/network.md#udp) +* [`[Doc]` HTTP](https://github.com/ElemeFE/node-interview/blob/master/sections/network.md#http) +* [`[Doc]` DNS (域名服务器)](https://github.com/ElemeFE/node-interview/blob/master/sections/network.md#dns) +* [`[Doc]` ZLIB (压缩)](https://github.com/ElemeFE/node-interview/blob/master/sections/network.md#zlib) +* [`[Point]` RPC](https://github.com/ElemeFE/node-interview/blob/master/sections/network.md#rpc) + ### 常见问题 -* HTTP 协议中的 POST 和 PUT 有什么区别? -* `TCP/UDP` 的区别? `TCP` 粘包是怎么回事,如何处理? `UDP` 有粘包吗? -* `time_wait` 是什么情况?出现过多的 `close_wait` 可能是什么原因? +* HTTP 协议中的 POST 和 PUT 有什么区别? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/network.md#q-post-put) +* TCP/UDP 的区别? TCP 粘包是怎么回事,如何处理? UDP 有粘包吗? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/network.md#q-tcp-udp) +* `TIME_WAIT` 是什么情况? 出现过多的 `TIME_WAIT` 可能是什么原因? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/network.md#q-time-wait) * socket hang up 是什么意思? 一般什么情况下出现? * 列举几个提高网络传输速度的办法? -`更多整理中` +[阅读更多](https://github.com/ElemeFE/node-interview/blob/master/sections/network.md) ## OS @@ -134,6 +141,7 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 ### 常见问题 + * 服务器负载是什么概念? 如何计算负载? * ulimit 是用来干什么的? @@ -205,6 +213,8 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 ## 安全 +* `[Doc]` HTTPS +* `[Doc]` TLS/SSL * `[Point]` XSS * `[Point]` CSRF * `[Point]` 中间人攻击 @@ -218,3 +228,9 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 * 如何避免中间人攻击? `更多整理中` + +## 最后 + +目前 repo 处于施工现场的情况,如果发现问题欢迎在 [issues](https://github.com/ElemeFE/node-interview/issues) 中指出。如果有比较好的问题/知识点/指正,也欢迎提 PR。 + +另外关于 Js 基础 是个比较大的话题,在本教程不会很细致深入的讨论,更多的是列出一些重要或者更服务端更相关的地方,所以如果你拿着《Javascript 权威指南》给教程提 PR 可能不会采纳。本教程的重点更准确的说是服务端基础中 Node.js 程序员需要了解的部分。 diff --git a/assets/socket-backlog.png b/assets/socket-backlog.png new file mode 100644 index 0000000000000000000000000000000000000000..6a878486e3d5d912cd4518da7f2e1005cb6c0e47 GIT binary patch literal 214725 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 z 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 ZnZaCV(yIRpb}s;#>gnp|vd$@?2>{g^G)Mpd literal 0 HcmV?d00001 diff --git a/sections/js-basic.md b/sections/js-basic.md index b759d26..62e9f95 100644 --- a/sections/js-basic.md +++ b/sections/js-basic.md @@ -17,12 +17,12 @@ ## 类型判断 -Javascript 的类型判断其实是个挺折磨人的话题, 不然也不会有 Typescript 出现了. - -能清晰的说出变量类型以及各种 `typeof` 情况的,要么是好好看过书的,要么就是平常代码写得不少的. +Javascript 的类型判断其实是个挺折磨人的话题, 不然也不会有 Typescript 出现了. 在类型判断的问题上, 基础上 推荐阅读 [lodash](https://github.com/lodash/lodash) 的源代码. 这类问题一般只是简单的开场, 不会因为说你不知道 `undefined == null` 的结果是 `true` 就一票否决一个人. 只是根据个人经验看来,这个问题答不清楚的有不小的概率属于基础较差. 如果你对这种问题没有任何概念, 也许要反思一下是不是该找本书过一下 Js 的基础了. +另外在这个问题上, 对使用 TypeScript 以及 flow 同学会有一定的加分. + ## 作用域 在面试时, 作用域并不是一个很好问的知识点, 一般会问的是 `es6 中 let 与 var 的区别`, 或者列举代码, 然后通过对代码的解读来看你对作用域的掌握比较方便. diff --git a/sections/module.md b/sections/module.md index b46232b..4701081 100644 --- a/sections/module.md +++ b/sections/module.md @@ -1,15 +1,15 @@ # 模块 -* [`[Basic]` 模块机制](https://github.com/ElemeFE/node-interview/blob/master/sections/node-basic.md#模块机制) -* [`[Basic]` 热更新](https://github.com/ElemeFE/node-interview/blob/master/sections/node-basic.md#热更新) -* [`[Basic]` 上下文](https://github.com/ElemeFE/node-interview/blob/master/sections/node-basic.md#上下文) +* [`[Basic]` 模块机制](https://github.com/ElemeFE/node-interview/blob/master/sections/module.md#模块机制) +* [`[Basic]` 热更新](https://github.com/ElemeFE/node-interview/blob/master/sections/module.md#热更新) +* [`[Basic]` 上下文](https://github.com/ElemeFE/node-interview/blob/master/sections/module.md#上下文) ## 常见问题 > 如何在不重启 node 进程的情况下热更新一个 js/json 文件? 这个问题本身是否有问题? -可以清除掉 `require` 的缓存重新 `require`, 是具体情况还可以用 VM 模块重新执行. +可以清除掉 `require.cache` 的缓存重新 `require(xxx)`, 视具体情况还可以用 VM 模块重新执行. 当然这个问题可能是典型的 [`X-Y Problem`](http://coolshell.cn/articles/10804.html), 使用 js 实现热更新很容易碰到 v8 优化之后各地拿到缓存的引用导致热更新 js 没意义. 当然热更新 json 还是可以简单一点比如用读取文件的方式来热更新, 但是这样也不如从 redis 之类的数据库中读取比较合理. @@ -70,13 +70,13 @@ function require(...) { ### 热更新 -从面试官的角度看, `热更新` 是很多程序常见的问题, 问的过程中可以一定程度的暴露应聘程序员的水平. +从面试官的角度看, `热更新` 是很多程序常见的问题. 对客户端而言, 热更新意味着不用换包, 当然也包含着 md5 校验/差异更新等复杂问题; 对服务端而言, 热更新意味着服务不用重启, 这样可用性较高同时也优雅和有逼格. 问的过程中可以一定程度的暴露应聘程序员的水平. -因为热更新意味着服务不用重启, 对于服务来说这属于高可用性的特性了. 从 PHP 转 node 的同学可能会有些想法, 比如 PHP 的代码直接刷上去就好了, 并没有什么所谓的重启. 而 node 重启看起来动作还挺大. 当然这里面的区别, 主要是与同时有 PHP 与 node 开发经验的同学可以讨论, 也是很暴露水平的. +从 PHP 转 node 的同学可能会有些想法, 比如 PHP 的代码直接刷上去就好了, 并没有所谓的重启. 而 node 重启看起来动作还挺大. 当然这里面的区别, 主要是与同时有 PHP 与 node 开发经验的同学可以讨论, 也是很好的切入点. 在 Node.js 中做热更新代码, 牵扯到的知识点可能主要是 `require` 会有一个 `cache`, 有这个 `cache` 在, 即使你更新了 `.js` 文件, 在代码中再次 `require` 还是会拿到之前的编译好缓存在 v8 内存 (code space) 中的的旧代码. 但是如果只是单纯的清除掉 `require` 中的 `cache`, 再次 `require` 确实能拿到新的代码, 但是这时候很容易碰到各地维持旧的引用依旧跑的旧的代码的问题. 如果还要继续推行这种热更新代码的话, 可能要推翻当前的架构, 从头开始从新设计一下目前的框架. -不过热更新 json 之类的配置文件的话, 还是可以简单的实现的, 更新 `require` 的 `cache` 可以实现, 不会有持有旧引用的问题, 可以参见我 2 年前写着玩的[例子](https://www.npmjs.com/package/auto-reload), 但是这样写并没什么用, 你要热更新配置的话, 为什么不存数据库? 或者用 `zookeeper` 之类的运维工具? 通过更新文件还要再发布一次, 但是存数据库直接写个接口配个界面多爽你说是不是? +不过热更新 json 之类的配置文件的话, 还是可以简单的实现的, 更新 `require` 的 `cache` 可以实现, 不会有持有旧引用的问题, 可以参见我 2 年前写着玩的[例子](https://www.npmjs.com/package/auto-reload), 但是如果旧的引用一直被持有很容易出现内存泄漏, 而要热更新配置的话, 为什么不存数据库? 或者用 `zookeeper` 之类的服务? 通过更新文件还要再发布一次, 但是存数据库直接写个接口配个界面多爽你说是不是? 所以这个问题其实本身其实是值得商榷的, 可能是典型的 [`X-Y Problem`](http://coolshell.cn/articles/10804.html), 不过聊起来确实是可以暴露水平. diff --git a/sections/network.md b/sections/network.md new file mode 100644 index 0000000..8578671 --- /dev/null +++ b/sections/network.md @@ -0,0 +1,224 @@ +# Network + +* `[Doc]` Net (网络) +* `[Doc]` UDP/Datagram +* `[Doc]` HTTP +* `[Doc]` HTTPS +* `[Doc]` SSL/TLS +* `[Doc]` DNS (域名服务器) +* `[Doc]` ZLIB (压缩) +* `[Point]` RPC + + +## Net + +目前互联化的核心是建立在 TCP/IP 协议的基础上的, 这些协议将数据分割成小的数据包进行传输, 并且解决传输过程中各种各样复杂的问题. 关于协议的具体细节推荐阅读 W.Richard Stevens 的[《TCP/IP 详解 卷1:协议》](https://www.amazon.cn/TCP-IP%E8%AF%A6%E8%A7%A3%E5%8D%B71-%E5%8D%8F%E8%AE%AE-W-Richard-Stevens/dp/B00116OTVS/), 本文不做赘述, 只是列举一些常见的知识点, 新人推荐看[《图解TCP/IP》](https://www.amazon.cn/%E5%9B%BE%E4%B9%A6/dp/B00DMS9990/), 抓包工具推荐看[《Wireshark网络分析就这么简单》](https://www.amazon.cn/%E5%9B%BE%E4%B9%A6/dp/B00PB5QQ84/). + +### 粘包 + +默认情况下, TCP 连接会启用延迟传送算法 (Nagle 算法), 在数据发送之前缓存他们. 如果短时间有多个数据发送, 会缓冲到一起作一次发送 (缓冲大小见 `socket.bufferSize`), 这样可以减少 IO 消耗提高性能. + +如果是传输文件的话, 那么根本不用处理粘包的问题, 来一个包拼一个包就好了. 但是如果是多条消息, 或者是别的用途的数据那么久需要处理粘包. + +可以参见网上流传比较广的一个例子, 连续调用两次 send 分别发送两段数据 data1 和 data2, 在接收端有以下几种常见的情况: + +* A. 先接收到 data1, 然后接收到 data2 . +* B. 先接收到 data1 的部分数据, 然后接收到 data1 余下的部分以及 data2 的全部. +* C. 先接收到了 data1 的全部数据和 data2 的部分数据, 然后接收到了 data2 的余下的数据. +* D. 一次性接收到了 data1 和 data2 的全部数据. + +其中的 BCD 就是我们常见的粘包的情况. 而对于处理粘包的问题, 常见的解决方案有: + +* 1. 多次发送之前间隔一个等待时间 +* 2. 关闭 Nagle 算法 +* 3. 进行封包/拆包 + +***方案1*** + +只需要等上一段时间再进行下一次 send 就好, 适用于交互频率特别低的场景. 缺点也很明显, 对于比较频繁的场景而言传输效率实在太低. 不过几乎用做什么处理. + +***方案2*** + +关闭 Nagle 算法, 在 Node.js 中你可以通过 [`socket.setNoDelay()`](https://nodejs.org/dist/latest-v6.x/docs/api/net.html#net_socket_setnodelay_nodelay) 方法来关闭 Nagle 算法, 让每一次 send 都不缓冲直接发送. + +该方法比较适用于每次发送的数据都比较大 (但不是文件那么大), 并且频率不是特别高的场景. 如果是每次发送的数据量比较小, 并且频率特别高的, 关闭 Nagle 纯属自废武功. + +另外, 该方法不适用于网络较差的情况, 因为 Nagle 算法是在服务端进行的包合并情况, 但是如果短时间内客户端的网络情况不好, 或者应用层由于某些原因不能及时将 TCP 的数据 recv, 就会造成多个包在客户端缓冲从而粘包的情况. (如果是在稳定的机房内部通信那么这个概率是比较小可以选择忽略的) + +***方案3*** + +封包/拆包是目前业内常见的解决方案了. 即给每个数据包在发送之前, 于其前/后放一些有特征的数据, 然后收到数据的时候根据特征数据分割出来各个数据包. + +### 可靠传输 + +为每一个发送的数据包分配一个序列号(SYN, Synchronise packet), 每一个包在对方收到后要返回一个对应的应答数据包(ACK, Acknowledgedgement),. 发送方如果发现某个包没有被对方 ACK, 则会选择重发. 接收方通过 SYN 序号来保证数据的不会乱序(reordering), 发送方通过 ACK 来保证数据不缺漏, 以此参考决定是否重传. 关于具体的序号计算, 丢包时的重传机制等可以参见阅读陈皓的 [《TCP的那些事儿(上)》](http://coolshell.cn/articles/11564.html) 此处不做赘述. + +### window + +TCP 头里有一个 Window 字段, 是接收端告诉发送端自己还有多少缓冲区可以接收数据的. 发送端就可以根据接收端的处理能力来发送数据, 从而避免接收端处理不过来. 详细参见陈皓的 [《TCP的那些事儿(下)》](http://coolshell.cn/articles/11609.html) + +> window 是否设置的越大越好? + +类似木桶理论, 一个木桶能装多少水, 是由最短的那块木板决定的. 一个 TCP 连接的 window 是由该连接中间一连串设备中 + +### backlog + +![图片出处 http://www.cnxct.com/something-about-phpfpm-s-backlog/](https://github.com/ElemeFE/node-interview/blob/master/assets/socket-backlog.png) + +关于该 backlog 的定义参见 [man](https://linux.die.net/man/2/listen) 手册: + +> The behavior of the backlog argument on TCP sockets changed with Linux 2.2. Now it specifies the queue length for completely established sockets waiting to be accepted, instead of the number of incomplete connection requests. + +backlog 用于设置客户端与服务端 `ESTABLISHED` 之后等待 accept 的队列长图 (如上图中的 accept queue). 如果 backlog 过小, 在并发连接大的情况下容易导致 accept queue 装满之后断开连接. 但是如果将这个队列设置的特别大, 那么假定连接数并发量是 65525, 以 php-fpm 的 qps 5000 为例, 处理完约耗时 13s, 而这段时间中连接可能早已被 nginx 或者客户端断开, 那么我们去 accept 这个 socket 时只会拿到一个 broken pipe (该例子出处见 [PHP 源码 Set FPM_BACKLOG_DEFAULT to 511](https://github.com/php/php-src/commit/ebf4ffc9354f316f19c839a114b26a564033708a)). 经过我也不懂的计算 backlog 的长度默认是 511. + +另外提一句, 这个 backlog 是通过系统指定时是通过 `somaxconn` 参数来指定 accept queue 的. 而 `tcp_max_syn_backlog` 参数指定的是 SYN queue 的长度. + +### 状态机 + +![tcpfsm.png](https://github.com/ElemeFE/node-interview/blob/master/assets/tcpfsm.png) + +关于网络连接的建立以及断开, 存在着一个复杂的状态转换机制, 完整的状态表参见 [《The TCP/IP Guide》](http://www.tcpipguide.com/free/t_TCPOperationalOverviewandtheTCPFiniteStateMachineF-2.htm) + +state|简述 +-----|--- +CLOSED|连接关闭, 所有连接的初始状态 +LISTEN|监听状态, 等待客户端发送 SYN +SYN-SENT|客户端发送了 SYN, 等待服务端回复 +SYN-RECEIVED|双方都收到了 SYN, 等待 ACK +ESTABLISHED| SYN-RECEIVED 收到 ACK 之后, 状态切换为连接已建立. +CLOSE-WAIT|被动方收到了关闭请求(FIN)后, 发送 ACK, 如果有数据要发送, 则发送数据, 无数据发送则回复 FIN. 状态切换到 LAST-ACK +LAST-ACK|等待对方 ACK 当前设备的 CLOSE-WAIT 时发送的 FIN, 等到则切换 CLOSED +FIN-WAIT-1|主动方发送 FIN, 等待 ACK +FIN-WAIT-2|主动方收到被动方的 ACK, 等待 FIN +CLOSING|主动方收到了FIN, 却没收到 FIN-WAIT-1 时发的 ACK, 此时等待那个 ACK +TIME-WAIT|主动方收到 FIN, 返回收到对方 FIN 的 ACK, 等待对方是否真的收到了 ACK, 如果过一会又来一个 FIN, 表示对方没收到, 这时要再 ACK 一次 + +> `TIME_WAIT` 是什么情况? 出现过多的 `TIME_WAIT` 可能是什么原因? + +`TIME_WAIT` 是连接的某一方 (可能是服务端也可能是客户端) 主动断开连接时, 四次挥手等待被断开的一方是否收到最后一次挥手 (ACK) 的状态. 如果在等待时间中, 再次收到第三次挥手 (FIN) 表示对方没收到最后一次挥手, 这时要再 ACK 一次. 这个等待的作用是避免出现连接混用的情况 (`prevent potential overlap with new connections` see [TCP Connection Termination](http://www.tcpipguide.com/free/t_TCPConnectionTermination.htm) for more). + +出现大量的 `TIME_WAIT` 比较常见的情况是, 并发量大, 服务器在短时间断开了大量连接. 对应 HTTP server 的情况可能是没开启 keepAlive. 如果有开 keepAlive, 一般是等待客户端自己主动断开, 那么`TIME_WAIT` 就只存在客户端, 而服务端则是 `CLOSE_WAIT` 的状态, 如果服务端出现大量 `CLOSE_WAIT`, 意味着当前服务端建立的链接大面积的被断开, 可能是目标服务集群重启或者拔网线/断电了之类. + + +## UDP + +> TCP/UDP 的区别? UDP 有粘包吗? + +协议|连接性|双工性|可靠性|有序性|有界性|拥塞控制|传输速度|量级|头部大小 +---|---|---|---|---|---|---|---|---|--- +TCP|面向连接
(Connection oriented)|全双工(1:1)|可靠
(重传机制)|有序
(通过SYN排序)|无, 有[粘包情况](#粘包)|有|慢|低|20~60字节 +UDP|无连接
(Connection less)|n:m|不可靠
(丢包后数据丢失)|无序|有消息边界, **无粘包**|无|快|高|8字节 + +UDP socket 支持 n 对 m 的连接状态, 在[官方文档](https://nodejs.org/dist/latest-v6.x/docs/api/dgram.html)中有写到在 `dgram.createSocket(options[, callback])` 中的 option 可以指定 `reuseAddr` 即 `SO_REUSEADDR`标志. 通过 `SO_REUSEADDR` 可以简单的实现 n 对 m 的多播特性 (不过仅在支持多播的系统上才有). + + +### 常见的应用场景 + + + + + + + + + + + + + + + +
传输层协议应用应用层协议
TCP电子邮件SMTP
终端连接TELNET
终端连接SSH
万维网HTTP
文件传输FTP
UDP域名解析DNS
简单文件传输TFTP
网络时间校对NTP
网络文件系统NFS
路由选择RIP
IP电话-
流式多媒体通信-
+ +简单的说, UDP 速度快, 开销低, 不用封包/拆包允许丢一部分数据, 监控统计/日志数据上报/流媒体通信等场景都可以用 UDP. 目前 Node.js 的项目中使用 UDP 比较流行的是 [StatsD](https://github.com/etsy/statsd) 监控服务. + + +## HTTP + +目前世界上运行最良好的分布式集群, 莫过于当前的万维网了 (http servers) 了. 目前前端工程师也都是靠 HTTP 协议吃饭的, 所以 2-3 年的前端同学都应该对 HTTP 有比较深的理解了, 所以这里不做太多的赘述. 推荐书籍[《图解HTTP》](https://www.amazon.cn/%E5%9B%BE%E4%B9%A6/dp/B00JTQK1L4/), 博客[HTTP 协议入门](http://www.ruanyifeng.com/blog/2016/08/http.html). + +另外最近几年开始大家对 HTTP 的面试的考察也渐渐偏向[理解 RESTful 架构](http://www.ruanyifeng.com/blog/2011/09/restful.html). 简单的说, RESTful 是把每个 URI 当做资源 (Resources), 通过 method 作为动词来对资源做不同的动作, 然后服务器返回 status 来得知资源状态的变化 (State Transfer); + +### method/status + +因为 HTTP 的方法 (method) 与状态码 (status) 讲解太常见, 你可以使用如下代码打印出来自己看 Node.js 官方定义的, 完整的就不列举了. + +```javascript +const http = require('http'); + +console.log(http.METHODS); +console.log(http.STATUS_CODES); +``` + +一个常见的 method 列表, 关于这些 method 在 RESTful 中的一些应用的详细可以参见[Using HTTP Methods for RESTful Services](http://www.restapitutorial.com/lessons/httpmethods.html) + +methods|CRUD|幂等|缓存 +---|---|---|--- +GET|Read|✓|✓ +POST|Create|| +PUT|Update/Replace|✓ +PATCH|Update/Modify|✓ +DELETE|Delete|✓ + +> GET 和 POST 有什么区别? + +网上有很多讲这个的, 比如从书签, url 等前端的角度去看他们的区别这里不赘述. 而从后端的角度看, 前两年出来一个 《GET 和 POST 没有区别》(出处不好考究, 就没贴了) 的文章比较有名, 早在我刚学 PHP 的时候也有过这种疑惑, 刚学 Node 的时候发现不能像 PHP 那样同时处理 GET 和 POST 的时候还很不适应. 后来接触 RESTful 才意识到, 这两个东西最根本的差别是语义, 引申了看, 协议 (protocol) 这种东西就是人与人之间协商的约定, 什么行为是什么作用都是"约定"好的, 而不是强制使用的, 非要把 GET 当 POST 这样不遵守约定的做法我们也爱莫能助. + +跑题了, 简而言之, 讨论这二者的区别最好从 RESTful 提倡的语义角度来讲比较符合当代程序员的逼格比较合理. + +> POST 和 PUT 有什么区别? + +POST 是新建 (create) 资源, 非幂等, 同一个请求如果重复 POST 会新建多个资源. PUT 是 Update/Replace, 幂等, 同一个 PUT 请求重复操作会得到同样的结果. + + +### headers + +HTTP headers 是在进行 HTTP 请求的交互过程中互相支会对方一些信息的主要字段. 比如请求 (Request) 的时候告诉服务端自己能接受的各项参数, 以及之前就存在本地的一些数据等. 详细各位可以参见 wikipedia: + +* [Request fields](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields) +* [Response fields](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Response_fields) + +有点不想写...整理中 + +### Agent + +Node.js 中的 `http.Agent` 用于池化 HTTP 客户端请求的 socket (pooling sockets used in HTTP client requests). 也就是复用 HTTP 请求时候的 socket. 如果你没有指定 Agent 的话, 默认用的是 `http.globalAgent`. + +另外最近发现一个 Agent 坑爹的地方, 当 keepAlive 为 true 是, 由于 socket 复用, 之前的事件监听如果忘了清楚很容易导致重复监听, 并且旧的监听中的引用不会释放从导致内存泄漏, 参见这个 [issue](https://github.com/nodejs/node/issues/9268). (本组的同学有在整理这方面的文章, 请期待) + + +## DNS + +早期可以用 TCP/IP 通信之后, 有一个比较蛋疼的问题, 就是 ip 都是一串比较长的数字, 比较难记, 于是大家想了个办法, 给每个 ip 取个好记一点的名字比如 `Alan -> 192.168.0.11` 这样只需要记住好记的名字即可, 随着这个名字的规范化最终变成了今天的域名 (Domain name), 而帮助别人记录这个名字的服务就叫域名解析服务 (Domain Name Service). + +DNS 服务主要基于 UDP, 这里简单介绍 Node.js 实现的接口中的两个方法: + +方法|功能|同步|网络请求|速度 +---|---|---|---|--- +.lookup(hostname[, options], cb)|通过系统自带的 DNS 缓存 (如 `/etc/hosts`)|同步|无|快 +.resolve(hostname[, rrtype], cb)|通过系统配置的 DNS 服务器指定的记录 (rrtype指定)|异步|有|慢 + +当你要解析一个域名的 ip 时, 通过 .lookup 查询直接调用 `getaddrinfo` 来拿取地址, 速度很快, 但是如果本地的 hosts 文件被修改了, .lookup 就会拿 hosts 文件中的地方, 而 .resolve 依旧是外部正常的地址. + +由于 .lookup 是同步的, 所以如果由于什么不可控的原因导致 `getaddrinfo` 缓慢或者阻塞是会影响整个 Node 进程的, 参见[文档](https://nodejs.org/dist/latest-v6.x/docs/api/dns.html#dns_dns_lookup). + + +## ZLIB + +在网络传输过程中, 如果网速稳定的情况下, 对数据进行压缩, 压缩比率越大, 那么传输的效率就越高等同于速度越快了. zlib 模块提供了 Gzip/Gunzip, Deflate/Inflate 和 DeflateRaw/InflateRaw 等压缩方法的类, 这些类接收相同的参数, 都属于可读写的 Stream 实例. + +整理中 + +## RPC + +RPC (Remote Procedure Call Protocol) 基于 TCP/IP 来实现调用远程服务器的方法, 与 http 同属应用层. 常用于构建集群, 以及微服务 (推荐一本[《Node.js 微服务》](https://www.amazon.cn/%E5%9B%BE%E4%B9%A6/dp/B01MXY8ARP)虽然我还没看完) + +常见的 RPC 几大代表: + +* [thrift](http://thrift.apache.org/) +* HTTP +* MQ + +整理中 + + diff --git a/sections/process.md b/sections/process.md index b283ea7..d6d563c 100644 --- a/sections/process.md +++ b/sections/process.md @@ -173,7 +173,6 @@ cluster 模块提供了两种分发连接的方式. 第二种方式是由主进程创建 socket 监听端口后, 将 socket 的句柄直接分发给相应的 workder, 然后当连接进来时, 就直接由相应的 worker 来 accept 连接并处理. -使用第二种方式时, 多个 worker 之间会存在竞争关系, 产生一个老生常谈的 "惊群效应" 从而导致效率变低的问题. 该问题常见于 Apache. 并且各自竞争的情况下无法控制一个新的连接由哪个进程来处理, 从而导致各 worker 进程之间的负载不均衡, 比如 70% 的连接终止于八个进程中的两个. ## 进程间通信 From 09c5d181ffbe72db137027b4e67dc46b0d511f3a Mon Sep 17 00:00:00 2001 From: "qingwei.li" Date: Wed, 1 Mar 2017 17:01:21 +0800 Subject: [PATCH 02/98] add website, fixed #6 --- .nojekyll | 0 README.md | 144 +++++++++++++++++++++++++-------------------------- _navbar.md | 8 +++ index.html | 27 ++++++++++ package.json | 11 ++++ 5 files changed, 118 insertions(+), 72 deletions(-) create mode 100644 .nojekyll create mode 100644 _navbar.md create mode 100644 index.html create mode 100644 package.json diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md index 714b187..43dbe52 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![ElemeFE-background](https://github.com/ElemeFE/node-interview/blob/master/assets/ElemeFE-background.png) +![ElemeFE-background](assets/ElemeFE-background.png) # 如何通过饿了么 Node.js 面试 @@ -8,127 +8,127 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 如果你觉得大多不了解, 就不用投简历了 (这样两边都节约了时间), 如果你觉得大都有了解或者**光看大纲都都觉得很简单那么欢迎投递简历至 ElemeFe (fe.job@ele.me)**. -### 导读 +## 导读 -虽然说目的是要通过面试, 但是本教程并不是简单的把所有面试题列出来, 而主要是将面试中需要确认你是否懂的点列举出来, 并进行一定程度的讨论. +虽然说目的是要通过面试, 但是本教程并不是简单的把所有面试题列出来, 而**主要是将面试中需要确认你是否懂的点列举出来**, 并进行一定程度的讨论. 本文将一些常见的问题划分归类, 每类标明涵盖的一些`覆盖点`, 并且列举几个`常见问题`, 通常这些问题都是 2~3 年工作经验需要了解或者面对的. 如果你对某类问题感兴趣, 或者想知道其中列举问题的答案, 可以通过该类下方的 `阅读更多` 查看更多的内容. 整体上大纲列举的并不是很全面, 细节上覆盖率不高, 很多讨论只是点到即止, 希望大家带着问题去思考. -## [Js 基础问题](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md) +## [Js 基础问题](sections/js-basic.md) > 与前端 Js 不同, 后端是直面服务器的, 更加偏向内存方面. ### 覆盖点 -* [`[Basic]` 类型判断](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#类型判断) -* [`[Basic]` 作用域](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#作用域) -* [`[Basic]` 引用传递](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#引用传递) -* [`[Basic]` 内存释放](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#内存释放) -* [`[Basic]` ES6 新特性](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#es6-新特性) +* [`[Basic]` 类型判断](sections/js-basic.md#类型判断) +* [`[Basic]` 作用域](sections/js-basic.md#作用域) +* [`[Basic]` 引用传递](sections/js-basic.md#引用传递) +* [`[Basic]` 内存释放](sections/js-basic.md#内存释放) +* [`[Basic]` ES6 新特性](sections/js-basic.md#es6-新特性) ### 常见问题 -* js 中什么类型是引用传递, 什么类型是值传递? 如何将值类型的变量以引用的方式传递? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#q-value) +* js 中什么类型是引用传递, 什么类型是值传递? 如何将值类型的变量以引用的方式传递? [[more]](sections/js-basic.md#q-value) * js 中, 0.1 + 0.2 === 0.3 是否为 true ? 在不知道浮点数位数时应该怎样判断两个浮点数之和与第三数是否相等? -* const 定义的 Array 中间元素能否被修改? 如果可以, 那 const 的意义是? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#q-const) -* Javascript 中不同类型以及不同环境下变量的内存都是何时释放? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#q-mem) +* const 定义的 Array 中间元素能否被修改? 如果可以, 那 const 的意义是? [[more]](sections/js-basic.md#q-const) +* Javascript 中不同类型以及不同环境下变量的内存都是何时释放? [[more]](sections/js-basic.md#q-mem) -[阅读更多](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md) +[阅读更多](sections/js-basic.md) -## [模块](https://github.com/ElemeFE/node-interview/blob/master/sections/module.md) +## [模块](sections/module.md) -* [`[Basic]` 模块机制](https://github.com/ElemeFE/node-interview/blob/master/sections/module.md#模块机制) -* [`[Basic]` 热更新](https://github.com/ElemeFE/node-interview/blob/master/sections/module.md#热更新) -* [`[Basic]` 上下文](https://github.com/ElemeFE/node-interview/blob/master/sections/module.md#上下文) +* [`[Basic]` 模块机制](sections/module.md#模块机制) +* [`[Basic]` 热更新](sections/module.md#热更新) +* [`[Basic]` 上下文](sections/module.md#上下文) ### 常见问题 -* a.js 和 b.js 两个文件互相 require 是否会死循环? 双方是否能导出变量? 如何从设计上避免这种问题? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/module.md#q-loop) -* 如果 a.js require 了 b.js, 那么在 b 中定义全局变量 `t = 111` 能否在 a 中直接打印出来? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/module.md#q-global) -* 如何在不重启 node 进程的情况下热更新一个 js/json 文件? 这个问题本身是否有问题? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/module.md#q-hot) +* a.js 和 b.js 两个文件互相 require 是否会死循环? 双方是否能导出变量? 如何从设计上避免这种问题? [[more]](sections/module.md#q-loop) +* 如果 a.js require 了 b.js, 那么在 b 中定义全局变量 `t = 111` 能否在 a 中直接打印出来? [[more]](sections/module.md#q-global) +* 如何在不重启 node 进程的情况下热更新一个 js/json 文件? 这个问题本身是否有问题? [[more]](sections/module.md#q-hot) -[阅读更多](https://github.com/ElemeFE/node-interview/blob/master/sections/module.md) +[阅读更多](sections/module.md) -## [事件/异步](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md) +## [事件/异步](sections/event-async.md) -* [`[Basic]` Promise](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#promise) -* [`[Doc]` Events (事件)](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#events) -* [`[Doc]` Timers (定时器)](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#timers) -* [`[Point]` 阻塞/异步](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#阻塞异步) -* [`[Point]` 并行/并发](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#并行并发) +* [`[Basic]` Promise](sections/event-async.md#promise) +* [`[Doc]` Events (事件)](sections/event-async.md#events) +* [`[Doc]` Timers (定时器)](sections/event-async.md#timers) +* [`[Point]` 阻塞/异步](sections/event-async.md#阻塞异步) +* [`[Point]` 并行/并发](sections/event-async.md#并行并发) ### 常见问题 -* Promise 中 .then 的第二参数与 .catch 有什么区别? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#q-1) -* Eventemitter 的 emit 是同步还是异步? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#q-2) -* 如何判断接口是否异步? 是否只要有回调函数就是异步? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#q-3) -* nextTick, setTimeout 以及 setImmediate 三者有什么区别? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#q-4) -* 如何实现一个 sleep 函数? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#q-5) -* 如何实现一个异步的 reduce? (注:不是异步完了之后同步 reduce) [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#q-6) +* Promise 中 .then 的第二参数与 .catch 有什么区别? [[more]](sections/event-async.md#q-1) +* Eventemitter 的 emit 是同步还是异步? [[more]](sections/event-async.md#q-2) +* 如何判断接口是否异步? 是否只要有回调函数就是异步? [[more]](sections/event-async.md#q-3) +* nextTick, setTimeout 以及 setImmediate 三者有什么区别? [[more]](sections/event-async.md#q-4) +* 如何实现一个 sleep 函数? [[more]](sections/event-async.md#q-5) +* 如何实现一个异步的 reduce? (注:不是异步完了之后同步 reduce) [[more]](sections/event-async.md#q-6) -[阅读更多](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md) +[阅读更多](sections/event-async.md) -## [进程](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md) +## [进程](sections/process.md) -* [`[Doc]` Process (进程)](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#process) -* [`[Doc]` Child Processes (子进程)](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#child-process) -* [`[Doc]` Cluster (集群)](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#cluster) -* [`[Basic]` 进程间通信](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#进程间通信) -* [`[Basic]` 守护进程](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#守护进程) +* [`[Doc]` Process (进程)](sections/process.md#process) +* [`[Doc]` Child Processes (子进程)](sections/process.md#child-process) +* [`[Doc]` Cluster (集群)](sections/process.md#cluster) +* [`[Basic]` 进程间通信](sections/process.md#进程间通信) +* [`[Basic]` 守护进程](sections/process.md#守护进程) ### 常见问题 -* 进程的当前工作目录是什么? 有什么作用? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#q-cwd) -* child_process.fork 与 POSIX 的 fork 有什么区别? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#q-fork) -* 父进程或子进程的死亡是否会影响对方? 什么是孤儿进程? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#q-child) -* cluster 是如何保证负载均衡的? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#how-it-works) -* 什么是守护进程? 如何实现守护进程? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#守护进程) +* 进程的当前工作目录是什么? 有什么作用? [[more]](sections/process.md#q-cwd) +* child_process.fork 与 POSIX 的 fork 有什么区别? [[more]](sections/process.md#q-fork) +* 父进程或子进程的死亡是否会影响对方? 什么是孤儿进程? [[more]](sections/process.md#q-child) +* cluster 是如何保证负载均衡的? [[more]](sections/process.md#how-it-works) +* 什么是守护进程? 如何实现守护进程? [[more]](sections/process.md#守护进程) -[阅读更多](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md) +[阅读更多](sections/process.md) -## [IO](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md) +## [IO](sections/io.md) -* [`[Doc]` Buffer](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#buffer) -* [`[Doc]` String Decoder (字符串解码)](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#string-decoder) -* [`[Doc]` Stream (流)](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#stream) -* [`[Doc]` Console (控制台)](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#console) -* [`[Doc]` File System (文件系统)](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#file) -* [`[Doc]` Readline](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#readline) -* [`[Doc]` REPL](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#repl) +* [`[Doc]` Buffer](sections/io.md#buffer) +* [`[Doc]` String Decoder (字符串解码)](sections/io.md#string-decoder) +* [`[Doc]` Stream (流)](sections/io.md#stream) +* [`[Doc]` Console (控制台)](sections/io.md#console) +* [`[Doc]` File System (文件系统)](sections/io.md#file) +* [`[Doc]` Readline](sections/io.md#readline) +* [`[Doc]` REPL](sections/io.md#repl) ### 常见问题 -* Buffer 一般用于处理什么数据? 其长度能否动态变化? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#buffer) -* Stream 的 highWaterMark 与 drain 事件是什么? 二者之间的关系是? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#缓冲区) -* Stream 的 pipe 的作用是? 在 pipe 的过程中数据是引用传递还是拷贝传递? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#pipe) -* 什么是文件描述符? 输入流/输出流/错误流是什么? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#file) -* console.log 是同步还是异步? 如何实现一个 console.log? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#console) -* 如何* `[Doc]` HTTP同步的获取用户的输入? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#如何同步的获取用户的输入) -* Readline 是如何实现的? (有思路即可) [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#readline) +* Buffer 一般用于处理什么数据? 其长度能否动态变化? [[more]](sections/io.md#buffer) +* Stream 的 highWaterMark 与 drain 事件是什么? 二者之间的关系是? [[more]](sections/io.md#缓冲区) +* Stream 的 pipe 的作用是? 在 pipe 的过程中数据是引用传递还是拷贝传递? [[more]](sections/io.md#pipe) +* 什么是文件描述符? 输入流/输出流/错误流是什么? [[more]](sections/io.md#file) +* console.log 是同步还是异步? 如何实现一个 console.log? [[more]](sections/io.md#console) +* 如何* `[Doc]` HTTP同步的获取用户的输入? [[more]](sections/io.md#如何同步的获取用户的输入) +* Readline 是如何实现的? (有思路即可) [[more]](sections/io.md#readline) -[阅读更多](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md) +[阅读更多](sections/io.md) ## Network -* [`[Doc]` Net (网络)](https://github.com/ElemeFE/node-interview/blob/master/sections/network.md#net) -* [`[Doc]` UDP/Datagram](https://github.com/ElemeFE/node-interview/blob/master/sections/network.md#udp) -* [`[Doc]` HTTP](https://github.com/ElemeFE/node-interview/blob/master/sections/network.md#http) -* [`[Doc]` DNS (域名服务器)](https://github.com/ElemeFE/node-interview/blob/master/sections/network.md#dns) -* [`[Doc]` ZLIB (压缩)](https://github.com/ElemeFE/node-interview/blob/master/sections/network.md#zlib) -* [`[Point]` RPC](https://github.com/ElemeFE/node-interview/blob/master/sections/network.md#rpc) +* [`[Doc]` Net (网络)](sections/network.md#net) +* [`[Doc]` UDP/Datagram](sections/network.md#udp) +* [`[Doc]` HTTP](sections/network.md#http) +* [`[Doc]` DNS (域名服务器)](sections/network.md#dns) +* [`[Doc]` ZLIB (压缩)](sections/network.md#zlib) +* [`[Point]` RPC](sections/network.md#rpc) ### 常见问题 -* HTTP 协议中的 POST 和 PUT 有什么区别? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/network.md#q-post-put) -* TCP/UDP 的区别? TCP 粘包是怎么回事,如何处理? UDP 有粘包吗? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/network.md#q-tcp-udp) -* `TIME_WAIT` 是什么情况? 出现过多的 `TIME_WAIT` 可能是什么原因? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/network.md#q-time-wait) +* HTTP 协议中的 POST 和 PUT 有什么区别? [[more]](sections/network.md#q-post-put) +* TCP/UDP 的区别? TCP 粘包是怎么回事,如何处理? UDP 有粘包吗? [[more]](sections/network.md#q-tcp-udp) +* `TIME_WAIT` 是什么情况? 出现过多的 `TIME_WAIT` 可能是什么原因? [[more]](sections/network.md#q-time-wait) * socket hang up 是什么意思? 一般什么情况下出现? * 列举几个提高网络传输速度的办法? -[阅读更多](https://github.com/ElemeFE/node-interview/blob/master/sections/network.md) +[阅读更多](sections/network.md) ## OS diff --git a/_navbar.md b/_navbar.md new file mode 100644 index 0000000..d2cc4fc --- /dev/null +++ b/_navbar.md @@ -0,0 +1,8 @@ +- [首页](/) +- 章节 + - [事件/异步](sections/event-async) + - [IO](sections/io) + - [Javascript 基础问题](sections/js-basic) + - [模块](sections/module) + - [Network](sections/network) + - [进程](sections/process) diff --git a/index.html b/index.html new file mode 100644 index 0000000..9302a6d --- /dev/null +++ b/index.html @@ -0,0 +1,27 @@ + + + + + Node.js Interview + + + + + + +
+ + + + \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..2893a3e --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "node-interview", + "repository": "git@github.com:ElemeFE/node-interview.git", + "scripts": { + "serve": "docsify serve" + }, + "license": "MIT", + "dependencies": { + "docsify-cli": "^3.0.1" + } +} From 1e7b57eae63f88cb53951ee7d8809761980dd424 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Wed, 1 Mar 2017 18:56:20 +0800 Subject: [PATCH 03/98] Add several HTTP questions --- README.md | 8 +++--- sections/network.md | 66 ++++++++++++++++++++++++++++++++++++--------- sections/process.md | 1 + 3 files changed, 58 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 43dbe52..b97957f 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,6 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 > 与前端 Js 不同, 后端是直面服务器的, 更加偏向内存方面. -### 覆盖点 - * [`[Basic]` 类型判断](sections/js-basic.md#类型判断) * [`[Basic]` 作用域](sections/js-basic.md#作用域) * [`[Basic]` 引用传递](sections/js-basic.md#引用传递) @@ -109,9 +107,9 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 * 如何* `[Doc]` HTTP同步的获取用户的输入? [[more]](sections/io.md#如何同步的获取用户的输入) * Readline 是如何实现的? (有思路即可) [[more]](sections/io.md#readline) -[阅读更多](sections/io.md) +[阅读更多](sections/network.md) -## Network +## [Network](sections/io.md) * [`[Doc]` Net (网络)](sections/network.md#net) * [`[Doc]` UDP/Datagram](sections/network.md#udp) @@ -122,7 +120,9 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 ### 常见问题 +* cookie 与 session 的区别? 服务端如何清除 cookie? [[more]](sections/network.md#q-cookie-session) * HTTP 协议中的 POST 和 PUT 有什么区别? [[more]](sections/network.md#q-post-put) +* 什么是跨域请求? 如何允许跨域? [[more]](sections/network.md#q-cors) * TCP/UDP 的区别? TCP 粘包是怎么回事,如何处理? UDP 有粘包吗? [[more]](sections/network.md#q-tcp-udp) * `TIME_WAIT` 是什么情况? 出现过多的 `TIME_WAIT` 可能是什么原因? [[more]](sections/network.md#q-time-wait) * socket hang up 是什么意思? 一般什么情况下出现? diff --git a/sections/network.md b/sections/network.md index 8578671..2075431 100644 --- a/sections/network.md +++ b/sections/network.md @@ -1,13 +1,11 @@ # Network -* `[Doc]` Net (网络) -* `[Doc]` UDP/Datagram -* `[Doc]` HTTP -* `[Doc]` HTTPS -* `[Doc]` SSL/TLS -* `[Doc]` DNS (域名服务器) -* `[Doc]` ZLIB (压缩) -* `[Point]` RPC +* [`[Doc]` Net (网络)](#net) +* [`[Doc]` UDP/Datagram](#udp) +* [`[Doc]` HTTP](#http) +* [`[Doc]` DNS (域名服务器)](#dns) +* [`[Doc]` ZLIB (压缩)](#zlib) +* [`[Point]` RPC](#rpc) ## Net @@ -59,7 +57,7 @@ TCP 头里有一个 Window 字段, 是接收端告诉发送端自己还有多少 > window 是否设置的越大越好? -类似木桶理论, 一个木桶能装多少水, 是由最短的那块木板决定的. 一个 TCP 连接的 window 是由该连接中间一连串设备中 +类似木桶理论, 一个木桶能装多少水, 是由最短的那块木板决定的. 一个 TCP 连接的 window 是由该连接中间一连串设备中 window 最小的那一个设备决定的. ### backlog @@ -97,7 +95,7 @@ TIME-WAIT|主动方收到 FIN, 返回收到对方 FIN 的 ACK, 等待对方是 `TIME_WAIT` 是连接的某一方 (可能是服务端也可能是客户端) 主动断开连接时, 四次挥手等待被断开的一方是否收到最后一次挥手 (ACK) 的状态. 如果在等待时间中, 再次收到第三次挥手 (FIN) 表示对方没收到最后一次挥手, 这时要再 ACK 一次. 这个等待的作用是避免出现连接混用的情况 (`prevent potential overlap with new connections` see [TCP Connection Termination](http://www.tcpipguide.com/free/t_TCPConnectionTermination.htm) for more). -出现大量的 `TIME_WAIT` 比较常见的情况是, 并发量大, 服务器在短时间断开了大量连接. 对应 HTTP server 的情况可能是没开启 keepAlive. 如果有开 keepAlive, 一般是等待客户端自己主动断开, 那么`TIME_WAIT` 就只存在客户端, 而服务端则是 `CLOSE_WAIT` 的状态, 如果服务端出现大量 `CLOSE_WAIT`, 意味着当前服务端建立的链接大面积的被断开, 可能是目标服务集群重启或者拔网线/断电了之类. +出现大量的 `TIME_WAIT` 比较常见的情况是, 并发量大, 服务器在短时间断开了大量连接. 对应 HTTP server 的情况可能是没开启 `keepAlive`. 如果有开 `keepAlive`, 一般是等待客户端自己主动断开, 那么`TIME_WAIT` 就只存在客户端, 而服务端则是 `CLOSE_WAIT` 的状态, 如果服务端出现大量 `CLOSE_WAIT`, 意味着当前服务端建立的链接大面积的被断开, 可能是目标服务集群重启之类. ## UDP @@ -178,13 +176,43 @@ HTTP headers 是在进行 HTTP 请求的交互过程中互相支会对方一些 * [Request fields](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields) * [Response fields](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Response_fields) -有点不想写...整理中 +> cookie 与 session 的区别? 服务端如何清除 cookie? + +主要区别在于, session 存在服务端, cookie 存在客户端. session 比 cookie 更安全. 而且 cookie 不一定一直能用 (可能被浏览器关掉). 服务端可以通过设置 cookie 的值为空并设置一个及时的 expires 来清除存在客户端上的 cookie. + +> 什么是跨域请求? 如何允许跨域? + +出于安全考虑, 默认情况下使用 XMLHttpRequest 和 Fetch 发起 HTTP 请求必须遵守同源策略, 即只能向相同域名请求. 向不同域名的请求被称作跨域请求 (cross-origin HTTP request). 可以通过设置 [CORS headers](https://developer.mozilla.org/en-US/docs/Glossary/CORS) 即 `Access-Control-Allow-` 系列来允许跨域. 例如: + +``` +location ~* ^/(?:v1|_) { + if ($request_method = OPTIONS) { return 200 ''; } + header_filter_by_lua ' + ngx.header["Access-Control-Allow-Origin"] = ngx.var.http_origin; # 这样相当于允许所有来源了 + ngx.header["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, PATCH, OPTIONS"; + ngx.header["Access-Control-Allow-Credentials"] = "true"; + ngx.header["Access-Control-Allow-Headers"] = "Content-Type"; + '; + proxy_pass http://localhost:3001; +} +``` + +> `Script error.` 是什么错误? 如何拿到更详细的信息? + +接上题, 由于同源性策略 (CORS), 如果你引用的 js 脚本所在的域与当前域不同, 那么浏览器会把 onError 中的 msg 替换为 `Script error.` 要拿到详细错误的方法, 处理配好 `Access-Control-Allow-Origin` 还有在引用脚本的时候指定 `crossorigin` 例如: + +```html + +``` + +详见[Javascript Script Error.](https://sentry.io/answers/javascript-script-error/) + ### Agent Node.js 中的 `http.Agent` 用于池化 HTTP 客户端请求的 socket (pooling sockets used in HTTP client requests). 也就是复用 HTTP 请求时候的 socket. 如果你没有指定 Agent 的话, 默认用的是 `http.globalAgent`. -另外最近发现一个 Agent 坑爹的地方, 当 keepAlive 为 true 是, 由于 socket 复用, 之前的事件监听如果忘了清楚很容易导致重复监听, 并且旧的监听中的引用不会释放从导致内存泄漏, 参见这个 [issue](https://github.com/nodejs/node/issues/9268). (本组的同学有在整理这方面的文章, 请期待) +另外最近发现一个 Agent 坑爹的地方, 当 `keepAlive` 为 true 是, 由于 socket 复用, 之前的事件监听如果忘了清除很容易导致重复监听, 并且旧的监听中的引用不会释放从导致内存泄漏, 参见这个 [issue](https://github.com/nodejs/node/issues/9268). (本组的同学有在整理这方面的文章, 请期待) ## DNS @@ -215,10 +243,22 @@ RPC (Remote Procedure Call Protocol) 基于 TCP/IP 来实现调用远程服务 常见的 RPC 几大代表: -* [thrift](http://thrift.apache.org/) +* [Thrift](http://thrift.apache.org/) * HTTP * MQ +### Thrift + +如果要自定义一个 RPC 协议, 估计很难做的比 [Apache Thrift](https://thrift.apache.org/) 更好了. + +### HTTP + +使用 HTTP 协议来进行 RPC 调用也是很常见的, 比较有名的框架参见 [gRPC](http://www.grpc.io/). 不过相比 TCP 连接, 通过 HTTP 1.1 的方式性能比较低, 到 HTTP 2 也许会有很大的提升, 但是尚未测试. + +### MQ + +使用消息队列 (Message Queue) 来进行 RPC 调用在业内有不少例子. 最近几年开始流行用 [Apache kafka](https://kafka.apache.org/). + 整理中 diff --git a/sections/process.md b/sections/process.md index d6d563c..761a9d8 100644 --- a/sections/process.md +++ b/sections/process.md @@ -173,6 +173,7 @@ cluster 模块提供了两种分发连接的方式. 第二种方式是由主进程创建 socket 监听端口后, 将 socket 的句柄直接分发给相应的 workder, 然后当连接进来时, 就直接由相应的 worker 来 accept 连接并处理. +使用第二种方式时, 多个 worker 之间会存在竞争关系, 产生一个老生常谈的 "[惊群效应](https://www.google.com.hk/search?q=%E6%83%8A%E7%BE%A4%E6%95%88%E5%BA%94)" 从而导致效率变低的问题. 该问题常见于 Apache. 并且各自竞争的情况下无法控制一个新的连接由哪个进程来处理, 从而导致各 worker 进程之间的负载不均衡, 比如 70% 的连接被8个进程中的2个处理, 而其他进程比较清闲. ## 进程间通信 From 1c89ef4f2848f46b101c966259f8799fbb29befb Mon Sep 17 00:00:00 2001 From: Lellansin Date: Wed, 1 Mar 2017 19:17:22 +0800 Subject: [PATCH 04/98] Fix docsify pictrue broken --- sections/event-async.md | 2 +- sections/network.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sections/event-async.md b/sections/event-async.md index 35a044b..310ee00 100644 --- a/sections/event-async.md +++ b/sections/event-async.md @@ -12,7 +12,7 @@ ## Promise -![callback-hell](https://github.com/ElemeFE/node-interview/blob/master/assets/callback-hell.jpg) +![callback-hell](assets/callback-hell.jpg) 相信很多同学在面试的时候都碰到过这样一个问题, `如何处理 Callback Hell`. 在早些年的时候, 大家会看到有很多的解决方案例如 [Q](https://www.npmjs.com/package/q), [async](https://www.npmjs.com/package/async), [EventProxy](https://www.npmjs.com/package/eventproxy) 等等. 最后从流行程度来看 `Promise` 当之无愧的独领风骚, 并且是在 ES6 的 Javascript 标准上赢得了支持. diff --git a/sections/network.md b/sections/network.md index 2075431..11a8639 100644 --- a/sections/network.md +++ b/sections/network.md @@ -61,7 +61,7 @@ TCP 头里有一个 Window 字段, 是接收端告诉发送端自己还有多少 ### backlog -![图片出处 http://www.cnxct.com/something-about-phpfpm-s-backlog/](https://github.com/ElemeFE/node-interview/blob/master/assets/socket-backlog.png) +![图片出处 http://www.cnxct.com/something-about-phpfpm-s-backlog/](assets/socket-backlog.png) 关于该 backlog 的定义参见 [man](https://linux.die.net/man/2/listen) 手册: @@ -73,7 +73,7 @@ backlog 用于设置客户端与服务端 `ESTABLISHED` 之后等待 accept 的 ### 状态机 -![tcpfsm.png](https://github.com/ElemeFE/node-interview/blob/master/assets/tcpfsm.png) +![tcpfsm.png](assets/tcpfsm.png) 关于网络连接的建立以及断开, 存在着一个复杂的状态转换机制, 完整的状态表参见 [《The TCP/IP Guide》](http://www.tcpipguide.com/free/t_TCPOperationalOverviewandtheTCPFiniteStateMachineF-2.htm) From bc44c9126969b0c68c6c4b66244c1acf74d7e008 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Wed, 1 Mar 2017 19:52:22 +0800 Subject: [PATCH 05/98] Update RPC thrift drsp, using wiki drsp --- README.md | 4 ++-- sections/event-async.md | 2 +- sections/network.md | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index b97957f..3ca29a7 100644 --- a/README.md +++ b/README.md @@ -107,9 +107,9 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 * 如何* `[Doc]` HTTP同步的获取用户的输入? [[more]](sections/io.md#如何同步的获取用户的输入) * Readline 是如何实现的? (有思路即可) [[more]](sections/io.md#readline) -[阅读更多](sections/network.md) +[阅读更多](sections/io.md) -## [Network](sections/io.md) +## [Network](sections/network.md) * [`[Doc]` Net (网络)](sections/network.md#net) * [`[Doc]` UDP/Datagram](sections/network.md#udp) diff --git a/sections/event-async.md b/sections/event-async.md index 310ee00..f2098f1 100644 --- a/sections/event-async.md +++ b/sections/event-async.md @@ -12,7 +12,7 @@ ## Promise -![callback-hell](assets/callback-hell.jpg) +![callback-hell](../assets/callback-hell.jpg) 相信很多同学在面试的时候都碰到过这样一个问题, `如何处理 Callback Hell`. 在早些年的时候, 大家会看到有很多的解决方案例如 [Q](https://www.npmjs.com/package/q), [async](https://www.npmjs.com/package/async), [EventProxy](https://www.npmjs.com/package/eventproxy) 等等. 最后从流行程度来看 `Promise` 当之无愧的独领风骚, 并且是在 ES6 的 Javascript 标准上赢得了支持. diff --git a/sections/network.md b/sections/network.md index 11a8639..3be53c7 100644 --- a/sections/network.md +++ b/sections/network.md @@ -61,7 +61,7 @@ TCP 头里有一个 Window 字段, 是接收端告诉发送端自己还有多少 ### backlog -![图片出处 http://www.cnxct.com/something-about-phpfpm-s-backlog/](assets/socket-backlog.png) +![图片出处 http://www.cnxct.com/something-about-phpfpm-s-backlog/](../assets/socket-backlog.png) 关于该 backlog 的定义参见 [man](https://linux.die.net/man/2/listen) 手册: @@ -73,7 +73,7 @@ backlog 用于设置客户端与服务端 `ESTABLISHED` 之后等待 accept 的 ### 状态机 -![tcpfsm.png](assets/tcpfsm.png) +![tcpfsm.png](../assets/tcpfsm.png) 关于网络连接的建立以及断开, 存在着一个复杂的状态转换机制, 完整的状态表参见 [《The TCP/IP Guide》](http://www.tcpipguide.com/free/t_TCPOperationalOverviewandtheTCPFiniteStateMachineF-2.htm) @@ -249,16 +249,16 @@ RPC (Remote Procedure Call Protocol) 基于 TCP/IP 来实现调用远程服务 ### Thrift -如果要自定义一个 RPC 协议, 估计很难做的比 [Apache Thrift](https://thrift.apache.org/) 更好了. +> **Thrift**是一种[接口描述语言](https://zh.wikipedia.org/wiki/%E6%8E%A5%E5%8F%A3%E6%8F%8F%E8%BF%B0%E8%AF%AD%E8%A8%80 "接口描述语言")和二进制通讯协议,它被用来定义和创建跨语言的服务。它被当作一个[远程过程调用](https://zh.wikipedia.org/wiki/%E8%BF%9C%E7%A8%8B%E8%BF%87%E7%A8%8B%E8%B0%83%E7%94%A8 "远程过程调用")(RPC)框架来使用,是由[Facebook](https://zh.wikipedia.org/wiki/Facebook "Facebook")为“大规模跨语言服务开发”而开发的。它通过一个代码生成引擎联合了一个软件栈,来创建不同程度的、无缝的[跨平台](https://zh.wikipedia.org/wiki/%E8%B7%A8%E5%B9%B3%E5%8F%B0 "跨平台")高效服务,可以使用[C#](https://zh.wikipedia.org/wiki/C%E2%99%AF "C♯")、[C++](https://zh.wikipedia.org/wiki/C%2B%2B "C++")(基于[POSIX](https://zh.wikipedia.org/wiki/POSIX "POSIX")兼容系统)、Cappuccino、[Cocoa](https://zh.wikipedia.org/wiki/Cocoa "Cocoa")、[Delphi](https://zh.wikipedia.org/wiki/Delphi "Delphi")、[Erlang](https://zh.wikipedia.org/wiki/Erlang "Erlang")、[Go](https://zh.wikipedia.org/wiki/Go "Go")、[Haskell](https://zh.wikipedia.org/wiki/Haskell "Haskell")、[Java](https://zh.wikipedia.org/wiki/Java "Java")、[Node.js](https://zh.wikipedia.org/wiki/Node.js "Node.js")、[OCaml](https://zh.wikipedia.org/wiki/OCaml "OCaml")、[Perl](https://zh.wikipedia.org/wiki/Perl "Perl")、[PHP](https://zh.wikipedia.org/wiki/PHP "PHP")、[Python](https://zh.wikipedia.org/wiki/Python "Python")、[Ruby](https://zh.wikipedia.org/wiki/Ruby "Ruby")和[Smalltalk](https://zh.wikipedia.org/wiki/Smalltalk "Smalltalk")。虽然它以前是由Facebook开发的,但它现在是[Apache软件基金会](https://zh.wikipedia.org/wiki/Apache%E8%BD%AF%E4%BB%B6%E5%9F%BA%E9%87%91%E4%BC%9A "Apache软件基金会")的[开源](https://zh.wikipedia.org/wiki/%E5%BC%80%E6%BA%90 "开源")项目了。该实现被描述在2007年4月的一篇由Facebook发表的技术论文中,该论文现由Apache掌管。 ### HTTP -使用 HTTP 协议来进行 RPC 调用也是很常见的, 比较有名的框架参见 [gRPC](http://www.grpc.io/). 不过相比 TCP 连接, 通过 HTTP 1.1 的方式性能比较低, 到 HTTP 2 也许会有很大的提升, 但是尚未测试. +使用 HTTP 协议来进行 RPC 调用也是很常见的, 相比 TCP 连接, 通过通过 HTTP 的方式性能会差一些, 但是在使用以及调试上会简单一些. 近期比较有名的框架参见 [gRPC](http://www.grpc.io/): + +> gRPC is an open source remote procedure call (RPC) system initially developed at Google. It uses HTTP/2 for transport, Protocol Buffers as the interface description language, and provides features such as authentication, bidirectional streaming and flow control, blocking or nonblocking bindings, and cancellation and timeouts. It generates cross-platform client and server bindings for many languages. ### MQ -使用消息队列 (Message Queue) 来进行 RPC 调用在业内有不少例子. 最近几年开始流行用 [Apache kafka](https://kafka.apache.org/). +使用消息队列 (Message Queue) 来进行 RPC 调用 (RPC over mq) 在业内有不少例子, 比较适合业务解耦/广播/限流等场景. 整理中 - - From 742b4b295511076dca49ac92e764ac6dd45979eb Mon Sep 17 00:00:00 2001 From: Mars Wong Date: Sat, 4 Mar 2017 22:01:25 +0800 Subject: [PATCH 06/98] Fix typo in event-async.md (#11) --- sections/event-async.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sections/event-async.md b/sections/event-async.md index f2098f1..30f3c4f 100644 --- a/sections/event-async.md +++ b/sections/event-async.md @@ -123,7 +123,7 @@ emitter.emit('myEvent'); 使用 emitter 处理问题可以处理比较复杂的状态场景, 比如 TCP 的复杂状态机, 做多项异步操作的时候每一步都可能报错, 这个时候 .emit 错误并且执行某些 .once 的操作可以将你从泥沼中拯救出来. -另外可以注意一下的是, 有些同学喜欢用 emitter 来监控某些类的状态, 但是在这些类释放的时候可能会忘记释放 emitter, 而 emitter 的 listener 可能一直这些类的内部持有其引用从而可能导致内存泄漏. +另外可以注意一下的是, 有些同学喜欢用 emitter 来监控某些类的状态, 但是在这些类释放的时候可能会忘记释放 emitter, 而这些类的内部可能持有该 emitter 的 listener 的引用从而导致内存泄漏. ## 阻塞/异步 From 74a0e285b592584c583d10f8a03ed82445f1c00f Mon Sep 17 00:00:00 2001 From: Mars Wong Date: Sun, 5 Mar 2017 22:13:30 +0800 Subject: [PATCH 07/98] Update process.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 勘误及改进建议 --- sections/process.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sections/process.md b/sections/process.md index 761a9d8..540e9cc 100644 --- a/sections/process.md +++ b/sections/process.md @@ -128,7 +128,7 @@ Node.js 的 `child_process.fork()` 不像 POSIX [fork(2)](http://man7.org/linux/ > 父进程或子进程的死亡是否会影响对方? 什么是孤儿进程? -子进程死亡不会影响父进程, 不过 node 中父进程会收到子进程死亡的信号. 反之父进程死亡, 一般情况下子进程也会跟着死亡, 如果子进程需要死亡却没有随之终止而继续存在的状态, 被称作孤儿进程. 另外, 子进程死亡之后资源没有回收的情况被称作僵死进程. +子进程死亡不会影响父进程, 不过子进程死亡时(线程组的最后一个线程,通常是“领头”线程死亡时),会向它的父进程发送死亡信号. 反之父进程死亡, 一般情况下子进程也会随之死亡, 但如果此时子进程处于可运行态、僵死状态等等的话, 子进程将被`进程1`(init 进程)收养,从而成为孤儿进程. 另外, 子进程死亡的时候(处于“终止状态”),父进程没有及时调用 `wait()` 或 `waitpid()` 来返回死亡进程的相关信息,此时子进程还有一个 `PCB` 残留在进程表中,被称作僵尸进程. ## Cluster From f102605c3f9df406a480f91926882aa23f323e1e Mon Sep 17 00:00:00 2001 From: Mars Wong Date: Sun, 5 Mar 2017 22:37:34 +0800 Subject: [PATCH 08/98] Update process.md --- sections/process.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sections/process.md b/sections/process.md index 540e9cc..062f446 100644 --- a/sections/process.md +++ b/sections/process.md @@ -169,11 +169,11 @@ worker 进程是由 child_process.fork() 方法创建的, 所以可以通过 IPC cluster 模块提供了两种分发连接的方式. -第一种方式 (默认方式, 不适用于 windows), 通过轮询(round-robin)的方式分发连接. 主进程监听端口, 接收到新连接之后, 通过内建的算法来决定将 accept 到的客户端 socket fd 传递给指定的 worker 处理. 使用该方式时, 每个连接由哪个 worker 来处理, 完全由 master 的 round-robin 算法决定. +第一种方式 (默认方式, 不适用于 windows), 通过时间片轮转法(round-robin)分发连接. 主进程监听端口, 接收到新连接之后, 通过时间片轮转法来决定将接收到的客户端的 socket 句柄传递给指定的 worker 处理. 至于每个连接由哪个 worker 来处理, 完全由内置的循环算法决定. -第二种方式是由主进程创建 socket 监听端口后, 将 socket 的句柄直接分发给相应的 workder, 然后当连接进来时, 就直接由相应的 worker 来 accept 连接并处理. +第二种方式是由主进程创建 socket 监听端口后, 将 socket 句柄直接分发给相应的 worker, 然后当连接进来时, 就直接由相应的 worker 来接收连接并处理. -使用第二种方式时, 多个 worker 之间会存在竞争关系, 产生一个老生常谈的 "[惊群效应](https://www.google.com.hk/search?q=%E6%83%8A%E7%BE%A4%E6%95%88%E5%BA%94)" 从而导致效率变低的问题. 该问题常见于 Apache. 并且各自竞争的情况下无法控制一个新的连接由哪个进程来处理, 从而导致各 worker 进程之间的负载不均衡, 比如 70% 的连接被8个进程中的2个处理, 而其他进程比较清闲. +使用第二种方式时, 多个 worker 之间会存在竞争关系, 产生一个老生常谈的 "[惊群效应](https://www.google.com.hk/search?q=%E6%83%8A%E7%BE%A4%E6%95%88%E5%BA%94)" 从而导致效率变低的问题. 该问题常见于 Apache. 并且各自竞争的情况下无法控制一个新的连接由哪个进程来处理, 从而导致各 worker 进程之间的负载不均衡, 比如通常 70% 的连接仅被 8 个进程中的 2 个处理, 而其他进程比较清闲. ## 进程间通信 From 7a1a5421dafd3de7c0bb11f892810dd84b7c238e Mon Sep 17 00:00:00 2001 From: Lellansin Date: Wed, 8 Mar 2017 13:00:29 +0800 Subject: [PATCH 09/98] Add io.md & add socket-hang-up question --- README.md | 23 ++-- sections/js-basic.md | 4 +- sections/network.md | 59 +++++++++- sections/os.md | 272 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 343 insertions(+), 15 deletions(-) create mode 100644 sections/os.md diff --git a/README.md b/README.md index 3ca29a7..a32c49a 100644 --- a/README.md +++ b/README.md @@ -125,27 +125,26 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 * 什么是跨域请求? 如何允许跨域? [[more]](sections/network.md#q-cors) * TCP/UDP 的区别? TCP 粘包是怎么回事,如何处理? UDP 有粘包吗? [[more]](sections/network.md#q-tcp-udp) * `TIME_WAIT` 是什么情况? 出现过多的 `TIME_WAIT` 可能是什么原因? [[more]](sections/network.md#q-time-wait) -* socket hang up 是什么意思? 一般什么情况下出现? +* socket hang up 是什么意思? 可能在什么情况下出现? [[more]](sections/network.md#socket-hang-up) * 列举几个提高网络传输速度的办法? [阅读更多](sections/network.md) -## OS +## [OS](sections/io.md) -* `[Doc]` TTY -* `[Doc]` OS (操作系统) -* `[Doc]` 命令行参数 -* `[Basic]` 负载 -* `[Basic]` 指标 -* `[Point]` CheckList +* [`[Doc]` TTY](sections/io.md#tty) +* [`[Doc]` OS (操作系统)](sections/io.md#os) +* [`[Doc]` 命令行参数](sections/io.md#命令行参数) +* [`[Basic]` 负载](sections/io.md#负载) +* [`[Point]` CheckList](sections/io.md#CheckList) ### 常见问题 +* 什么是 TTY? 如何判断是否处于 TTY 环境? [[more]](sections/io.md#tty) +* 服务器负载是什么概念? 如何查看负载? [[more]](sections/io.md#负载) +* ulimit 是用来干什么的? [[more]](sections/io.md#ulimit) -* 服务器负载是什么概念? 如何计算负载? -* ulimit 是用来干什么的? - -`更多整理中` +[阅读更多](sections/io.md) ## 错误处理/调试/优化 diff --git a/sections/js-basic.md b/sections/js-basic.md index 62e9f95..b778d42 100644 --- a/sections/js-basic.md +++ b/sections/js-basic.md @@ -119,8 +119,8 @@ setInterval(replaceThing, 1000) `...` 的使用上, 如何实现一个数组的去重 (使用 Set 可以加分). -> const 定义的 Array 中间元素能否被修改? 如果可以, 那 const 的意义是? +> const 定义的 Array 中间元素能否被修改? 如果可以, 那 const 修饰对象有什么意义? -可以被修改, const 可以避免类型改变, 可以保护引用不变, 引用不变对使用 Map 有很大的意义. +其中的值可以被修改. 意义上, 主要保护引用不被修改 (如用 [Map](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map) 等接口对引用的变化很敏感, 使用 const 保护引用始终如一是有意义的), 也适合用在 immutable 的场景. 暂时写上这些, 之后会慢慢整理, 如果内容比较多可能单独归一类来讨论. diff --git a/sections/network.md b/sections/network.md index 3be53c7..e8c05c1 100644 --- a/sections/network.md +++ b/sections/network.md @@ -205,7 +205,7 @@ location ~* ^/(?:v1|_) { ``` -详见[Javascript Script Error.](https://sentry.io/answers/javascript-script-error/) +详见 [Javascript Script Error.](https://sentry.io/answers/javascript-script-error/) ### Agent @@ -214,6 +214,61 @@ Node.js 中的 `http.Agent` 用于池化 HTTP 客户端请求的 socket (pooling 另外最近发现一个 Agent 坑爹的地方, 当 `keepAlive` 为 true 是, 由于 socket 复用, 之前的事件监听如果忘了清除很容易导致重复监听, 并且旧的监听中的引用不会释放从导致内存泄漏, 参见这个 [issue](https://github.com/nodejs/node/issues/9268). (本组的同学有在整理这方面的文章, 请期待) +### socket hang up + +hang up 有挂断的意思, socket hang up 也可以理解为 socket 被挂断. 在 Node.js 中当你要 response 一个请求的时候, 发现该这个 socket 已经被 "挂断", 就会就会报 socket hang up 错误. + +[Node.js 中源码的情况:](https://github.com/nodejs/node/blob/v6.x/lib/_http_client.js#L286): + +```javascript +function socketCloseListener() { + var socket = this; + var req = socket._httpMessage; + + // Pull through final chunk, if anything is buffered. + // the ondata function will handle it properly, and this + // is a no-op if no final chunk remains. + socket.read(); + + // NOTE: It's important to get parser here, because it could be freed by + // the `socketOnData`. + var parser = socket.parser; + req.emit('close'); + if (req.res && req.res.readable) { + // Socket closed before we emitted 'end' below. + req.res.emit('aborted'); + var res = req.res; + res.on('end', function() { + res.emit('close'); + }); + res.push(null); + } else if (!req.res && !req.socket._hadError) { + // This socket error fired before we started to + // receive a response. The error needs to + // fire on the request. + req.emit('error', createHangUpError()); // <------------------- socket hang up + req.socket._hadError = true; + } + + // Too bad. That output wasn't getting written. + // This is pretty terrible that it doesn't raise an error. + // Fixed better in v0.10 + if (req.output) + req.output.length = 0; + if (req.outputEncodings) + req.outputEncodings.length = 0; + + if (parser) { + parser.finish(); + freeParser(parser, req, socket); + } +} +``` + +典型的情况是用户使用浏览器, 请求的时间有点长, 然后用户简单的按了一下 F5 刷新页面. 这个操作会让浏览器取消之前的请求, 然后导致服务端 throw 了一个 socket hang up. + +详见万能的 stackoverflow: [NodeJS - What does “socket hang up” actually mean?](http://stackoverflow.com/questions/16995184/nodejs-what-does-socket-hang-up-actually-mean) + ## DNS @@ -226,6 +281,8 @@ DNS 服务主要基于 UDP, 这里简单介绍 Node.js 实现的接口中的两 .lookup(hostname[, options], cb)|通过系统自带的 DNS 缓存 (如 `/etc/hosts`)|同步|无|快 .resolve(hostname[, rrtype], cb)|通过系统配置的 DNS 服务器指定的记录 (rrtype指定)|异步|有|慢 +> DNS 模块中 .lookup 与 .resolve 的区别? + 当你要解析一个域名的 ip 时, 通过 .lookup 查询直接调用 `getaddrinfo` 来拿取地址, 速度很快, 但是如果本地的 hosts 文件被修改了, .lookup 就会拿 hosts 文件中的地方, 而 .resolve 依旧是外部正常的地址. 由于 .lookup 是同步的, 所以如果由于什么不可控的原因导致 `getaddrinfo` 缓慢或者阻塞是会影响整个 Node 进程的, 参见[文档](https://nodejs.org/dist/latest-v6.x/docs/api/dns.html#dns_dns_lookup). diff --git a/sections/os.md b/sections/os.md new file mode 100644 index 0000000..033add0 --- /dev/null +++ b/sections/os.md @@ -0,0 +1,272 @@ +# OS + +* `[Doc]` TTY +* `[Doc]` OS (操作系统) +* `[Doc]` 命令行参数 +* `[Basic]` 负载 +* `[Point]` CheckList +* `[Basic]` 指标 + +## TTY + +"tty" 原意是指 "teletype" 即打字机, "pty" 则是 "pseudo-teletype" 即伪打字机. 在 Unix 中, `/dev/tty*` 是指任何表现的像打字机的设备, 例如终端 (terminal). + +你可以通过 `w` 命令查看当前登录的用户情况, 你会发现每登录了一个窗口就会有一个新的 tty. + +```shell +$ w + 11:49:43 up 482 days, 19:38, 3 users, load average: 0.03, 0.08, 0.07 +USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT +dev pts/0 10.0.128.252 10:44 1:01m 0.09s 0.07s -bash +dev pts/2 10.0.128.252 11:08 2:07 0.17s 0.14s top +root pts/3 10.0.240.2 11:43 7.00s 0.04s 0.00s w +``` + +使用 ps 命令查看进程信息中也有 tty 的信息: + +```shell +$ ps -x + PID TTY STAT TIME COMMAND + 5530 ? S 0:00 sshd: dev@pts/3 + 5531 pts/3 Ss+ 0:00 -bash +11296 ? S 0:00 sshd: dev@pts/4 +11297 pts/4 Ss 0:00 -bash +13318 pts/4 R+ 0:00 ps -x +23733 ? Ssl 2:53 PM2 v1.1.2: God Daemon +``` + +其中为 `?` 的是没有依赖 TTY 的进程, 即[守护进程](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#%E5%AE%88%E6%8A%A4%E8%BF%9B%E7%A8%8B). + +在 Node.js 中你可以通过 stdio 的 isTTY 来判断当前进程是否处于 TTY (如终端) 的环境. + +```shell +$ node -p -e "Boolean(process.stdout.isTTY)" +true +$ node -p -e "Boolean(process.stdout.isTTY)" | cat +false +``` + +## OS + +通过 OS 模块可以获取到当前系统一些基础信息的辅助函数. + +|属性|描述| +|---|---| +|os.EOL|根据当前系统, 返回当前系统的 `End Of Line`| +|os.arch()|返回当前系统的 CPU 架构, 如 `'x86'` 和 `'x64'`| +|os.constants|返回系统常量| +|os.cpus()|返回 CPU 每个核的信息| +|os.endianness()|返回 CPU 字节序, 如果是大端字节序返回 `BE`, 小端字节序则 `LE`| +|os.freemem()|返回系统空闲内存的大小, 单位是字节| +|os.homedir()|返回当前用户的根目录| +|os.hostname()|返回当前系统的主机名| +|os.loadavg()|返回负载信息| +|os.networkInterfaces()|返回网卡信息 (类似 `ifconfig`)| +|os.platform()|返回编译时指定的平台信息, 如 `win32`, `linux`, 同 `process.platform()`| +|os.release()|返回操作系统的分发版本号| +|os.tmpdir()|返回系统默认的临时文件夹| +|os.totalmem()|返回总内存大小(同内存条大小)| +|os.type()|根据 `[uname](https://en.wikipedia.org/wiki/Uname#Examples)` 返回系统的名称| +|os.uptime()|返回系统的运行时间,单位是秒| +|os.userInfo([options])|返回当前用户信息| + +### OS 常量 + +* 信号常量 (Signal Constants), 如 `SIGHUP`, `SIGKILL` 等. +* POSIX 错误常量 (POSIX Error Constants), 如 `EACCES`, `EADDRINUSE` 等. +* Windows 错误常量 (Windows Specific Error Constants), 如 `WSAEACCES`, `WSAEBADF` 等. +* libuv 常量 (libuv Constants), 仅 `UV_UDP_REUSEADDR`. + +## 命令行参数 + +命令行参数 (Command Line Options), 即对 CLI 使用上的一些文档. 关于 CLI 主要有 4 种使用方式: + +* node [options] [v8 options] [script.js | -e "script"] [arguments] +* node debug [script.js | -e "script" | :] … +* node --v8-options +* 无参数直接启动 REPL 环境 + +### Options + +|参数|简介| +|---|---| +|-v, --version|查看当前 node 版本| +|-h, --help|查看帮助文档| +|-e, --eval "script"|将参数字符串当做代码执行 +|-p, --print "script"|打印 `-e` 的返回值 +|-c, --check|检查语法并不执行 +|-i, --interactive|即使 stdin 不是终端也打开 REPL 模式 +|-r, --require module|在启动前预先 `require` 指定模块 +|--no-deprecation|关闭废弃模块警告 +|--trace-deprecation|打印废弃模块的堆栈跟踪信息 +|--throw-deprecation|执行废弃模块时抛出错误 +|--no-warnings|无视报警(包括废弃警告) +|--trace-warnings|打印警告的 stack (包括废弃模块) +|--trace-sync-io|只要检测到异步 I/O 出于 Event loop 的开头就打印 stack trace +|--zero-fill-buffers|自动初始化(zero-fill) **Buffer** 和 **SlowBuffer** +|--preserve-symlinks|在解析和缓存模块时指示模块加载程序保存符号链接 +|--track-heap-objects|为堆快照跟踪堆对象的分配情况 +|--prof-process|使用 v8 选项 `--prof` 生成 Profilling 报告 +|--v8-options|显示 v8 命令行选项 +|--tls-cipher-list=list|指明替代的默认 TLS 加密器列表 +|--enable-fips|在启动时开启 FIPS-compliant crypto +|--force-fips|在启动时强制实施 FIPS-compliant +|--openssl-config=file|启动时加载 OpenSSL 配置文件 +|--icu-data-dir=file|指定ICU数据加载路径 + +### 环境变量 + +|环境变量|简介| +|----|----| +|`NODE_DEBUG=module[,…]`|指定要打印调试信息的核心模块列表 +|`NODE_PATH=path[:…]`|指定搜索目录模块路径的前缀列表 +|`NODE_DISABLE_COLORS=1`|关闭 REPL 的颜色显示 +|`NODE_ICU_DATA=file`|ICU (Intl object) 数据路径 +|`NODE_REPL_HISTORY=file`|持久化存储REPL历史文件的路径 +|`NODE_TTY_UNSAFE_ASYNC=1`|设置为1时, 将同步操作 stdio (如 console.log 变成同步) +|`NODE_EXTRA_CA_CERTS=file`|指定 CA (如 VeriSign) 的额外证书路径 + +## 负载 + +负载是衡量服务器运行状态的一个重要概念. 通过负载情况, 我们可以知道服务器目前状态是清闲, 良好, 繁忙还是即将 crash. + +通常我们要查看的负载是 CPU 负载, 详细一点的情况你可以通过阅读这篇博客: [Understanding Linux CPU Load](http://blog.scoutapp.com/articles/2009/07/31/understanding-load-averages) 来了解. + +命令行上可以通过 `uptime`, `top` 命令, Node.js 中可以通过 `os.loadavg()` 来获取当前系统的负载情况: + +``` +load average: 0.09, 0.05, 0.01 +``` + +其中分别是最近 1 分钟, 5 分钟, 15 分钟内系统 CPU 的平均负载. 当 CPU 的一个核工作饱和的时候负载为 1, 有几核 CPU 那么饱和负载就是几. + +在 Node.js 中单个进程的 CPU 负载查看可以使用 [pidusage](https://github.com/soyuka/pidusage) 模块. + +除了 CPU 负载, 对于服务端 (偏维护) 还需要了解网络负载, 磁盘负载等. + +## CheckList + +> 有一个醉汉半夜在路灯下徘徊,路过的人奇怪地问他:“你在路灯下找什么?”醉汉回答:“我在找我的KEY”,路人更奇怪了:“找钥匙为什么在路灯下?”,醉汉说:“因为这里最亮!”。 + +很多服务端的同学在说到检查服务器状态时只知道使用 `top` 命令, 其实情况就和上面的笑话一样, 因为对于他们而言 `top` 是最亮的那盏路灯. + +对于服务端程序员而言, 完整的服务器 checklist 首推 [《性能之巅》](https://www.amazon.cn/%E5%9B%BE%E4%B9%A6/dp/B0140I5WPK) 第二章中讲述的 [USE 方法](http://www.brendangregg.com/USEmethod/use-linux.html). + +The USE Method provides a strategy for performing a complete check of system health, identifying common bottlenecks and errors. For each system resource, metrics for utilization, saturation and errors are identified and checked. Any issues discovered are then investigated using further strategies. + +This is an example USE-based metric list for Linux operating systems (eg, Ubuntu, CentOS, Fedora). This is primarily intended for system administrators of the physical systems, who are using command line tools. Some of these metrics can be found in remote monitoring tools. + +### Physical Resources + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
componenttypemetric
CPUutilizationsystem-wide: vmstat 1, "us" + "sy" + "st"; sar -u, sum fields except "%idle" and "%iowait"; dstat -c, sum fields except "idl" and "wai"; per-cpu: mpstat -P ALL 1, sum fields except "%idle" and "%iowait"; sar -P ALL, same as mpstat; per-process: top, "%CPU"; htop, "CPU%"; ps -o pcpu; pidstat 1, "%CPU"; per-kernel-thread: top/htop ("K" to toggle), where VIRT == 0 (heuristic). [1]
CPUsaturationsystem-wide: vmstat 1, "r" > CPU count [2]; sar -q, "runq-sz" > CPU count; dstat -p, "run" > CPU count; per-process: /proc/PID/schedstat 2nd field (sched_info.run_delay); perf sched latency (shows "Average" and "Maximum" delay per-schedule); dynamic tracing, eg, SystemTap schedtimes.stp "queued(us)" [3]
CPUerrorsperf (LPE) if processor specific error events (CPC) are available; eg, AMD64's "04Ah Single-bit ECC Errors Recorded by Scrubber" [4]
Memory capacityutilizationsystem-wide: free -m, "Mem:" (main memory), "Swap:" (virtual memory); vmstat 1, "free" (main memory), "swap" (virtual memory); sar -r, "%memused"; dstat -m, "free"; slabtop -s c for kmem slab usage; per-process: top/htop, "RES" (resident main memory), "VIRT" (virtual memory), "Mem" for system-wide summary
Memory capacitysaturationsystem-wide: vmstat 1, "si"/"so" (swapping); sar -B, "pgscank" + "pgscand" (scanning); sar -W; per-process: 10th field (min_flt) from /proc/PID/stat for minor-fault rate, or dynamic tracing [5]; OOM killer: dmesg | grep killed
Memory capacityerrorsdmesg for physical failures; dynamic tracing, eg, SystemTap uprobes for failed malloc()s
Network Interfacesutilizationsar -n DEV 1, "rxKB/s"/max "txKB/s"/max; ip -s link, RX/TX tput / max bandwidth; /proc/net/dev, "bytes" RX/TX tput/max; nicstat "%Util" [6]
Network Interfacessaturationifconfig, "overruns", "dropped"; netstat -s, "segments retransmited"; sar -n EDEV, *drop and *fifo metrics; /proc/net/dev, RX/TX "drop"; nicstat "Sat" [6]; dynamic tracing for other TCP/IP stack queueing [7]
Network Interfaceserrorsifconfig, "errors", "dropped"; netstat -i, "RX-ERR"/"TX-ERR"; ip -s link, "errors"; sar -n EDEV, "rxerr/s" "txerr/s"; /proc/net/dev, "errs", "drop"; extra counters may be under /sys/class/net/...; dynamic tracing of driver function returns 76]
Storage device I/Outilizationsystem-wide: iostat -xz 1, "%util"; sar -d, "%util"; per-process: iotop; pidstat -d; /proc/PID/sched "se.statistics.iowait_sum"
Storage device I/Osaturationiostat -xnz 1, "avgqu-sz" > 1, or high "await"; sar -d same; LPE block probes for queue length/latency; dynamic/static tracing of I/O subsystem (incl. LPE block probes)
Storage device I/Oerrors/sys/devices/.../ioerr_cnt; smartctl; dynamic/static tracing of I/O subsystem response codes [8]
Storage capacityutilizationswap: swapon -s; free; /proc/meminfo "SwapFree"/"SwapTotal"; file systems: "df -h"
Storage capacitysaturationnot sure this one makes sense - once it's full, ENOSPC
Storage capacityerrorsstrace for ENOSPC; dynamic tracing for ENOSPC; /var/log/messages errs, depending on FS
Storage controllerutilizationiostat -xz 1, sum devices and compare to known IOPS/tput limits per-card
Storage controllersaturationsee storage device saturation, ...
Storage controllererrorssee storage device errors, ...
Network controllerutilizationinfer from ip -s link (or /proc/net/dev) and known controller max tput for its interfaces
Network controllersaturationsee network interface saturation, ...
Network controllererrorssee network interface errors, ...
CPU interconnectutilizationLPE (CPC) for CPU interconnect ports, tput / max
CPU interconnectsaturationLPE (CPC) for stall cycles
CPU interconnecterrorsLPE (CPC) for whatever is available
Memory interconnectutilizationLPE (CPC) for memory busses, tput / max; or CPI greater than, say, 5; CPC may also have local vs remote counters
Memory interconnectsaturationLPE (CPC) for stall cycles
Memory interconnecterrorsLPE (CPC) for whatever is available
I/O interconnectutilizationLPE (CPC) for tput / max if available; inference via known tput from iostat/ip/...
I/O interconnectsaturationLPE (CPC) for stall cycles
I/O interconnecterrorsLPE (CPC) for whatever is available
+ + +### Software Resources + + + + + + + + + + + + + + + + + + + + +
componenttypemetric
Kernel mutexutilizationWith CONFIG_LOCK_STATS=y, /proc/lock_stat "holdtime-totat" / "acquisitions" (also see "holdtime-min", "holdtime-max") [8]; dynamic tracing of lock functions or instructions (maybe)
Kernel mutexsaturationWith CONFIG_LOCK_STATS=y, /proc/lock_stat "waittime-total" / "contentions" (also see "waittime-min", "waittime-max"); dynamic tracing of lock functions or instructions (maybe); spinning shows up with profiling (perf record -a -g -F 997 ..., oprofile, dynamic tracing)
Kernel mutexerrorsdynamic tracing (eg, recusive mutex enter); other errors can cause kernel lockup/panic, debug with kdump/crash
User mutexutilizationvalgrind --tool=drd --exclusive-threshold=... (held time); dynamic tracing of lock to unlock function time
User mutexsaturationvalgrind --tool=drd to infer contention from held time; dynamic tracing of synchronization functions for wait time; profiling (oprofile, PEL, ...) user stacks for spins
User mutexerrorsvalgrind --tool=drd various errors; dynamic tracing of pthread_mutex_lock() for EAGAIN, EINVAL, EPERM, EDEADLK, ENOMEM, EOWNERDEAD, ...
Task capacityutilizationtop/htop, "Tasks" (current); sysctl kernel.threads-max, /proc/sys/kernel/threads-max (max)
Task capacitysaturationthreads blocking on memory allocation; at this point the page scanner should be running (sar -B "pgscan*"), else examine using dynamic tracing
Task capacityerrors"can't fork()" errors; user-level threads: pthread_create() failures with EAGAIN, EINVAL, ...; kernel: dynamic tracing of kernel_thread() ENOMEM
File descriptorsutilizationsystem-wide: sar -v, "file-nr" vs /proc/sys/fs/file-max; dstat --fs, "files"; or just /proc/sys/fs/file-nr; per-process: ls /proc/PID/fd | wc -l vs ulimit -n
File descriptorssaturationdoes this make sense? I don't think there is any queueing or blocking, other than on memory allocation.
File descriptorserrorsstrace errno == EMFILE on syscalls returning fds (eg, open(), accept(), ...).
+ +#### ulimit + +ulimit 用于管理用户对系统资源的访问. + +``` +-a 显示目前全部限制情况 +-c 设定 core 文件的最大值, 单位为区块 +-d <数据节区大小> 程序数据节区的最大值, 单位为KB +-f <文件大小> shell 所能建立的最大文件, 单位为区块 +-H 设定资源的硬性限制, 也就是管理员所设下的限制 +-m <内存大小> 指定可使用内存的上限, 单位为 KB +-n <文件描述符数目> 指定同一时间最多可开启的 fd 数 +-p <缓冲区大小> 指定管道缓冲区的大小, 单位512字节 +-s <堆叠大小> 指定堆叠的上限, 单位为 KB +-S 设定资源的弹性限制 +-t 指定CPU使用时间的上限, 单位为秒 +-u <进程数目> 用户最多可开启的进程数目 +-v <虚拟内存大小> 指定可使用的虚拟内存上限, 单位为 KB +``` + +例如: + +``` +$ ulimit -a +core file size (blocks, -c) 0 +data seg size (kbytes, -d) unlimited +scheduling priority (-e) 0 +file size (blocks, -f) unlimited +pending signals (-i) 127988 +max locked memory (kbytes, -l) 64 +max memory size (kbytes, -m) unlimited +open files (-n) 655360 +pipe size (512 bytes, -p) 8 +POSIX message queues (bytes, -q) 819200 +real-time priority (-r) 0 +stack size (kbytes, -s) 8192 +cpu time (seconds, -t) unlimited +max user processes (-u) 4096 +virtual memory (kbytes, -v) unlimited +file locks (-x) unlimited +``` + +注意, open socket 等资源拿到的也是 fd, 所以 `ulimit -n` 比较小除了文件打不开, 还可能建立不了 socket 链接. + From abf7d92f0d113cbd507e8f80d685f3dfb72f3781 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Wed, 8 Mar 2017 13:02:20 +0800 Subject: [PATCH 10/98] Fix index typos --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index a32c49a..f0baae2 100644 --- a/README.md +++ b/README.md @@ -130,21 +130,21 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 [阅读更多](sections/network.md) -## [OS](sections/io.md) +## [OS](sections/os.md) -* [`[Doc]` TTY](sections/io.md#tty) -* [`[Doc]` OS (操作系统)](sections/io.md#os) -* [`[Doc]` 命令行参数](sections/io.md#命令行参数) -* [`[Basic]` 负载](sections/io.md#负载) -* [`[Point]` CheckList](sections/io.md#CheckList) +* [`[Doc]` TTY](sections/os.md#tty) +* [`[Doc]` OS (操作系统)](sections/os.md#os) +* [`[Doc]` 命令行参数](sections/os.md#命令行参数) +* [`[Basic]` 负载](sections/os.md#负载) +* [`[Point]` CheckList](sections/os.md#CheckList) ### 常见问题 -* 什么是 TTY? 如何判断是否处于 TTY 环境? [[more]](sections/io.md#tty) -* 服务器负载是什么概念? 如何查看负载? [[more]](sections/io.md#负载) -* ulimit 是用来干什么的? [[more]](sections/io.md#ulimit) +* 什么是 TTY? 如何判断是否处于 TTY 环境? [[more]](sections/os.md#tty) +* 服务器负载是什么概念? 如何查看负载? [[more]](sections/os.md#负载) +* ulimit 是用来干什么的? [[more]](sections/os.md#ulimit) -[阅读更多](sections/io.md) +[阅读更多](sections/os.md) ## 错误处理/调试/优化 From 608724461cef08f237b21bd60775c80c9ac42f2c Mon Sep 17 00:00:00 2001 From: Lellansin Date: Wed, 8 Mar 2017 14:39:22 +0800 Subject: [PATCH 11/98] Add question about EOL of OS --- README.md | 4 +++- sections/os.md | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f0baae2..2c0adac 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,7 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 * TCP/UDP 的区别? TCP 粘包是怎么回事,如何处理? UDP 有粘包吗? [[more]](sections/network.md#q-tcp-udp) * `TIME_WAIT` 是什么情况? 出现过多的 `TIME_WAIT` 可能是什么原因? [[more]](sections/network.md#q-time-wait) * socket hang up 是什么意思? 可能在什么情况下出现? [[more]](sections/network.md#socket-hang-up) +* hosts 文件是什么? 什么叫 DNS 本地解析? * 列举几个提高网络传输速度的办法? [阅读更多](sections/network.md) @@ -136,11 +137,12 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 * [`[Doc]` OS (操作系统)](sections/os.md#os) * [`[Doc]` 命令行参数](sections/os.md#命令行参数) * [`[Basic]` 负载](sections/os.md#负载) -* [`[Point]` CheckList](sections/os.md#CheckList) +* [`[Point]` CheckList](sections/os.md#checklist) ### 常见问题 * 什么是 TTY? 如何判断是否处于 TTY 环境? [[more]](sections/os.md#tty) +* 不同操作系统的换行符 (EOL) 有什么区别? [[more]](sections/os.md#os) * 服务器负载是什么概念? 如何查看负载? [[more]](sections/os.md#负载) * ulimit 是用来干什么的? [[more]](sections/os.md#ulimit) diff --git a/sections/os.md b/sections/os.md index 033add0..b39494b 100644 --- a/sections/os.md +++ b/sections/os.md @@ -70,6 +70,20 @@ false |os.uptime()|返回系统的运行时间,单位是秒| |os.userInfo([options])|返回当前用户信息| +> 不同操作系统的换行符 (EOL) 有什么区别? + +end of line (EOL) 同 newline, line ending, 以及 line break. + +通常由 line feed (LF, `\n`) 和 carriage return (CR, `\r`) 组成. 常见的情况: + +|符号|系统| +|---|---| +|LF|在 Unix 或 Unix 相容系统 (GNU/Linux, AIX, Xenix, Mac OS X, ...)、BeOS、Amiga、RISC OS| +|CR+LF|MS-DOS、微软视窗操作系统 (Microsoft Windows)、大部分非 Unix 的系统| +|CR|Apple II 家族, Mac OS 至版本9| + +如果不了解 EOL 跨系统的兼容情况, 那么在处理文件的行分割/行统计等情况时可能会被坑. + ### OS 常量 * 信号常量 (Signal Constants), 如 `SIGHUP`, `SIGKILL` 等. From 36f0fe795e003ff157ee147f6a08d7839f8c9bfc Mon Sep 17 00:00:00 2001 From: Lellansin Date: Fri, 10 Mar 2017 11:00:36 +0800 Subject: [PATCH 12/98] Update readme & add promise question --- README.md | 5 +++-- sections/event-async.md | 20 +++++++++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2c0adac..7093f9e 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 * js 中什么类型是引用传递, 什么类型是值传递? 如何将值类型的变量以引用的方式传递? [[more]](sections/js-basic.md#q-value) * js 中, 0.1 + 0.2 === 0.3 是否为 true ? 在不知道浮点数位数时应该怎样判断两个浮点数之和与第三数是否相等? -* const 定义的 Array 中间元素能否被修改? 如果可以, 那 const 的意义是? [[more]](sections/js-basic.md#q-const) +* const 定义的 Array 中间元素能否被修改? 如果可以, 那 const 修饰对象的意义是? [[more]](sections/js-basic.md#q-const) * Javascript 中不同类型以及不同环境下变量的内存都是何时释放? [[more]](sections/js-basic.md#q-mem) [阅读更多](sections/js-basic.md) @@ -104,7 +104,7 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 * Stream 的 pipe 的作用是? 在 pipe 的过程中数据是引用传递还是拷贝传递? [[more]](sections/io.md#pipe) * 什么是文件描述符? 输入流/输出流/错误流是什么? [[more]](sections/io.md#file) * console.log 是同步还是异步? 如何实现一个 console.log? [[more]](sections/io.md#console) -* 如何* `[Doc]` HTTP同步的获取用户的输入? [[more]](sections/io.md#如何同步的获取用户的输入) +* 如何同步的获取用户的输入? [[more]](sections/io.md#如何同步的获取用户的输入) * Readline 是如何实现的? (有思路即可) [[more]](sections/io.md#readline) [阅读更多](sections/io.md) @@ -125,6 +125,7 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 * 什么是跨域请求? 如何允许跨域? [[more]](sections/network.md#q-cors) * TCP/UDP 的区别? TCP 粘包是怎么回事,如何处理? UDP 有粘包吗? [[more]](sections/network.md#q-tcp-udp) * `TIME_WAIT` 是什么情况? 出现过多的 `TIME_WAIT` 可能是什么原因? [[more]](sections/network.md#q-time-wait) +* ECONNRESET 是什么错误? 如何复现这个错误? * socket hang up 是什么意思? 可能在什么情况下出现? [[more]](sections/network.md#socket-hang-up) * hosts 文件是什么? 什么叫 DNS 本地解析? * 列举几个提高网络传输速度的办法? diff --git a/sections/event-async.md b/sections/event-async.md index 30f3c4f..7f68f76 100644 --- a/sections/event-async.md +++ b/sections/event-async.md @@ -59,7 +59,25 @@ setTimeout(() => { }, 10000); ``` -如果你不了解这两个问题, 可以自己在本地尝试研究一下打印的结果. 这里希望你掌握的是 Promise 的状态转换, 以及异步与 Promise 的关系, Promise 如何帮助你处理异步, 如果你研究过 Promise 的实现那就更好了. +以及理解如下代码的执行顺序 ([出处](https://zhuanlan.zhihu.com/p/25407758)): + +```javascript +setTimeout(function() { + console.log(1) +}, 0); +new Promise(function executor(resolve) { + console.log(2); + for( var i=0 ; i<10000 ; i++ ) { + i == 9999 && resolve(); + } + console.log(3); +}).then(function() { + console.log(4); +}); +console.log(5); +``` + +如果你不了解这些问题, 可以自己在本地尝试研究一下打印的结果. 这里希望你掌握的是 Promise 的状态转换, 包括异步与 Promise 的关系, 以及 Promise 如何帮助你处理异步, 如果你研究过 Promise 的实现那就更好了. ## Events From a7ce76addaac7f48472121d9176028587dec6fbc Mon Sep 17 00:00:00 2001 From: Lellansin Date: Tue, 14 Mar 2017 17:09:02 +0800 Subject: [PATCH 13/98] Add error.md & add TODO --- README.md | 25 +-- assets/node-js-survey-debug.png | Bin 0 -> 43825 bytes sections/error.md | 279 ++++++++++++++++++++++++++++++++ sections/io.md | 8 + sections/network.md | 13 +- 5 files changed, 309 insertions(+), 16 deletions(-) create mode 100644 assets/node-js-survey-debug.png create mode 100644 sections/error.md diff --git a/README.md b/README.md index 7093f9e..83c5e2c 100644 --- a/README.md +++ b/README.md @@ -149,23 +149,26 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 [阅读更多](sections/os.md) -## 错误处理/调试/优化 +## [错误处理/调试/优化](sections/error.md) -* `[Doc]` Errors (异常) -* `[Doc]` Domain (域) -* `[Doc]` Debugger (调试器) -* `[Doc]` C/C++ 插件 -* `[Doc]` V8 -* `[Point]` 内存快照 -* `[Point]` CPU剖析 +* [`[Doc]` Errors (异常)](sections/error.md#errors) +* [`[Doc]` Domain (域)](sections/error.md#domain) +* [`[Doc]` Debugger (调试器)](sections/error.md#debugger) +* [`[Doc]` C/C++ 插件](sections/error.md#c-c++-addon) +* [`[Doc]` V8](sections/error.md#v8) +* [`[Point]` 内存快照](sections/error.md#内存快照) +* [`[Point]` CPU剖析](sections/error.md#cpu-剖析) ### 常见问题 -* 怎么处理未预料的出错?用 try/catch ,domains 还是其它什么? -* domain 的原理是? 为什么要弃用 domain? +* 怎么处理未预料的出错? 用 try/catch ,domains 还是其它什么? [[more]](sections/error.md#q-handle-error) +* 什么是 `uncaughtException` 事件? 一般在什么情况下使用该事件? [[more]](sections/error.md#uncaughtException) +* domain 的原理是? 为什么要弃用 domain? [[more]](sections/error.md#domain) * 为什么要在 cb 的第一参数传 error? 为什么有的 cb 第一个参数不是 error, 例如 http.createServer? +* 为什么有些异常没法根据报错信息定位到代码调用? 如何准确的定位一个异常? [[more]](sections/error.md#错误栈丢失) +* 内存泄漏通常由哪些原因导致? 如何分析以及定位内存泄漏? [[more]](sections/error.md#内存快照) -`更多整理中` +[阅读更多](sections/error.md) ## 测试 diff --git a/assets/node-js-survey-debug.png b/assets/node-js-survey-debug.png new file mode 100644 index 0000000000000000000000000000000000000000..cc16367b42ad809bd2a9abba7ed649c6f5cd190d GIT binary patch literal 43825 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 i`xw=H=Ku29GEUefxX>%WBZL3FGtxJ^Q= 实际上, `try/catch` 唯一常用的是在 `JSON.parse` 和类似验证用户输入的地方 + +然而实际上现在在 Node.js 中你已经可以轻松的使用 try/catch 去捕获异步的异常了. 并且在 Node.js v7.6 之后使用了升级引擎的新版 v8, 旧版中 try/catch 代码不能优化的问题也解决了. 所以我们现在再来看 + +> 怎么处理未预料的出错? 用 try/catch , domains 还是其它什么? + +在 Node.js 中错误处理主要有一下几种方法: + +* callback(err, data) 回调约定 +* throw / try / catch +* EventEmitter 的 error 事件 + +callback(err, data) 这种形式的错误处理起来繁琐, 并不具备强制性, 目前已经处于仅需要了解, 不推荐使用的情况. 而 domain 模块则是半只脚踏进棺材了. + +1) 感谢 [co](https://github.com/visionmedia/co) 的先河, 现在的你已经简单的使用 try/catch 保护关键的位置, 以 koa 为例, 可以通过中间件的形式来进行错误处理, 详见 [Koa error hangding](https://github.com/koajs/koa/wiki/Error-Handling). 之后的 async/await 均属于这种模式. + +2) 通过 EventEmitter 的错误监听形式为各大关键的对象加上错误监听的回调. 例如监听 http server, tcp server 等对象的 `error` 事件以及 process 对象提供的 `uncaughtException` 和 `unhandledRejection` 等等. + +3) 使用 Promise 来封装异步, 并通过 Promise 的错误处理来 handle 错误. + +4) 如果上述办法不能起到良好的作用, 那么你需要学习如何优雅的 [Let It Crash](http://wiki.c2.com/?LetItCrash) + +> 为什么要在 cb 的第一参数传 error? 为什么有的 cb 第一个参数不是 error, 例如 http.createServer? + +TODO + + +### 错误栈丢失 + +```javascript +function test() { + throw new Error('test error'); +} + +function main() { + test(); +} + +main(); +``` + +可以收获报错: + +```javascript +/data/node-interview/error.js:2 + throw new Error('test error'); + ^ + +Error: test error + at test (/data/node-interview/error.js:2:9) + at main (/data/node-interview/error.js:6:3) + at Object. (/data/node-interview/error.js:9:1) + at Module._compile (module.js:570:32) + at Object.Module._extensions..js (module.js:579:10) + at Module.load (module.js:487:32) + at tryModuleLoad (module.js:446:12) + at Function.Module._load (module.js:438:3) + at Module.runMain (module.js:604:10) + at run (bootstrap_node.js:394:7) +``` + +可以发现报错的行数, test 函数, main 函数的调用关系都在 stack 中清晰的体现. + +当你使用 setImmediate 等定时器来设置异步的时候: + +```javascript +function test() { + throw new Error('test error'); +} + +function main() { + setImmediate(() => test()); +} + +main(); + +``` + +我们发现 + +```javascript +/data/node-interview/error.js:2 + throw new Error('test error'); + ^ + +Error: test error + at test (/data/node-interview/error.js:2:9) + at Immediate.setImmediate (/data/node-interview/error.js:6:22) + at runCallback (timers.js:637:20) + at tryOnImmediate (timers.js:610:5) + at processImmediate [as _immediateCallback] (timers.js:582:5) +``` + +错误栈中仅输出到 test 函数内调用的地方位置, 再往上 main 的调用信息就丢失了. 也就是说如果你的函数调用深度比较深的情况下, 你使用异步调用某个函数出错了的情况下追溯这个异步的调用是一个很困难的事情, 因为其之上的栈都已经丢失了. 如果你用过 [async](https://github.com/caolan/async) 之类的模块, 你还可能发现, 报错的 stack 会非常的长而且曲折, 光看 stack 很难去定位问题. + +这项目不大/作者清楚的情况下不是问题, 但是当项目大起来, 开发人员多起来之后, 这样追溯错误会变得异常痛苦. 关于这个问题, 在上文中提到 [错误处理的最佳实践](https://cnodejs.org/topic/55714dfac4e7fbea6e9a2e5d) 中, 关于 `编写新函数的具体建议` 那一带的内容有描述到. 通过使用 [verror](https://www.npmjs.com/package/verror) 这样的方式, 让 Error 一层层封装, 并在每一层将错误的信息一层层的包上, 最后拿到的 Error 直接可以从 message 中获取用于定位问题的关键信息. + +以昨天的数据为准(2017-3-13)各位只要对比一下看看 npm 上上个月 [verror](https://www.npmjs.com/package/verror) 的下载量 `1100w` 比 [express](https://www.npmjs.com/package/express) 的 `1070w` 还高. 应该就能感受到这种写法有多流行了. + +### 防御性编程 + +错误并不可怕, 可怕的是你不去准备应对错误————[防御性编程的介绍和技巧](http://blog.jobbole.com/101651/) + +### let it crash + +[Let It Crash](http://wiki.c2.com/?LetItCrash) + +### uncaughtException + +当异常没有被捕获一路冒泡到 Event Loop 时就会触发该事件 process 对象上的 `uncaughtException` 事件. 默认情况下, Node.js 对于此类异常会直接将其堆栈跟踪信息输出给 `stderr` 并结束进程, 而为 `uncaughtException` 事件添加监听可以覆盖该默认行为, 不会直接结束进程. + +```javascript +process.on('uncaughtException', (err) => { + console.log(`Caught exception: ${err}`); +}); + +setTimeout(() => { + console.log('This will still run.'); +}, 500); + +// Intentionally cause an exception, but don't catch it. +nonexistentFunc(); +console.log('This will not run.'); +``` + +#### 合理使用 uncaughtException + +`uncaughtException` 的初衷是可以让你拿到错误之后可以做一些回收处理之后再 process.exit. 官方的同志们还曾经讨论过要移除该事件 (详见 [issues](https://github.com/nodejs/node-v0.x-archive/issues/2582)) + +所以你需要明白 `uncaughtException` 其实已经是非常规手段了, 应尽量避免使用它来处理错误. 因为通过该事件捕获到错误后, 并不代表 `你可以愉快的继续运行 (On Error Resume Next)`. 程序内部存在未处理的异常, 这意味着应用程序处于一种未知的状态. 如果不能适当的恢复其状态, 那么很有可能会触发不可预见的问题. (使用 domain 会很夸张的加剧这个现象, 并产生新人不能理解的各类幽灵问题) + +如果在 `.on` 指定的监听回调中报错不会被捕获, Node.js 的进程会直接终端并返回一个非零的退出码, 最后输出相应的堆栈信息. 否则, 会出现无限递归. 除此之外, 内存崩溃/底层报错等情况也不会被捕获, **目前猜测**是 v8/C++ 那边撂担子不干了, Node.js 完全插不上话导致的 (TODO 整理到这里才想起来这个念头尚未验证, 如果有空的朋友帮忙验证下). + +所以官方建议的使用 `uncaughtException` 的正确姿势是在结束进程前使用同步的方式清理已使用的资源 (文件描述符、句柄等) 然后 process.exit. + +在 uncaughtException 事件之后执行普通的恢复操作并不安全. 官方建议是另外在专门准备一个 monitor 进程来做健康检查并通过 monitor 来管理恢复情况, 并在必要的时候重启 (所以官方是含蓄的提醒各位用 pm2 之类的工具). + + +### unhandledRejection + +当 Promise 被 reject 且没有绑定监听处理时, 就会触发该事件. 该事件对排查和追踪没有处理 reject 行为的 Promise 很有用. + +该事件的回调函数接收以下参数: + +* `reason` `[](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)` | `` 该 Promise 被 reject 的对象 (通常为 Error 对象) +* `p` 被 reject 的 Promise 本身 + +例如 + +```javascript +process.on('unhandledRejection', (reason, p) => { + console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); + // application specific logging, throwing an error, or other logic here +}); + +somePromise.then((res) => { + return reportToUser(JSON.pasre(res)); // note the typo (`pasre`) +}); // no `.catch` or `.then` +``` + +一下代码也会触发 `unhandledRejection` 事件: + +```javascript +function SomeResource() { + // Initially set the loaded status to a rejected promise + this.loaded = Promise.reject(new Error('Resource not yet loaded!')); +} + +var resource = new SomeResource(); +// no .catch or .then on resource.loaded for at least a turn +``` + +> In this example case, it is possible to track the rejection as a developer error as would typically be the case for other 'unhandledRejection' events. To address such failures, a non-operational `.catch(() => { })` handler may be attached to resource.loaded, which would prevent the 'unhandledRejection' event from being emitted. Alternatively, the 'rejectionHandled' event may be used. + + +## Domain + +Node.js 早期, try/catch 无法捕获异步的错误, 而错误优先的 callback 仅仅是一种约定并没有强制性并且写起来十分繁琐. 所以为了能够很好的捕获异常, Node.js 从 v0.8 开始引入 domain 这个模块. + +domain 本身是一个 EventEmitter 对象, 其中文意思是 "域" 的意思, 捕获异步异常的基本思路是创建一个域, cb 函数会在定义时会继承上一层的域, 报错通过当前域的 `.emit('error', err)` 方法触发错误事件将错误传递上去, 从而使得异步错误可以被强制捕获. (更多内容详见 [Node.js 异步异常的处理与domain模块解析](https://cnodejs.org/topic/516b64596d38277306407936)) + +但是 domain 的引入也带来了更多新的问题. 比如依赖的模块无法继承你定义的 domain, 导致你写的 domain 无法 cover 依赖模块报错. 而且, 很多人 (特别是新人) 由于不了解 Node.js 的内存/异步流程等问题, 在使用 domain 处理报错的时候, 没有做到完善的处理并盲目的让代码继续走下去, 这很可能导致**项目完全无法维护** (可能出现的问题真是不胜枚举, 各种梦魇...) + +该模块目前的情况: [deprecate domains](https://github.com/nodejs/node/issues/66) + + +## Debugger + +![node-js-survey-debug](../assets/node-js-survey-debug.png) + +类似 gdb 的命令行下 debug 工具 (上图中的 build-in debugger), 同时也支持远程 debug (类似 [node-inspector](https://github.com/node-inspector/node-inspector), 目前处于试验状态). 当然, 目前有不少同学觉得 [vscode](https://code.visualstudio.com/) 对 debug 工具集成的比较好. + +关于这个 build-in debugger 使用推荐看[官方文档](https://nodejs.org/dist/latest-v6.x/docs/api/debugger.html). 如果要深入一点, 你可能对本文感兴趣: [动态修改 NodeJS 程序中的变量值](http://code.oneapm.com/nodejs/2015/06/27/intereference/) + + +## C/C++ Addon + +在 Node.js 中开发 addon 最痛苦的地方莫过于升级 V8 导致的 C/C++ 代码不能兼容的问题, 这个问题在很早就出现了. 为了解决这个问题前人开了一个叫 [nan](https://github.com/nodejs/nan) 的项目. + +要学习 addon 开发, 除了[官方文档](https://nodejs.org/docs/latest/api/addons.html)也推荐阅读这个: https://github.com/nodejs/node-addon-examples + + +## V8 + +这里并不是介绍 V8, 而是介绍 Node.js 中的 V8 这个模块. 该模块用于开放 Node.js 内建的 V8 引擎的事件和接口. 这些接口由 V8 底层决定, 所以无法保证绝对的稳定性. + +|接口|描述| +|---|---| +|v8.getHeapStatistics()|获取 heap 信息| +|v8.getHeapSpaceStatistics()|获取 heap space 信息| +|v8.setFlagsFromString(string)|动态设置 V8 options| + +### v8.setFlagsFromString(string) + +该方法用于添加额外的 V8 命令行标志. 该方法需谨慎使用, 在 VM 启动后修改配置可能会发生不可预测的行为、崩溃和数据丢失; 或者什么反应都没有. + +通过 `node --v8-options` 命令可以查询当前 Node.js 环境中有哪些可用的 V8 options. 此外, 还可以参考非官方维护的一个 [V8 options 列表](https://github.com/thlorenz/v8-flags/blob/master/flags-0.11.md). + +用法: + +```javascript +// Print GC events to stdout for one minute. +const v8 = require('v8'); +v8.setFlagsFromString('--trace_gc'); +setTimeout(function() { v8.setFlagsFromString('--notrace_gc'); }, 60e3); +``` + +## 内存快照 + +内存快照常用与解决内存泄漏的问题. 快照工具推荐使用 [heapdump](https://github.com/bnoordhuis/node-heapdump) 用来保存内存快照, 使用 [devtool](https://github.com/Jam3/devtool) 来查看内存快照. 使用 heapdump 保存内存快照时, 只会有 Node.js 环境中的对象, 不会受到干扰(如果使用 [node-inspector](https://github.com/node-inspector/node-inspector) 的话, 快照中会有前端的变量干扰). + +使用以及内存泄漏的常见原因详见: [如何分析 Node.js 中的内存泄漏](https://zhuanlan.zhihu.com/p/25736931?group_id=825001468703674368). + +## CPU 剖析 + +整理中 + + diff --git a/sections/io.md b/sections/io.md index 49946cf..0c021b7 100644 --- a/sections/io.md +++ b/sections/io.md @@ -242,6 +242,14 @@ function Console(stdout, stderr) { Node.js 封装了标准 POSIX 文件 I/O 操作的集合. 通过 require('fs') 可以加载该模块. 该模块中的所有方法都有异步执行和同步执行两个版本. 你可以通过 fs.open 获得一个文件的文件描述符. +### 编码 + +// TODO + +UTF8, GBK, es6 中对编码的支持, 如何计算一个汉字的长度 + +BOM + ### stdio stdio (standard input output) 标准的输入输出流, 即输入流 (stdin), 输出流 (stdout), 错误流 (stderr) 三者. 在 Node.js 中分别对应 `process.stdin` (Readable), `process.stdout` (Writable) 以及 `process.stderr` (Writable) 三个 stream. diff --git a/sections/network.md b/sections/network.md index e8c05c1..e2a749b 100644 --- a/sections/network.md +++ b/sections/network.md @@ -95,7 +95,7 @@ TIME-WAIT|主动方收到 FIN, 返回收到对方 FIN 的 ACK, 等待对方是 `TIME_WAIT` 是连接的某一方 (可能是服务端也可能是客户端) 主动断开连接时, 四次挥手等待被断开的一方是否收到最后一次挥手 (ACK) 的状态. 如果在等待时间中, 再次收到第三次挥手 (FIN) 表示对方没收到最后一次挥手, 这时要再 ACK 一次. 这个等待的作用是避免出现连接混用的情况 (`prevent potential overlap with new connections` see [TCP Connection Termination](http://www.tcpipguide.com/free/t_TCPConnectionTermination.htm) for more). -出现大量的 `TIME_WAIT` 比较常见的情况是, 并发量大, 服务器在短时间断开了大量连接. 对应 HTTP server 的情况可能是没开启 `keepAlive`. 如果有开 `keepAlive`, 一般是等待客户端自己主动断开, 那么`TIME_WAIT` 就只存在客户端, 而服务端则是 `CLOSE_WAIT` 的状态, 如果服务端出现大量 `CLOSE_WAIT`, 意味着当前服务端建立的链接大面积的被断开, 可能是目标服务集群重启之类. +出现大量的 `TIME_WAIT` 比较常见的情况是, 并发量大, 服务器在短时间断开了大量连接. 对应 HTTP server 的情况可能是没开启 `keepAlive`. 如果有开 `keepAlive`, 一般是等待客户端自己主动断开, 那么`TIME_WAIT` 就只存在客户端, 而服务端则是 `CLOSE_WAIT` 的状态, 如果服务端出现大量 `CLOSE_WAIT`, 意味着当前服务端建立的连接大面积的被断开, 可能是目标服务集群重启之类. ## UDP @@ -218,7 +218,7 @@ Node.js 中的 `http.Agent` 用于池化 HTTP 客户端请求的 socket (pooling hang up 有挂断的意思, socket hang up 也可以理解为 socket 被挂断. 在 Node.js 中当你要 response 一个请求的时候, 发现该这个 socket 已经被 "挂断", 就会就会报 socket hang up 错误. -[Node.js 中源码的情况:](https://github.com/nodejs/node/blob/v6.x/lib/_http_client.js#L286): +[Node.js 中源码的情况:](https://github.com/nodejs/node/blob/v6.x/lib/_http_client.js#L286) ```javascript function socketCloseListener() { @@ -287,18 +287,21 @@ DNS 服务主要基于 UDP, 这里简单介绍 Node.js 实现的接口中的两 由于 .lookup 是同步的, 所以如果由于什么不可控的原因导致 `getaddrinfo` 缓慢或者阻塞是会影响整个 Node 进程的, 参见[文档](https://nodejs.org/dist/latest-v6.x/docs/api/dns.html#dns_dns_lookup). +> hosts 文件是什么? 什么叫 DNS 本地解析? + +TODO ## ZLIB 在网络传输过程中, 如果网速稳定的情况下, 对数据进行压缩, 压缩比率越大, 那么传输的效率就越高等同于速度越快了. zlib 模块提供了 Gzip/Gunzip, Deflate/Inflate 和 DeflateRaw/InflateRaw 等压缩方法的类, 这些类接收相同的参数, 都属于可读写的 Stream 实例. -整理中 +TODO ## RPC RPC (Remote Procedure Call Protocol) 基于 TCP/IP 来实现调用远程服务器的方法, 与 http 同属应用层. 常用于构建集群, 以及微服务 (推荐一本[《Node.js 微服务》](https://www.amazon.cn/%E5%9B%BE%E4%B9%A6/dp/B01MXY8ARP)虽然我还没看完) -常见的 RPC 几大代表: +常见的 RPC 方式: * [Thrift](http://thrift.apache.org/) * HTTP @@ -318,4 +321,4 @@ RPC (Remote Procedure Call Protocol) 基于 TCP/IP 来实现调用远程服务 使用消息队列 (Message Queue) 来进行 RPC 调用 (RPC over mq) 在业内有不少例子, 比较适合业务解耦/广播/限流等场景. -整理中 +TODO From c5e6fb5499fb168f2f815f2df59791a4fa70b184 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Wed, 15 Mar 2017 11:26:12 +0800 Subject: [PATCH 14/98] Update CPU profilling & fix typos --- README.md | 9 +++-- sections/error.md | 98 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 99 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 83c5e2c..a88c5bd 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 * js 中什么类型是引用传递, 什么类型是值传递? 如何将值类型的变量以引用的方式传递? [[more]](sections/js-basic.md#q-value) * js 中, 0.1 + 0.2 === 0.3 是否为 true ? 在不知道浮点数位数时应该怎样判断两个浮点数之和与第三数是否相等? * const 定义的 Array 中间元素能否被修改? 如果可以, 那 const 修饰对象的意义是? [[more]](sections/js-basic.md#q-const) -* Javascript 中不同类型以及不同环境下变量的内存都是何时释放? [[more]](sections/js-basic.md#q-mem) +* JavaScript 中不同类型以及不同环境下变量的内存都是何时释放? [[more]](sections/js-basic.md#q-mem) [阅读更多](sections/js-basic.md) @@ -157,13 +157,14 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 * [`[Doc]` C/C++ 插件](sections/error.md#c-c++-addon) * [`[Doc]` V8](sections/error.md#v8) * [`[Point]` 内存快照](sections/error.md#内存快照) -* [`[Point]` CPU剖析](sections/error.md#cpu-剖析) +* [`[Point]` CPU profiling](sections/error.md#cpu-profiling) ### 常见问题 * 怎么处理未预料的出错? 用 try/catch ,domains 还是其它什么? [[more]](sections/error.md#q-handle-error) -* 什么是 `uncaughtException` 事件? 一般在什么情况下使用该事件? [[more]](sections/error.md#uncaughtException) +* 什么是 `uncaughtException` 事件? 一般在什么情况下使用该事件? [[more]](sections/error.md#uncaughtexception) * domain 的原理是? 为什么要弃用 domain? [[more]](sections/error.md#domain) +* 什么是防御性编程? 与其相反的 let it crash 又是什么? * 为什么要在 cb 的第一参数传 error? 为什么有的 cb 第一个参数不是 error, 例如 http.createServer? * 为什么有些异常没法根据报错信息定位到代码调用? 如何准确的定位一个异常? [[more]](sections/error.md#错误栈丢失) * 内存泄漏通常由哪些原因导致? 如何分析以及定位内存泄漏? [[more]](sections/error.md#内存快照) @@ -238,4 +239,4 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 目前 repo 处于施工现场的情况,如果发现问题欢迎在 [issues](https://github.com/ElemeFE/node-interview/issues) 中指出。如果有比较好的问题/知识点/指正,也欢迎提 PR。 -另外关于 Js 基础 是个比较大的话题,在本教程不会很细致深入的讨论,更多的是列出一些重要或者更服务端更相关的地方,所以如果你拿着《Javascript 权威指南》给教程提 PR 可能不会采纳。本教程的重点更准确的说是服务端基础中 Node.js 程序员需要了解的部分。 +另外关于 Js 基础 是个比较大的话题,在本教程不会很细致深入的讨论,更多的是列出一些重要或者更服务端更相关的地方,所以如果你拿着《JavaScript 权威指南》给教程提 PR 可能不会采纳。本教程的重点更准确的说是服务端基础中 Node.js 程序员需要了解的部分。 diff --git a/sections/error.md b/sections/error.md index 9d2438d..bdee1c5 100644 --- a/sections/error.md +++ b/sections/error.md @@ -183,7 +183,7 @@ console.log('This will not run.'); 该事件的回调函数接收以下参数: -* `reason` `[](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)` | `` 该 Promise 被 reject 的对象 (通常为 Error 对象) +* `reason` [``](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) | `` 该 Promise 被 reject 的对象 (通常为 Error 对象) * `p` 被 reject 的 Promise 本身 例如 @@ -199,7 +199,7 @@ somePromise.then((res) => { }); // no `.catch` or `.then` ``` -一下代码也会触发 `unhandledRejection` 事件: +以下代码也会触发 `unhandledRejection` 事件: ```javascript function SomeResource() { @@ -272,8 +272,98 @@ setTimeout(function() { v8.setFlagsFromString('--notrace_gc'); }, 60e3); 使用以及内存泄漏的常见原因详见: [如何分析 Node.js 中的内存泄漏](https://zhuanlan.zhihu.com/p/25736931?group_id=825001468703674368). -## CPU 剖析 +## CPU profiling -整理中 +CPU profiling (剖析) 常用于性能优化. 有许多用于做 profiling 的第三方工具, 但是大部分情况下, 使用 Node.js 内置的是最简单的. 其内置调用的就是 [V8 本身的 profiler](https://github.com/v8/v8/wiki/Using%20V8%E2%80%99s%20internal%20profiler), 它可以在程序执行过程中中是对 stack 间隔性的抽样分析. + +使用 `--prof` 开启内置的 profilling + +```shell +node --prof app.js +``` + +程序运行之后会生成一个 `isolate-0xnnnnnnnnnnnn-v8.log` 在当前运行目录. + +你可以使用 `--prof-process` 来生成报告查看 + +``` +node --prof-process isolate-0xnnnnnnnnnnnn-v8.log +``` + +报告形如: + +``` +Statistical profiling result from isolate-0x103001200-v8.log, (12042 ticks, 2634 unaccounted, 0 excluded). + + [Shared libraries]: + ticks total nonlib name + 35 0.3% /usr/lib/system/libsystem_platform.dylib + 27 0.2% /usr/lib/system/libsystem_pthread.dylib + 7 0.1% /usr/lib/system/libsystem_c.dylib + 3 0.0% /usr/lib/system/libsystem_kernel.dylib + 1 0.0% /usr/lib/system/libsystem_malloc.dylib + + [JavaScript]: + ticks total nonlib name + 208 1.7% 1.7% Stub: LoadICStub + 187 1.6% 1.6% KeyedLoadIC: A keyed load IC from the snapshot + 104 0.9% 0.9% Stub: VectorStoreICStub + 69 0.6% 0.6% LazyCompile: *emit events.js:136:44 + 68 0.6% 0.6% Builtin: CallFunction_ReceiverIsNotNullOrUndefined + 65 0.5% 0.5% KeyedStoreIC: A keyed store IC from the snapshot {2} + 47 0.4% 0.4% Builtin: CallFunction_ReceiverIsAny + 43 0.4% 0.4% LazyCompile: *storeHeader _http_outgoing.js:312:21 + 34 0.3% 0.3% LazyCompile: *removeListener events.js:315:28 + 33 0.3% 0.3% Stub: RegExpExecStub + 33 0.3% 0.3% LazyCompile: *_addListener events.js:210:22 + 32 0.3% 0.3% Stub: CEntryStub + 32 0.3% 0.3% Builtin: ArgumentsAdaptorTrampoline + 31 0.3% 0.3% Stub: FastNewClosureStub + 30 0.2% 0.3% Stub: InstanceOfStub + ... + + [C++]: + ticks total nonlib name + 460 3.8% 3.8% _mach_port_extract_member + 329 2.7% 2.7% _openat$NOCANCEL + 199 1.7% 1.7% ___bsdthread_register + 136 1.1% 1.1% ___mkdir_extended + 116 1.0% 1.0% node::HandleWrap::Close(v8::FunctionCallbackInfo const&) + 112 0.9% 0.9% void v8::internal::BodyDescriptorBase::IterateBodyImpl(v8::internal::Heap*, v8::internal::HeapObject*, int, int) + 106 0.9% 0.9% _http_parser_execute + 103 0.9% 0.9% _szone_malloc_should_clear + 99 0.8% 0.8% int v8::internal::BinarySearch<(v8::internal::SearchMode)1, v8::internal::DescriptorArray>(v8::internal::DescriptorArray*, v8::internal::Name*, int, int*) + 89 0.7% 0.7% node::TCPWrap::Connect(v8::FunctionCallbackInfo const&) + 86 0.7% 0.7% v8::internal::LookupIterator::State v8::internal::LookupIterator::LookupInRegularHolder(v8::internal::Map*, v8::internal::JSReceiver*) + ... + + [Bottom up (heavy) profile]: + Note: percentage shows a share of a particular caller in the total + amount of its parent calls. + Callers occupying less than 2.0% are not shown. + + ticks parent name + 2634 21.9% UNKNOWN + 764 29.0% LazyCompile: *connect net.js:815:17 + 764 100.0% LazyCompile: ~ net.js:966:30 + 764 100.0% LazyCompile: *_tickCallback internal/process/next_tick.js:87:25 + 193 7.3% LazyCompile: *createWriteReq net.js:732:24 + 101 52.3% LazyCompile: *Socket._writeGeneric net.js:660:42 + 99 98.0% LazyCompile: ~ net.js:667:34 + 99 100.0% LazyCompile: ~g events.js:287:13 + 99 100.0% LazyCompile: *emit events.js:136:44 + 92 47.7% LazyCompile: ~Socket._writeGeneric net.js:660:42 + 91 98.9% LazyCompile: ~ net.js:667:34 + 91 100.0% LazyCompile: ~g events.js:287:13 + 91 100.0% LazyCompile: *emit events.js:136:44 + ... +``` + +|字段|描述| +|---|---| +|ticks|时间片| +|total|当前操作执行的时间占总时间的比率| +|nonlib|当前非 System library 执行时间比率| +整理中 From 8a3ad7c9ca988f1e8aa376cc7e649cc814b28436 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Wed, 15 Mar 2017 11:35:23 +0800 Subject: [PATCH 15/98] fix docsify image url --- README.md | 2 +- sections/error.md | 2 +- sections/event-async.md | 2 +- sections/network.md | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a88c5bd..6ec43c3 100644 --- a/README.md +++ b/README.md @@ -164,7 +164,7 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 * 怎么处理未预料的出错? 用 try/catch ,domains 还是其它什么? [[more]](sections/error.md#q-handle-error) * 什么是 `uncaughtException` 事件? 一般在什么情况下使用该事件? [[more]](sections/error.md#uncaughtexception) * domain 的原理是? 为什么要弃用 domain? [[more]](sections/error.md#domain) -* 什么是防御性编程? 与其相反的 let it crash 又是什么? +* 什么是防御性编程? 与其相对的 let it crash 又是什么? * 为什么要在 cb 的第一参数传 error? 为什么有的 cb 第一个参数不是 error, 例如 http.createServer? * 为什么有些异常没法根据报错信息定位到代码调用? 如何准确的定位一个异常? [[more]](sections/error.md#错误栈丢失) * 内存泄漏通常由哪些原因导致? 如何分析以及定位内存泄漏? [[more]](sections/error.md#内存快照) diff --git a/sections/error.md b/sections/error.md index bdee1c5..3916360 100644 --- a/sections/error.md +++ b/sections/error.md @@ -227,7 +227,7 @@ domain 本身是一个 EventEmitter 对象, 其中文意思是 "域" 的意思, ## Debugger -![node-js-survey-debug](../assets/node-js-survey-debug.png) +![node-js-survey-debug](/assets/node-js-survey-debug.png) 类似 gdb 的命令行下 debug 工具 (上图中的 build-in debugger), 同时也支持远程 debug (类似 [node-inspector](https://github.com/node-inspector/node-inspector), 目前处于试验状态). 当然, 目前有不少同学觉得 [vscode](https://code.visualstudio.com/) 对 debug 工具集成的比较好. diff --git a/sections/event-async.md b/sections/event-async.md index 7f68f76..dd882ef 100644 --- a/sections/event-async.md +++ b/sections/event-async.md @@ -12,7 +12,7 @@ ## Promise -![callback-hell](../assets/callback-hell.jpg) +![callback-hell](/assets/callback-hell.jpg) 相信很多同学在面试的时候都碰到过这样一个问题, `如何处理 Callback Hell`. 在早些年的时候, 大家会看到有很多的解决方案例如 [Q](https://www.npmjs.com/package/q), [async](https://www.npmjs.com/package/async), [EventProxy](https://www.npmjs.com/package/eventproxy) 等等. 最后从流行程度来看 `Promise` 当之无愧的独领风骚, 并且是在 ES6 的 Javascript 标准上赢得了支持. diff --git a/sections/network.md b/sections/network.md index e2a749b..72338bd 100644 --- a/sections/network.md +++ b/sections/network.md @@ -61,7 +61,7 @@ TCP 头里有一个 Window 字段, 是接收端告诉发送端自己还有多少 ### backlog -![图片出处 http://www.cnxct.com/something-about-phpfpm-s-backlog/](../assets/socket-backlog.png) +![图片出处 http://www.cnxct.com/something-about-phpfpm-s-backlog/](/assets/socket-backlog.png) 关于该 backlog 的定义参见 [man](https://linux.die.net/man/2/listen) 手册: @@ -73,7 +73,7 @@ backlog 用于设置客户端与服务端 `ESTABLISHED` 之后等待 accept 的 ### 状态机 -![tcpfsm.png](../assets/tcpfsm.png) +![tcpfsm.png](/assets/tcpfsm.png) 关于网络连接的建立以及断开, 存在着一个复杂的状态转换机制, 完整的状态表参见 [《The TCP/IP Guide》](http://www.tcpipguide.com/free/t_TCPOperationalOverviewandtheTCPFiniteStateMachineF-2.htm) From 5ada28d61df5646946dd586f3a45774a655a1376 Mon Sep 17 00:00:00 2001 From: cheogo Date: Wed, 15 Mar 2017 12:04:28 +0800 Subject: [PATCH 16/98] Update network.md finished what's hosts and location DNS Analysis TODO --- sections/network.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sections/network.md b/sections/network.md index e2a749b..4178331 100644 --- a/sections/network.md +++ b/sections/network.md @@ -289,7 +289,9 @@ DNS 服务主要基于 UDP, 这里简单介绍 Node.js 实现的接口中的两 > hosts 文件是什么? 什么叫 DNS 本地解析? -TODO +hosts 文件是个没有扩展名的系统文件,其作用就是将网址域名与其对应的 IP 地址建立一个关联“数据库”,当用户在浏览器中输入一个需要登录的网址时,系统会首先自动从 hosts 文件中寻找对应的IP地址。 + +当我们访问一个域名时,实际上需要的是访问对应的 IP 地址。这时候,获取 IP 地址的方式,先是读取浏览器缓存,如果未命中 => 接着读取本地 hosts 文件,如果还是未命中 => 则向 DNS 服务器发送请求获取。在向 DNS 服务器获取 IP 地址之前的行为,叫做 DNS 本地解析。 ## ZLIB From 97f73e0944ec33df429051ba189d27a11eb490af Mon Sep 17 00:00:00 2001 From: wenlei <847301609@qq.com> Date: Sat, 18 Mar 2017 15:08:08 +0800 Subject: [PATCH 17/98] =?UTF-8?q?=E6=8B=BC=E5=86=99=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sections/error.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sections/error.md b/sections/error.md index 3916360..d6a077d 100644 --- a/sections/error.md +++ b/sections/error.md @@ -53,7 +53,7 @@ console.log(os.constants.errno); callback(err, data) 这种形式的错误处理起来繁琐, 并不具备强制性, 目前已经处于仅需要了解, 不推荐使用的情况. 而 domain 模块则是半只脚踏进棺材了. -1) 感谢 [co](https://github.com/visionmedia/co) 的先河, 现在的你已经简单的使用 try/catch 保护关键的位置, 以 koa 为例, 可以通过中间件的形式来进行错误处理, 详见 [Koa error hangding](https://github.com/koajs/koa/wiki/Error-Handling). 之后的 async/await 均属于这种模式. +1) 感谢 [co](https://github.com/visionmedia/co) 的先河, 现在的你已经简单的使用 try/catch 保护关键的位置, 以 koa 为例, 可以通过中间件的形式来进行错误处理, 详见 [Koa error handling](https://github.com/koajs/koa/wiki/Error-Handling). 之后的 async/await 均属于这种模式. 2) 通过 EventEmitter 的错误监听形式为各大关键的对象加上错误监听的回调. 例如监听 http server, tcp server 等对象的 `error` 事件以及 process 对象提供的 `uncaughtException` 和 `unhandledRejection` 等等. From 07da59c73cf5c2ad16177a3c5f668c2d68b3fe58 Mon Sep 17 00:00:00 2001 From: "qingwei.li" Date: Mon, 20 Mar 2017 13:52:04 +0800 Subject: [PATCH 18/98] add en-us folder --- _navbar.md | 3 +++ en-us/README.md | 0 en-us/_navbar.md | 11 +++++++++++ en-us/sections/error.md | 0 en-us/sections/event-async.md | 0 en-us/sections/io.md | 0 en-us/sections/js-basic.md | 0 en-us/sections/module.md | 0 en-us/sections/network.md | 0 en-us/sections/os.md | 0 en-us/sections/process.md | 0 index.html | 22 +++++++++++++++++++++- package.json | 2 +- 13 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 en-us/README.md create mode 100644 en-us/_navbar.md create mode 100644 en-us/sections/error.md create mode 100644 en-us/sections/event-async.md create mode 100644 en-us/sections/io.md create mode 100644 en-us/sections/js-basic.md create mode 100644 en-us/sections/module.md create mode 100644 en-us/sections/network.md create mode 100644 en-us/sections/os.md create mode 100644 en-us/sections/process.md diff --git a/_navbar.md b/_navbar.md index d2cc4fc..3f1f12d 100644 --- a/_navbar.md +++ b/_navbar.md @@ -6,3 +6,6 @@ - [模块](sections/module) - [Network](sections/network) - [进程](sections/process) +- Translations + - [中文](/) + - [English](/en-us/) \ No newline at end of file diff --git a/en-us/README.md b/en-us/README.md new file mode 100644 index 0000000..e69de29 diff --git a/en-us/_navbar.md b/en-us/_navbar.md new file mode 100644 index 0000000..89618db --- /dev/null +++ b/en-us/_navbar.md @@ -0,0 +1,11 @@ +- [Home](/en-us/) +- Sections + - [Event/Async](/en-us/sections/event-async) + - [IO](/en-us/sections/io) + - [Javascript Basic](/en-us/sections/js-basic) + - [Module](/en-us/sections/module) + - [Network](/en-us/sections/network) + - [Process](/en-us/sections/process) +- Translations + - [中文](/) + - [English](/en-us/) diff --git a/en-us/sections/error.md b/en-us/sections/error.md new file mode 100644 index 0000000..e69de29 diff --git a/en-us/sections/event-async.md b/en-us/sections/event-async.md new file mode 100644 index 0000000..e69de29 diff --git a/en-us/sections/io.md b/en-us/sections/io.md new file mode 100644 index 0000000..e69de29 diff --git a/en-us/sections/js-basic.md b/en-us/sections/js-basic.md new file mode 100644 index 0000000..e69de29 diff --git a/en-us/sections/module.md b/en-us/sections/module.md new file mode 100644 index 0000000..e69de29 diff --git a/en-us/sections/network.md b/en-us/sections/network.md new file mode 100644 index 0000000..e69de29 diff --git a/en-us/sections/os.md b/en-us/sections/os.md new file mode 100644 index 0000000..e69de29 diff --git a/en-us/sections/process.md b/en-us/sections/process.md new file mode 100644 index 0000000..e69de29 diff --git a/index.html b/index.html index 9302a6d..00dc555 100644 --- a/index.html +++ b/index.html @@ -10,6 +10,27 @@ .markdown-section a:not(:hover) { text-decoration: none; } + .sidebar { + padding-top: 20px; + } + .sidebar h1 { + font-weight: normal; + } + .sidebar blockquote { + margin-left: 12px; + } + nav.app-nav { + position: fixed; + background-color: rgba(255, 255, 255, .8); + width: 100%; + margin-top: 0; + padding: 13px 0; + border-bottom: 1px solid rgba(0, 0, 0, .07); + margin-top: 0; + } + section.content { + padding-top: 50px; + } @@ -17,7 +38,6 @@ +``` + +还可以使用图片 url 等方式来上传脚本进行攻击 + +```html +
+ +``` + +还可以使用各种方式来回避检查, 例如空格, 回车, Tab + +```html + +``` + +还可以通过各种编码转换 (URL 编码, Unicode 编码, HTML 编码, ESCAPE 等) 来绕过检查 + +``` + + +``` + +### CPS 策略 + +在百般无奈, 没有统一解决方案的情况下, 厂商们推出了 CPS 策略. + +以 Node.js 为例, 计算脚本的 hashes 值: +``` +const crypto = require('crypto'); + +function getHashByCode(code, algorithm = 'sha256') { + return algorithm + '-' + crypto.createHash(algorithm).update(code, 'utf8').digest("base64"); +} + +getHashByCode('console.log("hello world");'); // 'sha256-wxWy1+9LmiuOeDwtQyZNmWpT0jqCUikqaqVlJdtdh/0=' +``` + +设置 CSP 头: + +``` +content-security-policy: script-src 'sha256-wxWy1+9LmiuOeDwtQyZNmWpT0jqCUikqaqVlJdtdh/0=' +``` + +```html + + +``` + +策略指令可以参见 [CSP Policy Directives](https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives)以及[阮一峰的博文](http://www.ruanyifeng.com/blog/2016/09/csp.html), [屈大神的博文](https://imququ.com/post/content-security-policy-reference.html) + + +## CSRF + +跨站请求伪造 (Cross-Site Request Forgery, CSRF, https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet) 是一种伪造跨站请求的攻击方式. 例如利用你在 A 站 (攻击目标) 的 cookie / 权限等, 在 B 站 (恶意/钓鱼网站) 拼装 A 站的请求. + +比如 Q 君是某论坛管理员. 已知这个论坛 A 删除的接口是 post 到某个地址, 并指定一个帖子的 id. 那么我可以在自己的博客 B 上组织一个 CSRF 请求. 然后诱使 Q 君来访问我的博客. 就可以在 Q 君不知情的情况下删除掉我想删的某个帖子. + +钓鱼方式包括但不限于公开网站 (xss), 攻击者的恶意网站, email 邮件, 微博, 微信, 短信等及时消息. + +同源策略是最早用于防止 CSRF 的一种方式, 即关于跨站请求 (Cross-Site Request) 只有在同源/信任的情况下才可以请求. 但是如果一个网站群, 在互相信任的情况下, 某个网站出现了问题: + +``` +a.public.com +b.public.com +c.public.com +... +``` + +以上情况下, 如果 c.public.com 上没有预防 xss 等情况, 使得攻击者可以基于此站对其他信任的网站发起 CSRF 攻击. + +另外同源策略主要是浏览器来进行验证的, 并且不同浏览器的实现又各自不同, 所以在某些浏览器上可以直接绕过, 而且也可以直接通过短信等方式直接绕过浏览器. + +预防: + +1. A 站 (预防站) 检查 http 请求的 header 确认其 origin +2. 检查 CSRF token + +### 1.同源检查 + +通过检查来过滤简单的 CSRF 攻击, 主要检查一下两个 header: + +* Origin Header +* Referer Header + +### 2.CSRF token + +简单来说, 对需要预防的请求, 通过特别的算法生成 token 存在 session 中, 然后将 token 隐藏在正确的界面表单中, 正式请求时带上该 token 在服务端验证, 避免跨站请求. + + +## 中间人攻击 + +中间人 (Man-in-the-middle attack, MITM) 是指攻击者与通讯的两端分别创建独立的联系, 并交换其所收到的数据, 使通讯的两端认为他们正在通过一个私密的连接与对方直接对话, 但事实上整个会话都被攻击者完全控制. 在中间人攻击中, 攻击者可以拦截通讯双方的通话并插入新的内容. + +目前比较常见的是在公共场所放置精心准备的免费 wifi, 劫持/监控通过该 wifi 的流量. 或者攻击路由器, 连上你家 wifi 攻破你家 wifi 之后在上面劫持流量等. + +对于通信过程中的 MITM, 常见的方案是通过 PKI / TLS 预防, 及时是通过存在第三方中间人的 wifi 你通过 HTTPS 访问的页面依旧是安全的. 而 HTTP 协议是明文传输, 则没有任何防护可言. + +不常见的还有强力的互相认证, 你确认他之后, 他也确认你一下; 延迟测试, 统计传输时间, 如果通讯延迟过高则认为可能存在第三方中间人; 等等. + +## SQL/NoSQL 注入 + +注入攻击是指当所执行的一些操作中有部分由用户传入时, 用户可以将其恶意逻辑注入到操作中. 当你使用 eval, new Function 等方式执行的字符串中有用户输入的部分时, 就可能被注入攻击. 上文中的 XSS 就属于一种注入攻击. 前面的章节中也提到过 Node.js 的 child_process.exec 由于调用 bash 解析, 如果执行的命令中有部分属于用户输入, 也可能被注入攻击. + +### SQL + +Sql 注入是网站常见的一种注入攻击方式. 其原因主要是由于登录时需要验证用户名/密码, 其执行 sql 类似: + +```sql +SELECT * FROM users WHERE usernae = 'myName' AND password = 'mySecret'; +``` + +其中的用户名和密码属于用户输入的部分, 那么在未做检查的情况下, 用户可能拼接恶意的字符串来达到其某种目的, 例如上传密码为 `'; DROP TABLE users; --` 使得最终执行的内容为: + +```sql +SELECT * FROM users WHERE usernae = 'myName' AND password = ''; DROP TABLE users; --'; +``` + +其能实现的功能, 包括但不限于删除数据 (经济损失), 篡改数据 (密码等), 窃取数据 (网站管理权限, 用户数据) 等. 防治手段常见于: + +* 给表名/字段名加前缀 (避免被猜到) +* 报错隐藏表信息 (避免被看到, 12306 早起就出现过的问题) +* 过滤可以拼接 SQL 的关键字符 +* 对用户输入进行转义 +* 验证用户输入的类型 (避免 limit, order by 等注入) +* 等... + +### NoSQL + +看个简单的情况: + +```javascript +let {user, pass, age} = ctx.query; + +db.collection.find({ + user, pass, + $where: `this.age >= ${age}` +}) +``` + +那么这里的 age 就可以注入了. 另外 GET/POST 还可以传递深层结构 (比如 ?name[0]=alan 传递上来), 通过 qs 之类的模块解析后导致注入, 如 [cnodejs 遭遇 mongodb 注入](https://github.com/cnodejs/nodeclub/commit/0f6cc14f6bcbbe6b4de3199c6896efaec637693e). + From 4fd402ad5889fa5a35aa01a8cb0f2568ee01098b Mon Sep 17 00:00:00 2001 From: heziqiang Date: Sat, 20 May 2017 13:21:35 +0800 Subject: [PATCH 34/98] issue: fix spelling error 'signle' => 'single' (#29) --- sections/storage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sections/storage.md b/sections/storage.md index e8b3b54..5e6a6e0 100644 --- a/sections/storage.md +++ b/sections/storage.md @@ -153,7 +153,7 @@ Cursor |Persistence | × | ✓ | |transcations | × | ✓ | |consistency | strong (by cas) | weak | -|thread | multi | signle | +|thread | multi | single | |memory | physical | physical & swap | From 4ae314d1c44ccdfd0ca1c914d53b731aa585214b Mon Sep 17 00:00:00 2001 From: Lellansin Date: Sun, 21 May 2017 01:55:57 +0800 Subject: [PATCH 35/98] doc: fix bad hash link --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d44a6c7..de6b9d8 100644 --- a/README.md +++ b/README.md @@ -227,17 +227,17 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 ## [安全](sections/security.md) * [`[Doc]` Crypto (加密)](sections/security.md#crypto) -* [`[Doc]` TLS/SSL](sections/security.md#tls-ssl) +* [`[Doc]` TLS/SSL](sections/security.md#tlsssl) * [`[Doc]` HTTPS](sections/security.md#https) * [`[Point]` XSS](sections/security.md#xss) * [`[Point]` CSRF](sections/security.md#csrf) * [`[Point]` 中间人攻击](sections/security.md#中间人攻击) -* [`[Point]` Sql/Nosql 注入](sections/security.md#SQL-NoSQL-注入) +* [`[Point]` Sql/Nosql 注入](sections/security.md#sqlnosql-注入) ### 常见问题 * 加密是如何保证用户密码的安全性? [[more]](sections/security.md#crypto) -* TLS 与 SSL 有什么区别? [[more]](sections/security.md#tls-ssl) +* TLS 与 SSL 有什么区别? [[more]](sections/security.md#tlsssl) * HTTPS 能否被劫持? [[more]](sections/security.md#https) * XSS 攻击是什么? 有什么危害? [[more]](sections/security.md#xss) * 过滤 Html 标签能否防止 XSS? 请列举不能的情况? [[more]](sections/security.md#xss) From 7896363fe2dd1023339c6e3e6381483a3a3271d8 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Sun, 21 May 2017 02:57:16 +0800 Subject: [PATCH 36/98] [release] 0.1.0 --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index f77257a..5ab0a72 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "name": "node-interview", + "version": "0.1.0", "repository": "git@github.com:ElemeFE/node-interview.git", "scripts": { "serve": "docsify serve ." From 230e23fb8100510146be86ee353805a56493b9f9 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Sun, 21 May 2017 02:59:57 +0800 Subject: [PATCH 37/98] refactor: fit for Internationalization --- README.md | 253 +----------- _navbar.md | 13 +- _sidebar.md | 7 + en-us/README.md | 0 en-us/_navbar.md | 11 - index.html | 3 +- sections/en-us/README.md | 109 +++++ sections/en-us/_sidebar.md | 16 + sections/{ => en-us}/error.md | 0 sections/{ => en-us}/event-async.md | 0 sections/{ => en-us}/io.md | 0 sections/{ => en-us}/js-basic.md | 0 sections/{ => en-us}/module.md | 0 sections/{ => en-us}/network.md | 0 sections/{ => en-us}/os.md | 0 sections/{ => en-us}/process.md | 0 {en-us => sections/en-us}/sections/error.md | 0 .../en-us}/sections/event-async.md | 0 {en-us => sections/en-us}/sections/io.md | 0 .../en-us}/sections/js-basic.md | 0 {en-us => sections/en-us}/sections/module.md | 0 {en-us => sections/en-us}/sections/network.md | 0 {en-us => sections/en-us}/sections/os.md | 0 {en-us => sections/en-us}/sections/process.md | 0 sections/{ => en-us}/security.md | 0 sections/{ => en-us}/storage.md | 0 sections/{ => en-us}/test.md | 0 sections/{ => en-us}/util.md | 0 sections/zh-cn/README.md | 253 ++++++++++++ sections/zh-cn/_sidebar.md | 16 + sections/zh-cn/error.md | 369 +++++++++++++++++ sections/zh-cn/event-async.md | 227 ++++++++++ sections/zh-cn/io.md | 386 ++++++++++++++++++ sections/zh-cn/js-basic.md | 126 ++++++ sections/zh-cn/module.md | 134 ++++++ sections/zh-cn/network.md | 334 +++++++++++++++ sections/zh-cn/os.md | 374 +++++++++++++++++ sections/zh-cn/process.md | 248 +++++++++++ sections/zh-cn/security.md | 208 ++++++++++ sections/zh-cn/storage.md | 168 ++++++++ sections/zh-cn/test.md | 256 ++++++++++++ sections/zh-cn/util.md | 230 +++++++++++ 42 files changed, 3474 insertions(+), 267 deletions(-) create mode 100644 _sidebar.md delete mode 100644 en-us/README.md delete mode 100644 en-us/_navbar.md create mode 100644 sections/en-us/README.md create mode 100644 sections/en-us/_sidebar.md rename sections/{ => en-us}/error.md (100%) rename sections/{ => en-us}/event-async.md (100%) rename sections/{ => en-us}/io.md (100%) rename sections/{ => en-us}/js-basic.md (100%) rename sections/{ => en-us}/module.md (100%) rename sections/{ => en-us}/network.md (100%) rename sections/{ => en-us}/os.md (100%) rename sections/{ => en-us}/process.md (100%) rename {en-us => sections/en-us}/sections/error.md (100%) rename {en-us => sections/en-us}/sections/event-async.md (100%) rename {en-us => sections/en-us}/sections/io.md (100%) rename {en-us => sections/en-us}/sections/js-basic.md (100%) rename {en-us => sections/en-us}/sections/module.md (100%) rename {en-us => sections/en-us}/sections/network.md (100%) rename {en-us => sections/en-us}/sections/os.md (100%) rename {en-us => sections/en-us}/sections/process.md (100%) rename sections/{ => en-us}/security.md (100%) rename sections/{ => en-us}/storage.md (100%) rename sections/{ => en-us}/test.md (100%) rename sections/{ => en-us}/util.md (100%) create mode 100644 sections/zh-cn/README.md create mode 100644 sections/zh-cn/_sidebar.md create mode 100644 sections/zh-cn/error.md create mode 100644 sections/zh-cn/event-async.md create mode 100644 sections/zh-cn/io.md create mode 100644 sections/zh-cn/js-basic.md create mode 100644 sections/zh-cn/module.md create mode 100644 sections/zh-cn/network.md create mode 100644 sections/zh-cn/os.md create mode 100644 sections/zh-cn/process.md create mode 100644 sections/zh-cn/security.md create mode 100644 sections/zh-cn/storage.md create mode 100644 sections/zh-cn/test.md create mode 100644 sections/zh-cn/util.md diff --git a/README.md b/README.md index de6b9d8..54460fc 100644 --- a/README.md +++ b/README.md @@ -1,253 +1,16 @@ ![ElemeFE-background](assets/ElemeFE-background.png) -# 如何通过饿了么 Node.js 面试 +# Node interview of ElemeFE -Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过饿了么大前端的面试, 职位是 2~3 年经验的 Node.js 服务端程序员 (并不是全栈), 如果你对这个职位感兴趣或者学习 Node.js 一些进阶的内容, 那么欢迎围观. +## What's this? -需要注意的是, 本文针对的并不是零基础的同学, 你需要有一定的 JavaScript/Node.js 基础, 并且有一定的工作经验. 另外本教程的重点更准确的说是服务端基础中 Node.js 程序员需要了解的部分. +This is a interview Index about Node.js which you should know while you are intersting about the Job of ElemeFE. -如果你觉得大多不了解, 就不用投简历了 (这样两边都节约了时间), 如果你觉得大都有了解或者**光看大纲都都觉得很简单那么欢迎投递简历至 ElemeFe (fe.job@ele.me)**. +## What is ElemeFE? -## 导读 +It means Frontend team of Eleme (饿了么) which seems like the bigest takeaway company. -虽然说目的是要通过面试, 但是本教程并不是简单的把所有面试题列出来, 而**主要是将面试中需要确认你是否懂的点列举出来**, 并进行一定程度的讨论. +#### Support languages -本文将一些常见的问题划分归类, 每类标明涵盖的一些`覆盖点`, 并且列举几个`常见问题`, 通常这些问题都是 2~3 年工作经验需要了解或者面对的. 如果你对某类问题感兴趣, 或者想知道其中列举问题的答案, 可以通过该类下方的 `阅读更多` 查看更多的内容. - -整体上大纲列举的并不是很全面, 细节上覆盖率不高, 很多讨论只是点到即止, 希望大家带着问题去思考. - -## [Js 基础问题](sections/js-basic.md) - -> 与前端 Js 不同, 后端是直面服务器的, 更加偏向内存方面. - -* [`[Basic]` 类型判断](sections/js-basic.md#类型判断) -* [`[Basic]` 作用域](sections/js-basic.md#作用域) -* [`[Basic]` 引用传递](sections/js-basic.md#引用传递) -* [`[Basic]` 内存释放](sections/js-basic.md#内存释放) -* [`[Basic]` ES6 新特性](sections/js-basic.md#es6-新特性) - -### 常见问题 - -* js 中什么类型是引用传递, 什么类型是值传递? 如何将值类型的变量以引用的方式传递? [[more]](sections/js-basic.md#q-value) -* js 中, 0.1 + 0.2 === 0.3 是否为 true ? 在不知道浮点数位数时应该怎样判断两个浮点数之和与第三数是否相等? -* const 定义的 Array 中间元素能否被修改? 如果可以, 那 const 修饰对象的意义是? [[more]](sections/js-basic.md#q-const) -* JavaScript 中不同类型以及不同环境下变量的内存都是何时释放? [[more]](sections/js-basic.md#q-mem) - -[阅读更多](sections/js-basic.md) - -## [模块](sections/module.md) - -* [`[Basic]` 模块机制](sections/module.md#模块机制) -* [`[Basic]` 热更新](sections/module.md#热更新) -* [`[Basic]` 上下文](sections/module.md#上下文) -* [`[Basic]` 包管理](sections/module.md#包管理) - -### 常见问题 - -* a.js 和 b.js 两个文件互相 require 是否会死循环? 双方是否能导出变量? 如何从设计上避免这种问题? [[more]](sections/module.md#q-loop) -* 如果 a.js require 了 b.js, 那么在 b 中定义全局变量 `t = 111` 能否在 a 中直接打印出来? [[more]](sections/module.md#q-global) -* 如何在不重启 node 进程的情况下热更新一个 js/json 文件? 这个问题本身是否有问题? [[more]](sections/module.md#q-hot) - -[阅读更多](sections/module.md) - -## [事件/异步](sections/event-async.md) - -* [`[Basic]` Promise](sections/event-async.md#promise) -* [`[Doc]` Events (事件)](sections/event-async.md#events) -* [`[Doc]` Timers (定时器)](sections/event-async.md#timers) -* [`[Point]` 阻塞/异步](sections/event-async.md#阻塞异步) -* [`[Point]` 并行/并发](sections/event-async.md#并行并发) - -### 常见问题 - -* Promise 中 .then 的第二参数与 .catch 有什么区别? [[more]](sections/event-async.md#q-1) -* Eventemitter 的 emit 是同步还是异步? [[more]](sections/event-async.md#q-2) -* 如何判断接口是否异步? 是否只要有回调函数就是异步? [[more]](sections/event-async.md#q-3) -* nextTick, setTimeout 以及 setImmediate 三者有什么区别? [[more]](sections/event-async.md#q-4) -* 如何实现一个 sleep 函数? [[more]](sections/event-async.md#q-5) -* 如何实现一个异步的 reduce? (注:不是异步完了之后同步 reduce) [[more]](sections/event-async.md#q-6) - -[阅读更多](sections/event-async.md) - -## [进程](sections/process.md) - -* [`[Doc]` Process (进程)](sections/process.md#process) -* [`[Doc]` Child Processes (子进程)](sections/process.md#child-process) -* [`[Doc]` Cluster (集群)](sections/process.md#cluster) -* [`[Basic]` 进程间通信](sections/process.md#进程间通信) -* [`[Basic]` 守护进程](sections/process.md#守护进程) - -### 常见问题 - -* 进程的当前工作目录是什么? 有什么作用? [[more]](sections/process.md#q-cwd) -* child_process.fork 与 POSIX 的 fork 有什么区别? [[more]](sections/process.md#q-fork) -* 父进程或子进程的死亡是否会影响对方? 什么是孤儿进程? [[more]](sections/process.md#q-child) -* cluster 是如何保证负载均衡的? [[more]](sections/process.md#how-it-works) -* 什么是守护进程? 如何实现守护进程? [[more]](sections/process.md#守护进程) - -[阅读更多](sections/process.md) - - -## [IO](sections/io.md) - -* [`[Doc]` Buffer](sections/io.md#buffer) -* [`[Doc]` String Decoder (字符串解码)](sections/io.md#string-decoder) -* [`[Doc]` Stream (流)](sections/io.md#stream) -* [`[Doc]` Console (控制台)](sections/io.md#console) -* [`[Doc]` File System (文件系统)](sections/io.md#file) -* [`[Doc]` Readline](sections/io.md#readline) -* [`[Doc]` REPL](sections/io.md#repl) - -### 常见问题 - -* Buffer 一般用于处理什么数据? 其长度能否动态变化? [[more]](sections/io.md#buffer) -* Stream 的 highWaterMark 与 drain 事件是什么? 二者之间的关系是? [[more]](sections/io.md#缓冲区) -* Stream 的 pipe 的作用是? 在 pipe 的过程中数据是引用传递还是拷贝传递? [[more]](sections/io.md#pipe) -* 什么是文件描述符? 输入流/输出流/错误流是什么? [[more]](sections/io.md#file) -* console.log 是同步还是异步? 如何实现一个 console.log? [[more]](sections/io.md#console) -* 如何同步的获取用户的输入? [[more]](sections/io.md#如何同步的获取用户的输入) -* Readline 是如何实现的? (有思路即可) [[more]](sections/io.md#readline) - -[阅读更多](sections/io.md) - -## [Network](sections/network.md) - -* [`[Doc]` Net (网络)](sections/network.md#net) -* [`[Doc]` UDP/Datagram](sections/network.md#udp) -* [`[Doc]` HTTP](sections/network.md#http) -* [`[Doc]` DNS (域名服务器)](sections/network.md#dns) -* [`[Doc]` ZLIB (压缩)](sections/network.md#zlib) -* [`[Point]` RPC](sections/network.md#rpc) - -### 常见问题 - -* cookie 与 session 的区别? 服务端如何清除 cookie? [[more]](sections/network.md#q-cookie-session) -* HTTP 协议中的 POST 和 PUT 有什么区别? [[more]](sections/network.md#q-post-put) -* 什么是跨域请求? 如何允许跨域? [[more]](sections/network.md#q-cors) -* TCP/UDP 的区别? TCP 粘包是怎么回事,如何处理? UDP 有粘包吗? [[more]](sections/network.md#q-tcp-udp) -* `TIME_WAIT` 是什么情况? 出现过多的 `TIME_WAIT` 可能是什么原因? [[more]](sections/network.md#q-time-wait) -* ECONNRESET 是什么错误? 如何复现这个错误? -* socket hang up 是什么意思? 可能在什么情况下出现? [[more]](sections/network.md#socket-hang-up) -* hosts 文件是什么? 什么叫 DNS 本地解析? -* 列举几个提高网络传输速度的办法? - -[阅读更多](sections/network.md) - -## [OS](sections/os.md) - -* [`[Doc]` TTY](sections/os.md#tty) -* [`[Doc]` OS (操作系统)](sections/os.md#os-1) -* [`[Doc]` Path](sections/os.md#path) -* [`[Doc]` 命令行参数](sections/os.md#命令行参数) -* [`[Basic]` 负载](sections/os.md#负载) -* [`[Point]` CheckList](sections/os.md#checklist) - -### 常见问题 - -* 什么是 TTY? 如何判断是否处于 TTY 环境? [[more]](sections/os.md#tty) -* 不同操作系统的换行符 (EOL) 有什么区别? [[more]](sections/os.md#os) -* 服务器负载是什么概念? 如何查看负载? [[more]](sections/os.md#负载) -* ulimit 是用来干什么的? [[more]](sections/os.md#ulimit) - -[阅读更多](sections/os.md) - -## [错误处理/调试/优化](sections/error.md) - -* [`[Doc]` Errors (异常)](sections/error.md#errors) -* [`[Doc]` Domain (域)](sections/error.md#domain) -* [`[Doc]` Debugger (调试器)](sections/error.md#debugger) -* [`[Doc]` C/C++ 插件](sections/error.md#c-c++-addon) -* [`[Doc]` V8](sections/error.md#v8) -* [`[Point]` 内存快照](sections/error.md#内存快照) -* [`[Point]` CPU profiling](sections/error.md#cpu-profiling) - -### 常见问题 - -* 怎么处理未预料的出错? 用 try/catch ,domains 还是其它什么? [[more]](sections/error.md#q-handle-error) -* 什么是 `uncaughtException` 事件? 一般在什么情况下使用该事件? [[more]](sections/error.md#uncaughtexception) -* domain 的原理是? 为什么要弃用 domain? [[more]](sections/error.md#domain) -* 什么是防御性编程? 与其相对的 let it crash 又是什么? -* 为什么要在 cb 的第一参数传 error? 为什么有的 cb 第一个参数不是 error, 例如 http.createServer? -* 为什么有些异常没法根据报错信息定位到代码调用? 如何准确的定位一个异常? [[more]](sections/error.md#错误栈丢失) -* 内存泄漏通常由哪些原因导致? 如何分析以及定位内存泄漏? [[more]](sections/error.md#内存快照) - -[阅读更多](sections/error.md) - -## [测试](sections/test.md) - -* [`[Basic]` 测试方法](sections/test.md#测试方法) -* [`[Basic]` 单元测试](sections/test.md#单元测试) -* [`[Basic]` 集成测试](sections/test.md#集成测试) -* [`[Basic]` 基准测试](sections/test.md#基准测试) -* [`[Basic]` 压力测试](sections/test.md#压力测试) -* [`[Doc]` Assert (断言)](sections/test.md#assert) - -### 常见问题 - -* 为什么要写测试? 写测试是否会拖累开发进度?[[more]](sections/test.md#q-why-write-test) -* 单元测试的单元是指什么? 什么是覆盖率?[[more]](sections/test.md#单元测试) -* 测试是如何保证业务逻辑中不会出现死循环的?[[more]](sections/test.md#q-death-loop) -* mock 是什么? 一般在什么情况下 mock?[[more]](sections/test.md#mock) - -[阅读更多](sections/test.md) - -## [util](sections/util.md) - -* [`[Doc]` URL](sections/util.md#url) -* [`[Doc]` Query Strings (查询字符串)](sections/util.md#query-strings) -* [`[Doc]` Utilities (实用函数)](sections/util.md#util-1) -* [`[Basic]` 正则表达式](sections/util.md#正则表达式) - -### 常见问题 - -* HTTP 如何通过 GET 方法 (URL) 传递 let arr = [1,2,3,4] 给服务器? [[more]](sections/util.md#get-param) -* Node.js 中继承 (util.inherits) 的实现? [[more]](sections/util.md#utilinherits) -* 如何递归获取某个文件夹下所有的文件名? [[more]](sections/util.md#q-traversal) - -[阅读更多](sections/util.md) - -## [存储](sections/storage.md) - -* [`[Point]` Mysql](sections/storage.md#mysql) -* [`[Point]` Mongodb](sections/storage.md#mongodb) -* [`[Point]` Replication](sections/storage.md#replication) -* [`[Point]` 数据一致性](sections/storage.md#数据一致性) -* [`[Point]` 缓存](sections/storage.md#缓存) - -### 常见问题 - -* 备份数据库与 M/S, M/M 等部署方式的区别? [[more]](sections/storage.md#replication) -* 索引有什么用,大致原理是什么? 设计索引有什么注意点? [[more]](sections/storage.md#索引) -* Monogdb 连接问题(超时/断开等)有可能是什么问题导致的? [[more]](sections/storage.md#Mongodb) -* 什么情况下数据会出现脏数据? 如何避免? [[more]](sections/storage.md#数据一致性) -* redis 与 memcached 的区别? [[more]](sections/storage.md#缓存) - -[阅读更多](sections/storage.md) - -## [安全](sections/security.md) - -* [`[Doc]` Crypto (加密)](sections/security.md#crypto) -* [`[Doc]` TLS/SSL](sections/security.md#tlsssl) -* [`[Doc]` HTTPS](sections/security.md#https) -* [`[Point]` XSS](sections/security.md#xss) -* [`[Point]` CSRF](sections/security.md#csrf) -* [`[Point]` 中间人攻击](sections/security.md#中间人攻击) -* [`[Point]` Sql/Nosql 注入](sections/security.md#sqlnosql-注入) - -### 常见问题 - -* 加密是如何保证用户密码的安全性? [[more]](sections/security.md#crypto) -* TLS 与 SSL 有什么区别? [[more]](sections/security.md#tlsssl) -* HTTPS 能否被劫持? [[more]](sections/security.md#https) -* XSS 攻击是什么? 有什么危害? [[more]](sections/security.md#xss) -* 过滤 Html 标签能否防止 XSS? 请列举不能的情况? [[more]](sections/security.md#xss) -* CSRF 是什么? 如何防范? [[more]](sections/security.md#csrf) -* 如何避免中间人攻击? [[more]](sections/security.md#中间人攻击) - -[阅读更多](sections/security.md) - -## 最后 - -目前 repo 处于施工现场的情况,如果发现问题欢迎在 [issues](https://github.com/ElemeFE/node-interview/issues) 中指出。如果有比较好的问题/知识点/指正,也欢迎提 PR。 - -另外关于 Js 基础 是个比较大的话题,在本教程不会很细致深入的讨论,更多的是列出一些重要或者跟服务端更相关的地方,所以如果你拿着《JavaScript 权威指南》给教程提 PR 可能不会采纳。本教程的重点更准确的说是服务端基础中 Node.js 程序员需要了解的部分。 +##### [English](sections/en-us/) +##### [简体中文](sections/zh-cn/) diff --git a/_navbar.md b/_navbar.md index 3f1f12d..f32dd39 100644 --- a/_navbar.md +++ b/_navbar.md @@ -1,11 +1,4 @@ -- [首页](/) -- 章节 - - [事件/异步](sections/event-async) - - [IO](sections/io) - - [Javascript 基础问题](sections/js-basic) - - [模块](sections/module) - - [Network](sections/network) - - [进程](sections/process) +- [Home](/) - Translations - - [中文](/) - - [English](/en-us/) \ No newline at end of file + - [English](sections/en-us/) + - [简体中文](sections/zh-cn/) \ No newline at end of file diff --git a/_sidebar.md b/_sidebar.md new file mode 100644 index 0000000..07835c3 --- /dev/null +++ b/_sidebar.md @@ -0,0 +1,7 @@ + + +- [Node interview of Eleme](/) + - [Introduction](/) +- [Versions](/) + - [English](sections/en-us/README.md) + - [中文简体](sections/zh-cn/README.md) \ No newline at end of file diff --git a/en-us/README.md b/en-us/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/en-us/_navbar.md b/en-us/_navbar.md deleted file mode 100644 index 89618db..0000000 --- a/en-us/_navbar.md +++ /dev/null @@ -1,11 +0,0 @@ -- [Home](/en-us/) -- Sections - - [Event/Async](/en-us/sections/event-async) - - [IO](/en-us/sections/io) - - [Javascript Basic](/en-us/sections/js-basic) - - [Module](/en-us/sections/module) - - [Network](/en-us/sections/network) - - [Process](/en-us/sections/process) -- Translations - - [中文](/) - - [English](/en-us/) diff --git a/index.html b/index.html index 00dc555..1e2acd3 100644 --- a/index.html +++ b/index.html @@ -40,7 +40,8 @@ window.$docsify = { name: 'Node.js Interview', auto2top: true, - loadNavbar: true + loadNavbar: true, + // loadSidebar: true } diff --git a/sections/en-us/README.md b/sections/en-us/README.md new file mode 100644 index 0000000..d796162 --- /dev/null +++ b/sections/en-us/README.md @@ -0,0 +1,109 @@ +![ElemeFE-background](assets/ElemeFE-background.png) + +# Node interview of ElemeFE + +Hi, welcome to ElemeFE + +## Guide + +## [Basic](sections/en-us/js-basic.md) + +> It's much more diff between frontend and backend. + + +**Common Problem** + + +[View more](sections/en-us/js-basic.md) + +## [Module](sections/en-us/module.md) + + +**Common Problem** + + +[View more](sections/en-us/module.md) + +## [Event & Async](sections/en-us/event-async.md) + + +**Common Problem** + + +[View more](sections/en-us/event-async.md) + +## [Process](sections/en-us/process.md) + + +**Common Problem** + + +[View more](sections/en-us/process.md) + + +## [IO](sections/en-us/io.md) + + +**Common Problem** + + +[View more](sections/en-us/io.md) + +## [Network](sections/en-us/network.md) + + +**Common Problem** + + +[View more](sections/en-us/network.md) + +## [OS](sections/en-us/os.md) + + +**Common Problem** + + +[View more](sections/en-us/os.md) + +## [Error/Debug/Opt](sections/en-us/error.md) + + +**Common Problem** + + +[View more](sections/en-us/error.md) + +## [Test](sections/en-us/test.md) + + +**Common Problem** + + +[View more](sections/en-us/test.md) + +## [Util](sections/en-us/util.md) + + +**Common Problem** + + +[View more](sections/en-us/util.md) + +## [Storage](sections/en-us/storage.md) + + +**Common Problem** + + +[View more](sections/en-us/storage.md) + +## [Security](sections/en-us/security.md) + + +**Common Problem** + + +[View more](sections/en-us/security.md) + +## Final + diff --git a/sections/en-us/_sidebar.md b/sections/en-us/_sidebar.md new file mode 100644 index 0000000..97ff9c5 --- /dev/null +++ b/sections/en-us/_sidebar.md @@ -0,0 +1,16 @@ + + +- [Node interview](/) + - [Guide](/) + - [Js Basic](sections/en-us/js-basic.md) + - [Module](sections/en-us/module.md) + - [Event & Async](sections/en-us/event-async.md) + - [Process](sections/en-us/process.md) + - [IO](sections/en-us/io.md) + - [Network](sections/en-us/network.md) + - [OS](sections/en-us/os.md) + - [Error/Debug/Opt](sections/en-us/error.md) + - [Test](sections/en-us/test.md) + - [Util](sections/en-us/util.md) + - [Storage](sections/en-us/storage.md) + - [Security](sections/en-us/security.md) diff --git a/sections/error.md b/sections/en-us/error.md similarity index 100% rename from sections/error.md rename to sections/en-us/error.md diff --git a/sections/event-async.md b/sections/en-us/event-async.md similarity index 100% rename from sections/event-async.md rename to sections/en-us/event-async.md diff --git a/sections/io.md b/sections/en-us/io.md similarity index 100% rename from sections/io.md rename to sections/en-us/io.md diff --git a/sections/js-basic.md b/sections/en-us/js-basic.md similarity index 100% rename from sections/js-basic.md rename to sections/en-us/js-basic.md diff --git a/sections/module.md b/sections/en-us/module.md similarity index 100% rename from sections/module.md rename to sections/en-us/module.md diff --git a/sections/network.md b/sections/en-us/network.md similarity index 100% rename from sections/network.md rename to sections/en-us/network.md diff --git a/sections/os.md b/sections/en-us/os.md similarity index 100% rename from sections/os.md rename to sections/en-us/os.md diff --git a/sections/process.md b/sections/en-us/process.md similarity index 100% rename from sections/process.md rename to sections/en-us/process.md diff --git a/en-us/sections/error.md b/sections/en-us/sections/error.md similarity index 100% rename from en-us/sections/error.md rename to sections/en-us/sections/error.md diff --git a/en-us/sections/event-async.md b/sections/en-us/sections/event-async.md similarity index 100% rename from en-us/sections/event-async.md rename to sections/en-us/sections/event-async.md diff --git a/en-us/sections/io.md b/sections/en-us/sections/io.md similarity index 100% rename from en-us/sections/io.md rename to sections/en-us/sections/io.md diff --git a/en-us/sections/js-basic.md b/sections/en-us/sections/js-basic.md similarity index 100% rename from en-us/sections/js-basic.md rename to sections/en-us/sections/js-basic.md diff --git a/en-us/sections/module.md b/sections/en-us/sections/module.md similarity index 100% rename from en-us/sections/module.md rename to sections/en-us/sections/module.md diff --git a/en-us/sections/network.md b/sections/en-us/sections/network.md similarity index 100% rename from en-us/sections/network.md rename to sections/en-us/sections/network.md diff --git a/en-us/sections/os.md b/sections/en-us/sections/os.md similarity index 100% rename from en-us/sections/os.md rename to sections/en-us/sections/os.md diff --git a/en-us/sections/process.md b/sections/en-us/sections/process.md similarity index 100% rename from en-us/sections/process.md rename to sections/en-us/sections/process.md diff --git a/sections/security.md b/sections/en-us/security.md similarity index 100% rename from sections/security.md rename to sections/en-us/security.md diff --git a/sections/storage.md b/sections/en-us/storage.md similarity index 100% rename from sections/storage.md rename to sections/en-us/storage.md diff --git a/sections/test.md b/sections/en-us/test.md similarity index 100% rename from sections/test.md rename to sections/en-us/test.md diff --git a/sections/util.md b/sections/en-us/util.md similarity index 100% rename from sections/util.md rename to sections/en-us/util.md diff --git a/sections/zh-cn/README.md b/sections/zh-cn/README.md new file mode 100644 index 0000000..2840334 --- /dev/null +++ b/sections/zh-cn/README.md @@ -0,0 +1,253 @@ +![ElemeFE-background](assets/ElemeFE-background.png) + +# 如何通过饿了么 Node.js 面试 + +Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过饿了么大前端的面试, 职位是 2~3 年经验的 Node.js 服务端程序员 (并不是全栈), 如果你对这个职位感兴趣或者学习 Node.js 一些进阶的内容, 那么欢迎围观. + +需要注意的是, 本文针对的并不是零基础的同学, 你需要有一定的 JavaScript/Node.js 基础, 并且有一定的工作经验. 另外本教程的重点更准确的说是服务端基础中 Node.js 程序员需要了解的部分. + +如果你觉得大多不了解, 就不用投简历了 (这样两边都节约了时间), 如果你觉得大都有了解或者**光看大纲都都觉得很简单那么欢迎投递简历至 ElemeFe (fe.job@ele.me)**. + +## 导读 + +虽然说目的是要通过面试, 但是本教程并不是简单的把所有面试题列出来, 而**主要是将面试中需要确认你是否懂的点列举出来**, 并进行一定程度的讨论. + +本文将一些常见的问题划分归类, 每类标明涵盖的一些`覆盖点`, 并且列举几个`常见问题`, 通常这些问题都是 2~3 年工作经验需要了解或者面对的. 如果你对某类问题感兴趣, 或者想知道其中列举问题的答案, 可以通过该类下方的 `阅读更多` 查看更多的内容. + +整体上大纲列举的并不是很全面, 细节上覆盖率不高, 很多讨论只是点到即止, 希望大家带着问题去思考. + +## [Js 基础问题](sections/zh-cn/js-basic.md) + +> 与前端 Js 不同, 后端是直面服务器的, 更加偏向内存方面. + +* [`[Basic]` 类型判断](sections/zh-cn/js-basic.md#类型判断) +* [`[Basic]` 作用域](sections/zh-cn/js-basic.md#作用域) +* [`[Basic]` 引用传递](sections/zh-cn/js-basic.md#引用传递) +* [`[Basic]` 内存释放](sections/zh-cn/js-basic.md#内存释放) +* [`[Basic]` ES6 新特性](sections/zh-cn/js-basic.md#es6-新特性) + +**常见问题** + +* js 中什么类型是引用传递, 什么类型是值传递? 如何将值类型的变量以引用的方式传递? [[more]](sections/zh-cn/js-basic.md#q-value) +* js 中, 0.1 + 0.2 === 0.3 是否为 true ? 在不知道浮点数位数时应该怎样判断两个浮点数之和与第三数是否相等? +* const 定义的 Array 中间元素能否被修改? 如果可以, 那 const 修饰对象的意义是? [[more]](sections/zh-cn/js-basic.md#q-const) +* JavaScript 中不同类型以及不同环境下变量的内存都是何时释放? [[more]](sections/zh-cn/js-basic.md#q-mem) + +[阅读更多](sections/zh-cn/js-basic.md) + +## [模块](sections/zh-cn/module.md) + +* [`[Basic]` 模块机制](sections/zh-cn/module.md#模块机制) +* [`[Basic]` 热更新](sections/zh-cn/module.md#热更新) +* [`[Basic]` 上下文](sections/zh-cn/module.md#上下文) +* [`[Basic]` 包管理](sections/zh-cn/module.md#包管理) + +**常见问题** + +* a.js 和 b.js 两个文件互相 require 是否会死循环? 双方是否能导出变量? 如何从设计上避免这种问题? [[more]](sections/zh-cn/module.md#q-loop) +* 如果 a.js require 了 b.js, 那么在 b 中定义全局变量 `t = 111` 能否在 a 中直接打印出来? [[more]](sections/zh-cn/module.md#q-global) +* 如何在不重启 node 进程的情况下热更新一个 js/json 文件? 这个问题本身是否有问题? [[more]](sections/zh-cn/module.md#q-hot) + +[阅读更多](sections/zh-cn/module.md) + +## [事件/异步](sections/zh-cn/event-async.md) + +* [`[Basic]` Promise](sections/zh-cn/event-async.md#promise) +* [`[Doc]` Events (事件)](sections/zh-cn/event-async.md#events) +* [`[Doc]` Timers (定时器)](sections/zh-cn/event-async.md#timers) +* [`[Point]` 阻塞/异步](sections/zh-cn/event-async.md#阻塞异步) +* [`[Point]` 并行/并发](sections/zh-cn/event-async.md#并行并发) + +**常见问题** + +* Promise 中 .then 的第二参数与 .catch 有什么区别? [[more]](sections/zh-cn/event-async.md#q-1) +* Eventemitter 的 emit 是同步还是异步? [[more]](sections/zh-cn/event-async.md#q-2) +* 如何判断接口是否异步? 是否只要有回调函数就是异步? [[more]](sections/zh-cn/event-async.md#q-3) +* nextTick, setTimeout 以及 setImmediate 三者有什么区别? [[more]](sections/zh-cn/event-async.md#q-4) +* 如何实现一个 sleep 函数? [[more]](sections/zh-cn/event-async.md#q-5) +* 如何实现一个异步的 reduce? (注:不是异步完了之后同步 reduce) [[more]](sections/zh-cn/event-async.md#q-6) + +[阅读更多](sections/zh-cn/event-async.md) + +## [进程](sections/zh-cn/process.md) + +* [`[Doc]` Process (进程)](sections/zh-cn/process.md#process) +* [`[Doc]` Child Processes (子进程)](sections/zh-cn/process.md#child-process) +* [`[Doc]` Cluster (集群)](sections/zh-cn/process.md#cluster) +* [`[Basic]` 进程间通信](sections/zh-cn/process.md#进程间通信) +* [`[Basic]` 守护进程](sections/zh-cn/process.md#守护进程) + +**常见问题** + +* 进程的当前工作目录是什么? 有什么作用? [[more]](sections/zh-cn/process.md#q-cwd) +* child_process.fork 与 POSIX 的 fork 有什么区别? [[more]](sections/zh-cn/process.md#q-fork) +* 父进程或子进程的死亡是否会影响对方? 什么是孤儿进程? [[more]](sections/zh-cn/process.md#q-child) +* cluster 是如何保证负载均衡的? [[more]](sections/zh-cn/process.md#how-it-works) +* 什么是守护进程? 如何实现守护进程? [[more]](sections/zh-cn/process.md#守护进程) + +[阅读更多](sections/zh-cn/process.md) + + +## [IO](sections/zh-cn/io.md) + +* [`[Doc]` Buffer](sections/zh-cn/io.md#buffer) +* [`[Doc]` String Decoder (字符串解码)](sections/zh-cn/io.md#string-decoder) +* [`[Doc]` Stream (流)](sections/zh-cn/io.md#stream) +* [`[Doc]` Console (控制台)](sections/zh-cn/io.md#console) +* [`[Doc]` File System (文件系统)](sections/zh-cn/io.md#file) +* [`[Doc]` Readline](sections/zh-cn/io.md#readline) +* [`[Doc]` REPL](sections/zh-cn/io.md#repl) + +**常见问题** + +* Buffer 一般用于处理什么数据? 其长度能否动态变化? [[more]](sections/zh-cn/io.md#buffer) +* Stream 的 highWaterMark 与 drain 事件是什么? 二者之间的关系是? [[more]](sections/zh-cn/io.md#缓冲区) +* Stream 的 pipe 的作用是? 在 pipe 的过程中数据是引用传递还是拷贝传递? [[more]](sections/zh-cn/io.md#pipe) +* 什么是文件描述符? 输入流/输出流/错误流是什么? [[more]](sections/zh-cn/io.md#file) +* console.log 是同步还是异步? 如何实现一个 console.log? [[more]](sections/zh-cn/io.md#console) +* 如何同步的获取用户的输入? [[more]](sections/zh-cn/io.md#如何同步的获取用户的输入) +* Readline 是如何实现的? (有思路即可) [[more]](sections/zh-cn/io.md#readline) + +[阅读更多](sections/zh-cn/io.md) + +## [Network](sections/zh-cn/network.md) + +* [`[Doc]` Net (网络)](sections/zh-cn/network.md#net) +* [`[Doc]` UDP/Datagram](sections/zh-cn/network.md#udp) +* [`[Doc]` HTTP](sections/zh-cn/network.md#http) +* [`[Doc]` DNS (域名服务器)](sections/zh-cn/network.md#dns) +* [`[Doc]` ZLIB (压缩)](sections/zh-cn/network.md#zlib) +* [`[Point]` RPC](sections/zh-cn/network.md#rpc) + +**常见问题** + +* cookie 与 session 的区别? 服务端如何清除 cookie? [[more]](sections/zh-cn/network.md#q-cookie-session) +* HTTP 协议中的 POST 和 PUT 有什么区别? [[more]](sections/zh-cn/network.md#q-post-put) +* 什么是跨域请求? 如何允许跨域? [[more]](sections/zh-cn/network.md#q-cors) +* TCP/UDP 的区别? TCP 粘包是怎么回事,如何处理? UDP 有粘包吗? [[more]](sections/zh-cn/network.md#q-tcp-udp) +* `TIME_WAIT` 是什么情况? 出现过多的 `TIME_WAIT` 可能是什么原因? [[more]](sections/zh-cn/network.md#q-time-wait) +* ECONNRESET 是什么错误? 如何复现这个错误? +* socket hang up 是什么意思? 可能在什么情况下出现? [[more]](sections/zh-cn/network.md#socket-hang-up) +* hosts 文件是什么? 什么叫 DNS 本地解析? +* 列举几个提高网络传输速度的办法? + +[阅读更多](sections/zh-cn/network.md) + +## [OS](sections/zh-cn/os.md) + +* [`[Doc]` TTY](sections/zh-cn/os.md#tty) +* [`[Doc]` OS (操作系统)](sections/zh-cn/os.md#os-1) +* [`[Doc]` Path](sections/zh-cn/os.md#path) +* [`[Doc]` 命令行参数](sections/zh-cn/os.md#命令行参数) +* [`[Basic]` 负载](sections/zh-cn/os.md#负载) +* [`[Point]` CheckList](sections/zh-cn/os.md#checklist) + +**常见问题** + +* 什么是 TTY? 如何判断是否处于 TTY 环境? [[more]](sections/zh-cn/os.md#tty) +* 不同操作系统的换行符 (EOL) 有什么区别? [[more]](sections/zh-cn/os.md#os) +* 服务器负载是什么概念? 如何查看负载? [[more]](sections/zh-cn/os.md#负载) +* ulimit 是用来干什么的? [[more]](sections/zh-cn/os.md#ulimit) + +[阅读更多](sections/zh-cn/os.md) + +## [错误处理/调试/优化](sections/zh-cn/error.md) + +* [`[Doc]` Errors (异常)](sections/zh-cn/error.md#errors) +* [`[Doc]` Domain (域)](sections/zh-cn/error.md#domain) +* [`[Doc]` Debugger (调试器)](sections/zh-cn/error.md#debugger) +* [`[Doc]` C/C++ 插件](sections/zh-cn/error.md#c-c++-addon) +* [`[Doc]` V8](sections/zh-cn/error.md#v8) +* [`[Point]` 内存快照](sections/zh-cn/error.md#内存快照) +* [`[Point]` CPU profiling](sections/zh-cn/error.md#cpu-profiling) + +**常见问题** + +* 怎么处理未预料的出错? 用 try/catch ,domains 还是其它什么? [[more]](sections/zh-cn/error.md#q-handle-error) +* 什么是 `uncaughtException` 事件? 一般在什么情况下使用该事件? [[more]](sections/zh-cn/error.md#uncaughtexception) +* domain 的原理是? 为什么要弃用 domain? [[more]](sections/zh-cn/error.md#domain) +* 什么是防御性编程? 与其相对的 let it crash 又是什么? +* 为什么要在 cb 的第一参数传 error? 为什么有的 cb 第一个参数不是 error, 例如 http.createServer? +* 为什么有些异常没法根据报错信息定位到代码调用? 如何准确的定位一个异常? [[more]](sections/zh-cn/error.md#错误栈丢失) +* 内存泄漏通常由哪些原因导致? 如何分析以及定位内存泄漏? [[more]](sections/zh-cn/error.md#内存快照) + +[阅读更多](sections/zh-cn/error.md) + +## [测试](sections/zh-cn/test.md) + +* [`[Basic]` 测试方法](sections/zh-cn/test.md#测试方法) +* [`[Basic]` 单元测试](sections/zh-cn/test.md#单元测试) +* [`[Basic]` 集成测试](sections/zh-cn/test.md#集成测试) +* [`[Basic]` 基准测试](sections/zh-cn/test.md#基准测试) +* [`[Basic]` 压力测试](sections/zh-cn/test.md#压力测试) +* [`[Doc]` Assert (断言)](sections/zh-cn/test.md#assert) + +**常见问题** + +* 为什么要写测试? 写测试是否会拖累开发进度?[[more]](sections/zh-cn/test.md#q-why-write-test) +* 单元测试的单元是指什么? 什么是覆盖率?[[more]](sections/zh-cn/test.md#单元测试) +* 测试是如何保证业务逻辑中不会出现死循环的?[[more]](sections/zh-cn/test.md#q-death-loop) +* mock 是什么? 一般在什么情况下 mock?[[more]](sections/zh-cn/test.md#mock) + +[阅读更多](sections/zh-cn/test.md) + +## [util](sections/zh-cn/util.md) + +* [`[Doc]` URL](sections/zh-cn/util.md#url) +* [`[Doc]` Query Strings (查询字符串)](sections/zh-cn/util.md#query-strings) +* [`[Doc]` Utilities (实用函数)](sections/zh-cn/util.md#util-1) +* [`[Basic]` 正则表达式](sections/zh-cn/util.md#正则表达式) + +**常见问题** + +* HTTP 如何通过 GET 方法 (URL) 传递 let arr = [1,2,3,4] 给服务器? [[more]](sections/zh-cn/util.md#get-param) +* Node.js 中继承 (util.inherits) 的实现? [[more]](sections/zh-cn/util.md#utilinherits) +* 如何递归获取某个文件夹下所有的文件名? [[more]](sections/zh-cn/util.md#q-traversal) + +[阅读更多](sections/zh-cn/util.md) + +## [存储](sections/zh-cn/storage.md) + +* [`[Point]` Mysql](sections/zh-cn/storage.md#mysql) +* [`[Point]` Mongodb](sections/zh-cn/storage.md#mongodb) +* [`[Point]` Replication](sections/zh-cn/storage.md#replication) +* [`[Point]` 数据一致性](sections/zh-cn/storage.md#数据一致性) +* [`[Point]` 缓存](sections/zh-cn/storage.md#缓存) + +**常见问题** + +* 备份数据库与 M/S, M/M 等部署方式的区别? [[more]](sections/zh-cn/storage.md#replication) +* 索引有什么用,大致原理是什么? 设计索引有什么注意点? [[more]](sections/zh-cn/storage.md#索引) +* Monogdb 连接问题(超时/断开等)有可能是什么问题导致的? [[more]](sections/zh-cn/storage.md#Mongodb) +* 什么情况下数据会出现脏数据? 如何避免? [[more]](sections/zh-cn/storage.md#数据一致性) +* redis 与 memcached 的区别? [[more]](sections/zh-cn/storage.md#缓存) + +[阅读更多](sections/zh-cn/storage.md) + +## [安全](sections/zh-cn/security.md) + +* [`[Doc]` Crypto (加密)](sections/zh-cn/security.md#crypto) +* [`[Doc]` TLS/SSL](sections/zh-cn/security.md#tlsssl) +* [`[Doc]` HTTPS](sections/zh-cn/security.md#https) +* [`[Point]` XSS](sections/zh-cn/security.md#xss) +* [`[Point]` CSRF](sections/zh-cn/security.md#csrf) +* [`[Point]` 中间人攻击](sections/zh-cn/security.md#中间人攻击) +* [`[Point]` Sql/Nosql 注入](sections/zh-cn/security.md#sqlnosql-注入) + +**常见问题** + +* 加密是如何保证用户密码的安全性? [[more]](sections/zh-cn/security.md#crypto) +* TLS 与 SSL 有什么区别? [[more]](sections/zh-cn/security.md#tlsssl) +* HTTPS 能否被劫持? [[more]](sections/zh-cn/security.md#https) +* XSS 攻击是什么? 有什么危害? [[more]](sections/zh-cn/security.md#xss) +* 过滤 Html 标签能否防止 XSS? 请列举不能的情况? [[more]](sections/zh-cn/security.md#xss) +* CSRF 是什么? 如何防范? [[more]](sections/zh-cn/security.md#csrf) +* 如何避免中间人攻击? [[more]](sections/zh-cn/security.md#中间人攻击) + +[阅读更多](sections/zh-cn/security.md) + +## 最后 + +目前 repo 处于施工现场的情况,如果发现问题欢迎在 [issues](https://github.com/ElemeFE/node-interview/issues) 中指出。如果有比较好的问题/知识点/指正,也欢迎提 PR。 + +另外关于 Js 基础 是个比较大的话题,在本教程不会很细致深入的讨论,更多的是列出一些重要或者跟服务端更相关的地方,所以如果你拿着《JavaScript 权威指南》给教程提 PR 可能不会采纳。本教程的重点更准确的说是服务端基础中 Node.js 程序员需要了解的部分。 diff --git a/sections/zh-cn/_sidebar.md b/sections/zh-cn/_sidebar.md new file mode 100644 index 0000000..c184c0b --- /dev/null +++ b/sections/zh-cn/_sidebar.md @@ -0,0 +1,16 @@ + + +- [如何通过饿了么 Node.js 面试](sections/zh-cn/) + - [导读](sections/zh-cn/) + - [Js 基础问题](sections/zh-cn/js-basic.md) + - [模块](sections/zh-cn/module.md) + - [事件/异步](sections/zh-cn/event-async.md) + - [进程](sections/zh-cn/process.md) + - [IO](sections/zh-cn/io.md) + - [Network](sections/zh-cn/network.md) + - [OS](sections/zh-cn/os.md) + - [错误处理/调试/优化](sections/zh-cn/error.md) + - [测试](sections/zh-cn/test.md) + - [util](sections/zh-cn/util.md) + - [存储](sections/zh-cn/storage.md) + - [安全](sections/zh-cn/security.md) diff --git a/sections/zh-cn/error.md b/sections/zh-cn/error.md new file mode 100644 index 0000000..96f23a9 --- /dev/null +++ b/sections/zh-cn/error.md @@ -0,0 +1,369 @@ +# 错误处理/调试/优化 + +* `[Doc]` Errors (异常) +* `[Doc]` Domain (域) +* `[Doc]` Debugger (调试器) +* `[Doc]` C/C++ 插件 +* `[Doc]` V8 +* `[Point]` 内存快照 +* `[Point]` CPU剖析 + + +## Errors + +在 Node.js 中的错误主要有一下四种类型: + +|错误|名称|触发| +|---|---|---| +|Standard JavaScript errors|标准 JavaScript 错误|由错误代码触发| +|System errors|系统错误|由操作系统触发| +|User-specified errors|用户自定义错误|通过 throw 抛出| +|Assertion errors|断言错误|由 `assert` 模块触发| + +其中标准的 JavaScript 错误常见有: + +* [EvalError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/EvalError): 调用 eval() 出现错误时抛出该错误 +* [SyntaxError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError): 代码不符合 JavaScript 语法规范时抛出该错误 +* [RangeError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError): 数组越界时抛出该错误 +* [ReferenceError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ReferenceError): 引用未定义的变量时抛出该错误 +* [TypeError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError): 参数类型错误时抛出该错误 +* [URIError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/URIError): 误用全局的 URI 处理函数时抛出该错误 + +而常见的系统错误列表可以通过 Node.js 的 os 对象常看列表: + +```javascript +const os = require('os'); + +console.log(os.constants.errno); +``` + +目前搜索 Node.js 面试题, 发现很多题目已经跟不上 Node.js 的发展了.比较老的 [NodeJS 错误处理最佳实践](https://cnodejs.org/topic/55714dfac4e7fbea6e9a2e5d), 译自 Joyent 的官方博客, 其中有这样的描述: + +> 实际上, `try/catch` 唯一常用的是在 `JSON.parse` 和类似验证用户输入的地方 + +然而实际上现在在 Node.js 中你已经可以轻松的使用 try/catch 去捕获异步的异常了. 并且在 Node.js v7.6 之后使用了升级引擎的新版 v8, 旧版中 try/catch 代码不能优化的问题也解决了. 所以我们现在再来看 + +> 怎么处理未预料的出错? 用 try/catch , domains 还是其它什么? + +在 Node.js 中错误处理主要有一下几种方法: + +* callback(err, data) 回调约定 +* throw / try / catch +* EventEmitter 的 error 事件 + +callback(err, data) 这种形式的错误处理起来繁琐, 并不具备强制性, 目前已经处于仅需要了解, 不推荐使用的情况. 而 domain 模块则是半只脚踏进棺材了. + +1) 感谢 [co](https://github.com/visionmedia/co) 的先河, 现在的你已经简单的使用 try/catch 保护关键的位置, 以 koa 为例, 可以通过中间件的形式来进行错误处理, 详见 [Koa error handling](https://github.com/koajs/koa/wiki/Error-Handling). 之后的 async/await 均属于这种模式. + +2) 通过 EventEmitter 的错误监听形式为各大关键的对象加上错误监听的回调. 例如监听 http server, tcp server 等对象的 `error` 事件以及 process 对象提供的 `uncaughtException` 和 `unhandledRejection` 等等. + +3) 使用 Promise 来封装异步, 并通过 Promise 的错误处理来 handle 错误. + +4) 如果上述办法不能起到良好的作用, 那么你需要学习如何优雅的 [Let It Crash](http://wiki.c2.com/?LetItCrash) + +> 为什么要在 cb 的第一参数传 error? 为什么有的 cb 第一个参数不是 error, 例如 http.createServer? + +TODO + + +### 错误栈丢失 + +```javascript +function test() { + throw new Error('test error'); +} + +function main() { + test(); +} + +main(); +``` + +可以收获报错: + +```javascript +/data/node-interview/error.js:2 + throw new Error('test error'); + ^ + +Error: test error + at test (/data/node-interview/error.js:2:9) + at main (/data/node-interview/error.js:6:3) + at Object. (/data/node-interview/error.js:9:1) + at Module._compile (module.js:570:32) + at Object.Module._extensions..js (module.js:579:10) + at Module.load (module.js:487:32) + at tryModuleLoad (module.js:446:12) + at Function.Module._load (module.js:438:3) + at Module.runMain (module.js:604:10) + at run (bootstrap_node.js:394:7) +``` + +可以发现报错的行数, test 函数, main 函数的调用关系都在 stack 中清晰的体现. + +当你使用 setImmediate 等定时器来设置异步的时候: + +```javascript +function test() { + throw new Error('test error'); +} + +function main() { + setImmediate(() => test()); +} + +main(); + +``` + +我们发现 + +```javascript +/data/node-interview/error.js:2 + throw new Error('test error'); + ^ + +Error: test error + at test (/data/node-interview/error.js:2:9) + at Immediate.setImmediate (/data/node-interview/error.js:6:22) + at runCallback (timers.js:637:20) + at tryOnImmediate (timers.js:610:5) + at processImmediate [as _immediateCallback] (timers.js:582:5) +``` + +错误栈中仅输出到 test 函数内调用的地方位置, 再往上 main 的调用信息就丢失了. 也就是说如果你的函数调用深度比较深的情况下, 你使用异步调用某个函数出错了的情况下追溯这个异步的调用是一个很困难的事情, 因为其之上的栈都已经丢失了. 如果你用过 [async](https://github.com/caolan/async) 之类的模块, 你还可能发现, 报错的 stack 会非常的长而且曲折, 光看 stack 很难去定位问题. + +这在项目不大/作者清楚的情况下不是问题, 但是当项目大起来, 开发人员多起来之后, 这样追溯错误会变得异常痛苦. 关于这个问题, 在上文中提到 [错误处理的最佳实践](https://cnodejs.org/topic/55714dfac4e7fbea6e9a2e5d) 中, 关于 `编写新函数的具体建议` 那一带的内容有描述到. 通过使用 [verror](https://www.npmjs.com/package/verror) 这样的方式, 让 Error 一层层封装, 并在每一层将错误的信息一层层的包上, 最后拿到的 Error 直接可以从 message 中获取用于定位问题的关键信息. + +以昨天的数据为准(2017-3-13)各位只要对比一下看看 npm 上上个月 [verror](https://www.npmjs.com/package/verror) 的下载量 `1100w` 比 [express](https://www.npmjs.com/package/express) 的 `1070w` 还高. 应该就能感受到这种写法有多流行了. + +### 防御性编程 + +错误并不可怕, 可怕的是你不去准备应对错误————[防御性编程的介绍和技巧](http://blog.jobbole.com/101651/) + +### let it crash + +[Let It Crash](http://wiki.c2.com/?LetItCrash) + +### uncaughtException + +当异常没有被捕获一路冒泡到 Event Loop 时就会触发该事件 process 对象上的 `uncaughtException` 事件. 默认情况下, Node.js 对于此类异常会直接将其堆栈跟踪信息输出给 `stderr` 并结束进程, 而为 `uncaughtException` 事件添加监听可以覆盖该默认行为, 不会直接结束进程. + +```javascript +process.on('uncaughtException', (err) => { + console.log(`Caught exception: ${err}`); +}); + +setTimeout(() => { + console.log('This will still run.'); +}, 500); + +// Intentionally cause an exception, but don't catch it. +nonexistentFunc(); +console.log('This will not run.'); +``` + +#### 合理使用 uncaughtException + +`uncaughtException` 的初衷是可以让你拿到错误之后可以做一些回收处理之后再 process.exit. 官方的同志们还曾经讨论过要移除该事件 (详见 [issues](https://github.com/nodejs/node-v0.x-archive/issues/2582)) + +所以你需要明白 `uncaughtException` 其实已经是非常规手段了, 应尽量避免使用它来处理错误. 因为通过该事件捕获到错误后, 并不代表 `你可以愉快的继续运行 (On Error Resume Next)`. 程序内部存在未处理的异常, 这意味着应用程序处于一种未知的状态. 如果不能适当的恢复其状态, 那么很有可能会触发不可预见的问题. (使用 domain 会很夸张的加剧这个现象, 并产生新人不能理解的各类幽灵问题) + +如果在 `.on` 指定的监听回调中报错不会被捕获, Node.js 的进程会直接终端并返回一个非零的退出码, 最后输出相应的堆栈信息. 否则, 会出现无限递归. 除此之外, 内存崩溃/底层报错等情况也不会被捕获, **目前猜测**是 v8/C++ 那边撂担子不干了, Node.js 完全插不上话导致的 (TODO 整理到这里才想起来这个念头尚未验证, 如果有空的朋友帮忙验证下). + +所以官方建议的使用 `uncaughtException` 的正确姿势是在结束进程前使用同步的方式清理已使用的资源 (文件描述符、句柄等) 然后 process.exit. + +在 uncaughtException 事件之后执行普通的恢复操作并不安全. 官方建议是另外在专门准备一个 monitor 进程来做健康检查并通过 monitor 来管理恢复情况, 并在必要的时候重启 (所以官方是含蓄的提醒各位用 pm2 之类的工具). + + +### unhandledRejection + +当 Promise 被 reject 且没有绑定监听处理时, 就会触发该事件. 该事件对排查和追踪没有处理 reject 行为的 Promise 很有用. + +该事件的回调函数接收以下参数: + +* `reason` [``](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) | `` 该 Promise 被 reject 的对象 (通常为 Error 对象) +* `p` 被 reject 的 Promise 本身 + +例如 + +```javascript +process.on('unhandledRejection', (reason, p) => { + console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); + // application specific logging, throwing an error, or other logic here +}); + +somePromise.then((res) => { + return reportToUser(JSON.pasre(res)); // note the typo (`pasre`) +}); // no `.catch` or `.then` +``` + +以下代码也会触发 `unhandledRejection` 事件: + +```javascript +function SomeResource() { + // Initially set the loaded status to a rejected promise + this.loaded = Promise.reject(new Error('Resource not yet loaded!')); +} + +var resource = new SomeResource(); +// no .catch or .then on resource.loaded for at least a turn +``` + +> In this example case, it is possible to track the rejection as a developer error as would typically be the case for other 'unhandledRejection' events. To address such failures, a non-operational `.catch(() => { })` handler may be attached to resource.loaded, which would prevent the 'unhandledRejection' event from being emitted. Alternatively, the 'rejectionHandled' event may be used. + + +## Domain + +Node.js 早期, try/catch 无法捕获异步的错误, 而错误优先的 callback 仅仅是一种约定并没有强制性并且写起来十分繁琐. 所以为了能够很好的捕获异常, Node.js 从 v0.8 开始引入 domain 这个模块. + +domain 本身是一个 EventEmitter 对象, 其中文意思是 "域" 的意思, 捕获异步异常的基本思路是创建一个域, cb 函数会在定义时会继承上一层的域, 报错通过当前域的 `.emit('error', err)` 方法触发错误事件将错误传递上去, 从而使得异步错误可以被强制捕获. (更多内容详见 [Node.js 异步异常的处理与domain模块解析](https://cnodejs.org/topic/516b64596d38277306407936)) + +但是 domain 的引入也带来了更多新的问题. 比如依赖的模块无法继承你定义的 domain, 导致你写的 domain 无法 cover 依赖模块报错. 而且, 很多人 (特别是新人) 由于不了解 Node.js 的内存/异步流程等问题, 在使用 domain 处理报错的时候, 没有做到完善的处理并盲目的让代码继续走下去, 这很可能导致**项目完全无法维护** (可能出现的问题真是不胜枚举, 各种梦魇...) + +该模块目前的情况: [deprecate domains](https://github.com/nodejs/node/issues/66) + + +## Debugger + +![node-js-survey-debug](/assets/node-js-survey-debug.png) + +类似 gdb 的命令行下 debug 工具 (上图中的 build-in debugger), 同时也支持远程 debug (类似 [node-inspector](https://github.com/node-inspector/node-inspector), 目前处于试验状态). 当然, 目前有不少同学觉得 [vscode](https://code.visualstudio.com/) 对 debug 工具集成的比较好. + +关于这个 build-in debugger 使用推荐看[官方文档](https://nodejs.org/dist/latest-v6.x/docs/api/debugger.html). 如果要深入一点, 你可能对本文感兴趣: [动态修改 NodeJS 程序中的变量值](http://code.oneapm.com/nodejs/2015/06/27/intereference/) + + +## C/C++ Addon + +在 Node.js 中开发 addon 最痛苦的地方莫过于升级 V8 导致的 C/C++ 代码不能兼容的问题, 这个问题在很早就出现了. 为了解决这个问题前人开了一个叫 [nan](https://github.com/nodejs/nan) 的项目. + +要学习 addon 开发, 除了[官方文档](https://nodejs.org/docs/latest/api/addons.html)也推荐阅读这个: https://github.com/nodejs/node-addon-examples + + +## V8 + +这里并不是介绍 V8, 而是介绍 Node.js 中的 V8 这个模块. 该模块用于开放 Node.js 内建的 V8 引擎的事件和接口. 这些接口由 V8 底层决定, 所以无法保证绝对的稳定性. + +|接口|描述| +|---|---| +|v8.getHeapStatistics()|获取 heap 信息| +|v8.getHeapSpaceStatistics()|获取 heap space 信息| +|v8.setFlagsFromString(string)|动态设置 V8 options| + +### v8.setFlagsFromString(string) + +该方法用于添加额外的 V8 命令行标志. 该方法需谨慎使用, 在 VM 启动后修改配置可能会发生不可预测的行为、崩溃和数据丢失; 或者什么反应都没有. + +通过 `node --v8-options` 命令可以查询当前 Node.js 环境中有哪些可用的 V8 options. 此外, 还可以参考非官方维护的一个 [V8 options 列表](https://github.com/thlorenz/v8-flags/blob/master/flags-0.11.md). + +用法: + +```javascript +// Print GC events to stdout for one minute. +const v8 = require('v8'); +v8.setFlagsFromString('--trace_gc'); +setTimeout(function() { v8.setFlagsFromString('--notrace_gc'); }, 60e3); +``` + +## 内存快照 + +内存快照常用与解决内存泄漏的问题. 快照工具推荐使用 [heapdump](https://github.com/bnoordhuis/node-heapdump) 用来保存内存快照, 使用 [devtool](https://github.com/Jam3/devtool) 来查看内存快照. 使用 heapdump 保存内存快照时, 只会有 Node.js 环境中的对象, 不会受到干扰(如果使用 [node-inspector](https://github.com/node-inspector/node-inspector) 的话, 快照中会有前端的变量干扰). + +使用以及内存泄漏的常见原因详见: [如何分析 Node.js 中的内存泄漏](https://zhuanlan.zhihu.com/p/25736931?group_id=825001468703674368). + +## CPU profiling + +CPU profiling (剖析) 常用于性能优化. 有许多用于做 profiling 的第三方工具, 但是大部分情况下, 使用 Node.js 内置的是最简单的. 其内置调用的就是 [V8 本身的 profiler](https://github.com/v8/v8/wiki/Using%20V8%E2%80%99s%20internal%20profiler), 它可以在程序执行过程中中是对 stack 间隔性的抽样分析. + +使用 `--prof` 开启内置的 profilling + +```shell +node --prof app.js +``` + +程序运行之后会生成一个 `isolate-0xnnnnnnnnnnnn-v8.log` 在当前运行目录. + +你可以使用 `--prof-process` 来生成报告查看 + +``` +node --prof-process isolate-0xnnnnnnnnnnnn-v8.log +``` + +报告形如: + +``` +Statistical profiling result from isolate-0x103001200-v8.log, (12042 ticks, 2634 unaccounted, 0 excluded). + + [Shared libraries]: + ticks total nonlib name + 35 0.3% /usr/lib/system/libsystem_platform.dylib + 27 0.2% /usr/lib/system/libsystem_pthread.dylib + 7 0.1% /usr/lib/system/libsystem_c.dylib + 3 0.0% /usr/lib/system/libsystem_kernel.dylib + 1 0.0% /usr/lib/system/libsystem_malloc.dylib + + [JavaScript]: + ticks total nonlib name + 208 1.7% 1.7% Stub: LoadICStub + 187 1.6% 1.6% KeyedLoadIC: A keyed load IC from the snapshot + 104 0.9% 0.9% Stub: VectorStoreICStub + 69 0.6% 0.6% LazyCompile: *emit events.js:136:44 + 68 0.6% 0.6% Builtin: CallFunction_ReceiverIsNotNullOrUndefined + 65 0.5% 0.5% KeyedStoreIC: A keyed store IC from the snapshot {2} + 47 0.4% 0.4% Builtin: CallFunction_ReceiverIsAny + 43 0.4% 0.4% LazyCompile: *storeHeader _http_outgoing.js:312:21 + 34 0.3% 0.3% LazyCompile: *removeListener events.js:315:28 + 33 0.3% 0.3% Stub: RegExpExecStub + 33 0.3% 0.3% LazyCompile: *_addListener events.js:210:22 + 32 0.3% 0.3% Stub: CEntryStub + 32 0.3% 0.3% Builtin: ArgumentsAdaptorTrampoline + 31 0.3% 0.3% Stub: FastNewClosureStub + 30 0.2% 0.3% Stub: InstanceOfStub + ... + + [C++]: + ticks total nonlib name + 460 3.8% 3.8% _mach_port_extract_member + 329 2.7% 2.7% _openat$NOCANCEL + 199 1.7% 1.7% ___bsdthread_register + 136 1.1% 1.1% ___mkdir_extended + 116 1.0% 1.0% node::HandleWrap::Close(v8::FunctionCallbackInfo const&) + 112 0.9% 0.9% void v8::internal::BodyDescriptorBase::IterateBodyImpl(v8::internal::Heap*, v8::internal::HeapObject*, int, int) + 106 0.9% 0.9% _http_parser_execute + 103 0.9% 0.9% _szone_malloc_should_clear + 99 0.8% 0.8% int v8::internal::BinarySearch<(v8::internal::SearchMode)1, v8::internal::DescriptorArray>(v8::internal::DescriptorArray*, v8::internal::Name*, int, int*) + 89 0.7% 0.7% node::TCPWrap::Connect(v8::FunctionCallbackInfo const&) + 86 0.7% 0.7% v8::internal::LookupIterator::State v8::internal::LookupIterator::LookupInRegularHolder(v8::internal::Map*, v8::internal::JSReceiver*) + ... + + [Bottom up (heavy) profile]: + Note: percentage shows a share of a particular caller in the total + amount of its parent calls. + Callers occupying less than 2.0% are not shown. + + ticks parent name + 2634 21.9% UNKNOWN + 764 29.0% LazyCompile: *connect net.js:815:17 + 764 100.0% LazyCompile: ~ net.js:966:30 + 764 100.0% LazyCompile: *_tickCallback internal/process/next_tick.js:87:25 + 193 7.3% LazyCompile: *createWriteReq net.js:732:24 + 101 52.3% LazyCompile: *Socket._writeGeneric net.js:660:42 + 99 98.0% LazyCompile: ~ net.js:667:34 + 99 100.0% LazyCompile: ~g events.js:287:13 + 99 100.0% LazyCompile: *emit events.js:136:44 + 92 47.7% LazyCompile: ~Socket._writeGeneric net.js:660:42 + 91 98.9% LazyCompile: ~ net.js:667:34 + 91 100.0% LazyCompile: ~g events.js:287:13 + 91 100.0% LazyCompile: *emit events.js:136:44 + ... +``` + +|字段|描述| +|---|---| +|ticks|时间片| +|total|当前操作执行的时间占总时间的比率| +|nonlib|当前非 System library 执行时间比率| + +整理中 + diff --git a/sections/zh-cn/event-async.md b/sections/zh-cn/event-async.md new file mode 100644 index 0000000..dd882ef --- /dev/null +++ b/sections/zh-cn/event-async.md @@ -0,0 +1,227 @@ +# 事件/异步 + +* [`[Basic]` Promise](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#promise) +* [`[Doc]` Events (事件)](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#events) +* [`[Doc]` Timers (定时器)](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#timers) +* [`[Point]` 阻塞/异步](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#阻塞异步) +* [`[Point]` 并行/并发](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#并行并发) + +## 简述 + +异步还是不异步? 这是一个问题. + +## Promise + +![callback-hell](/assets/callback-hell.jpg) + +相信很多同学在面试的时候都碰到过这样一个问题, `如何处理 Callback Hell`. 在早些年的时候, 大家会看到有很多的解决方案例如 [Q](https://www.npmjs.com/package/q), [async](https://www.npmjs.com/package/async), [EventProxy](https://www.npmjs.com/package/eventproxy) 等等. 最后从流行程度来看 `Promise` 当之无愧的独领风骚, 并且是在 ES6 的 Javascript 标准上赢得了支持. + +关于它的基础知识/概念推荐看阮一峰的 [Promise 对象](http://javascript.ruanyifeng.com/advanced/promise.html#toc9) 这里就不多不赘述. + +> Promise 中 .then 的第二参数与 .catch 有什么区别? + +参见 [We have a problem with promises](https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html) + +另外关于同步与异步, 有个问题希望大家看一下, 这是很简单的 Promise 的使用例子: + +```javascript +let doSth = new Promise((resolve, reject) => { + console.log('hello'); + resolve(); +}); + +doSth.then(() => { + console.log('over'); +}); +``` + +毫无疑问的可以得到一下输出结果: + +``` +hello +over +``` + +但是首先的问题是, 该 Promise 封装的代码肯定是同步的, 那么这个 then 的执行是异步的吗? + +其次的问题是, 如下代码, `setTimeout` 到 10s 之后再 `.then` 调用, 那么 `hello` 是会在 10s 之后在打印吗, 还是一开始就打印? + +```javascript +let doSth = new Promise((resolve, reject) => { + console.log('hello'); + resolve(); +}); + +setTimeout(() => { + doSth.then(() => { + console.log('over'); + }) +}, 10000); +``` + +以及理解如下代码的执行顺序 ([出处](https://zhuanlan.zhihu.com/p/25407758)): + +```javascript +setTimeout(function() { + console.log(1) +}, 0); +new Promise(function executor(resolve) { + console.log(2); + for( var i=0 ; i<10000 ; i++ ) { + i == 9999 && resolve(); + } + console.log(3); +}).then(function() { + console.log(4); +}); +console.log(5); +``` + +如果你不了解这些问题, 可以自己在本地尝试研究一下打印的结果. 这里希望你掌握的是 Promise 的状态转换, 包括异步与 Promise 的关系, 以及 Promise 如何帮助你处理异步, 如果你研究过 Promise 的实现那就更好了. + +## Events + +`Events` 是 Node.js 中一个非常重要的 core 模块, 在 node 中有许多重要的 core API 都是依赖其建立的. 比如 `Stream` 是基于 `Events` 实现的, 而 `fs`, `net`, `http` 等模块都依赖 `Stream`, 所以 `Events` 模块的重要性可见一斑. + +通过继承 EventEmitter 来使得一个类具有 node 提供的基本的 event 方法, 这样的对象可以称作 emitter, 而触发(emit)事件的 cb 则称作 listener. 与前端 DOM 树上的事件并不相同, emitter 的触发不存在冒泡, 逐层捕获等事件行为, 也没有处理事件传递的方法. + +> Eventemitter 的 emit 是同步还是异步? + +Node.js 中 Eventemitter 的 emit 是同步的. 在官方文档中有说明: + +> The EventListener calls all listeners synchronously in the order in which they were registered. This is important to ensure the proper sequencing of events and to avoid race conditions or logic errors. + +另外, 可以讨论如下的执行结果是输出 `hi 1` 还是 `hi 2`? + +```javascript +const EventEmitter = require('events'); + +let emitter = new EventEmitter(); + +emitter.on('myEvent', () => { + console.log('hi 1'); +}); + +emitter.on('myEvent', () => { + console.log('hi 2'); +}); + +emitter.emit('myEvent'); +``` + +或者如下情况是否会死循环? + +```javascript +const EventEmitter = require('events'); + +let emitter = new EventEmitter(); + +emitter.on('myEvent', () => { + console.log('hi'); + emitter.emit('myEvent'); +}); + +emitter.emit('myEvent'); +``` + +以及这样会不会死循环? + +```javascript +const EventEmitter = require('events'); + +let emitter = new EventEmitter(); + +emitter.on('myEvent', function sth () { + emitter.on('myEvent', sth); + console.log('hi'); +}); + +emitter.emit('myEvent'); +``` + +使用 emitter 处理问题可以处理比较复杂的状态场景, 比如 TCP 的复杂状态机, 做多项异步操作的时候每一步都可能报错, 这个时候 .emit 错误并且执行某些 .once 的操作可以将你从泥沼中拯救出来. + +另外可以注意一下的是, 有些同学喜欢用 emitter 来监控某些类的状态, 但是在这些类释放的时候可能会忘记释放 emitter, 而这些类的内部可能持有该 emitter 的 listener 的引用从而导致内存泄漏. + +## 阻塞/异步 + +> 如何判断接口是否异步? 是否只要有回调函数就是异步? + +开放性问题, 每个写 node 的人都有一套自己的判断方式. + +* 看文档 +* console.log 打印看看 +* 看是否有 IO 操作 + +单纯使用回调函数并不会异步, IO 操作才可能会异步, 除此之外还有使用 setTimeout 等方式实现异步. + +> 有这样一个场景, 你在线上使用 koa 搭建了一个网站, 这个网站项目中有一个你同事写的接口 A, 而 A 接口中在特殊情况下会变成死循环. 那么首先问题是, 如果触发了这个死循环, 会对网站造成什么影响? + +Node.js 中执行 js 代码的过程是单线程的. 只有当前代码都执行完, 才会切入事件循环, 然后从事件队列中 pop 出下一个回调函数开始执行代码. 所以 ① 实现一个 sleep 函数, 只要通过一个死循环就可以阻塞整个 js 的执行流程. (关于如何避免坑爹的同事写出死循环, 在后面的测试环节有写到.) + +> 如何实现一个 sleep 函数? ① + +```javascript +function sleep(ms) { + var start = Date.now(), expire = start + ms; + while (Date.now() < expire) ; + return; +} +``` + +而异步, 是使用 libuv 来实现的 (C/C++的同学可以参见 libev 和 libevent) 另一个线程里的事件队列. + +如果在线上的网站中出现了死循环的逻辑被触发, 整个进程就会一直卡在死循环中, 如果没有多进程部署的话, 之后的网站请求全部会超时, js 代码没有结束那么事件队列就会停下等待不会执行异步, 整个网站无法响应. + +> 如何实现一个异步的 reduce? (注:不是异步完了之后同步 reduce) + +需要了解 reduce 的情况, 是第 n 个与 n+1 的结果异步处理完之后, 在用新的结果与第 n+2 个元素继续依次异步下去. 不贴答案, 期待诸君的版本. + +## Timers + +在笔者这里将 Node.js 中的异步简单的划分为两种, 硬异步和软异步. + +硬异步是指由于 IO 操作或者外部调用走 libuv 而需要异步的情况. 当然, 也存在 readFileSync, execSync 等例外情况, 不过 node 由于是单线程的, 所以如果常规业务在普通时段执行可能比较耗时同步的 IO 操作会使得其执行过程中其他的所有操作都不能响应, 有点作死的感觉. 不过在启动/初始化以及一些工具脚本的应用场景下是完全没问题的. 而一般的场景下 IO 操作都是需要异步的. + +软异步是指, 通过 setTimeout 等方式来实现的异步. 关于 nextTick, setTimeout 以及 setImmediate 三者的区别参见[该帖](https://cnodejs.org/topic/5556efce7cabb7b45ee6bcac) + +**Event loop 示例** + +``` + ┌───────────────────────┐ +┌─>│ timers │ +│ └──────────┬────────────┘ +│ ┌──────────┴────────────┐ +│ │ I/O callbacks │ +│ └──────────┬────────────┘ +│ ┌──────────┴────────────┐ +│ │ idle, prepare │ +│ └──────────┬────────────┘ ┌───────────────┐ +│ ┌──────────┴────────────┐ │ incoming: │ +│ │ poll │<─────┤ connections, │ +│ └──────────┬────────────┘ │ data, etc. │ +│ ┌──────────┴────────────┐ └───────────────┘ +│ │ check │ +│ └──────────┬────────────┘ +│ ┌──────────┴────────────┐ +└──┤ close callbacks │ + └───────────────────────┘ +``` + +关于事件循环, Timers 以及 nextTick 的关系详见官方文档 [The Node.js Event Loop, Timers, and process.nextTick() (英文)](https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/) 以及阮一峰的 [JavaScript 运行机制详解:再谈Event Loop (中文)](http://www.ruanyifeng.com/blog/2014/10/event-loop.html) 等. + +## 并行/并发 + +并行 (Parallel) 与并发 (Concurrent) 是两个很常见的概念. + +可以看 Erlang 作者 Joe Armstrong 的博客 ([Concurrent and Parallel](http://joearms.github.io/2013/04/05/concurrent-and-parallel-programming.html)) + +![con_and_par](http://joearms.github.io/images/con_and_par.jpg) + +并发 (Concurrent) = 2 队列对应 1 咖啡机. + +并行 (Parallel) = 2 队列对应 2 咖啡机. + +Node.js 通过事件循环来挨个抽取实践队列中的一个个 Task 执行, 从而避免了传统的多线程情况下 `2个队列对应 1个咖啡机` 的时候上线文切换以及资源争抢/同步的问题, 所以获得了高并发的成就. + +至于在 node 中并行, 你可以通过 cluster 来再添加一个咖啡机. diff --git a/sections/zh-cn/io.md b/sections/zh-cn/io.md new file mode 100644 index 0000000..0c021b7 --- /dev/null +++ b/sections/zh-cn/io.md @@ -0,0 +1,386 @@ +# IO + +* [`[Doc]` Buffer](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#buffer) +* [`[Doc]` String Decoder (字符串解码)](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#string-decoder) +* [`[Doc]` Stream (流)](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#stream) +* [`[Doc]` Console (控制台)](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#console) +* [`[Doc]` File System (文件系统)](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#file) +* [`[Doc]` Readline](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#readline) +* [`[Doc]` REPL](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#repl) + +# 简述 + +Node.js 是以 IO 密集型业务著称. 那么问题来了, 你真的了解什么叫 IO, 什么又叫 IO 密集型业务吗? + +## Buffer + +Buffer 是 Node.js 中用于处理二进制数据的类, 其中与 IO 相关的操作 (网络/文件等) 均基于 Buffer. Buffer 类的实例非常类似整数数组, ***但其大小是固定不变的***, 并且其内存在 V8 堆栈外分配原始内存空间. Buffer 类的实例创建之后, 其所占用的内存大小就不能再进行调整. + +在 Node.js v6.x 之后 `new Buffer()` 接口开始被废弃, 理由是参数类型不同会返回不同类型的 Buffer 对象, 所以当开发者没有正确校验参数或没有正确初始化 Buffer 对象的内容时, 以及不了解的情况下初始化 就会在不经意间向代码中引入安全性和可靠性问题. + +接口|用途 +---|--- +Buffer.from()|根据已有数据生成一个 Buffer 对象 +Buffer.alloc()|创建一个初始化后的 Buffer 对象 +Buffer.allocUnsafe()|创建一个未初始化的 Buffer 对象 + +### TypedArray + +Node.js 的 Buffer 在 ES6 增加了 TypedArray 类型之后, 修改了原来的 Buffer 的实现, 选择基于 TypedArray 中 Uint8Array 来实现, 从而提升了一波性能. + +使用上, 你需要了解如下情况: + +```javascript +const arr = new Uint16Array(2); +arr[0] = 5000; +arr[1] = 4000; + +const buf1 = Buffer.from(arr); // 拷贝了该 buffer +const buf2 = Buffer.from(arr.buffer); // 与该数组共享了内存 + +console.log(buf1); +// 输出: , 拷贝的 buffer 只有两个元素 +console.log(buf2); +// 输出: + +arr[1] = 6000; +console.log(buf1); +// 输出: +console.log(buf2); +// 输出: +``` + +## String Decoder + +字符串解码器 (String Decoder) 是一个用于将 Buffer 拿来 decode 到 string 的模块, 是作为 Buffer.toString 的一个补充, 它支持多字节 UTF-8 和 UTF-16 字符. 例如 + +```javascript +const StringDecoder = require('string_decoder').StringDecoder; +const decoder = new StringDecoder('utf8'); + +const cent = Buffer.from([0xC2, 0xA2]); +console.log(decoder.write(cent)); // ¢ + +const euro = Buffer.from([0xE2, 0x82, 0xAC]); +console.log(decoder.write(euro)); // € +``` + +当然也可以断断续续的处理. + +```javascript +const StringDecoder = require('string_decoder').StringDecoder; +const decoder = new StringDecoder('utf8'); + +decoder.write(Buffer.from([0xE2])); +decoder.write(Buffer.from([0x82])); +console.log(decoder.end(Buffer.from([0xAC]))); // € +``` + +## Stream + +Node.js 内置的 `stream` 模块是多个核心模块的基础. 但是流 (stream) 是一种很早之前流行的编程方式. 可以用大家比较熟悉的 C语言来看这种流式操作: + +```c + +int copy(const char *src, const char *dest) +{ + FILE *fpSrc, *fpDest; + char buf[BUF_SIZE] = {0}; + int lenSrc, lenDest; + + // 打开要 src 的文件 + if ((fpSrc = fopen(src, "r")) == NULL) + { + printf("文件 '%s' 无法打开\n", src); + return FAILURE; + } + + // 打开 dest 的文件 + if ((fpDest = fopen(dest, "w")) == NULL) + { + printf("文件 '%s' 无法打开\n", dest); + fclose(fpSrc); + return FAILURE; + } + + // 从 src 中读取 BUF_SIZE 长的数据到 buf 中 + while ((lenSrc = fread(buf, 1, BUF_SIZE, fpSrc)) > 0) + { + // 将 buf 中的数据写入 dest 中 + if ((lenDest = fwrite(buf, 1, lenSrc, fpDest)) != lenSrc) + { + printf("写入文件 '%s' 失败\n", dest); + fclose(fpSrc); + fclose(fpDest); + return FAILURE; + } + // 写入成功后清空 buf + memset(buf, 0, BUF_SIZE); + } + + // 关闭文件 + fclose(fpSrc); + fclose(fpDest); + return SUCCESS; +} +``` + +应用的场景很简单, 你要拷贝一个 20G 大的文件, 如果你一次性将 20G 的数据读入到内存, 你的内存条可能不够用, 或者严重影响性能. 但是你如果使用一个 1MB 大小的缓存 (buf) 每次读取 1Mb, 然后写入 1Mb, 那么不论这个文件多大都只会占用 1Mb 的内存. + +而在 Node.js 中, 原理与上述 C 代码类似, 不过在读写的实现上通过 libuv 与 EventEmitter 加上了异步的特性. 在 linux/unix 中你可以通过 `|` 来感受到流式操作. + +### Stream 的类型 + + +类|使用场景|重写方法 +---|---|--- +[Readable](https://github.com/substack/stream-handbook#readable-streams)|只读|_read +[Writable](https://github.com/substack/stream-handbook#writable-streams)|只写|_write +[Duplex](https://github.com/substack/stream-handbook#duplex)|读写|_read, _write +[Transform](https://github.com/substack/stream-handbook#transform)|操作被写入数据, 然后读出结果|_transform, _flush + + +### 对象模式 + +通过 Node API 创建的流, 只能够对字符串或者 buffer 对象进行操作. 但其实流的实现是可以基于其他的 Javascript 类型(除了 null, 它在流中有特殊的含义)的. 这样的流就处在 "对象模式(objectMode)" 中. +在创建流对象的时候, 可以通过提供 `objectMode` 参数来生成对象模式的流. 试图将现有的流转换为对象模式是不安全的. + +### 缓冲区 + +Node.js 中 stream 的缓冲区, 以开头的 C语言 拷贝文件的代码为模板讨论, (抛开异步的区别看) 则是从 `src` 中读出数据到 `buf` 中后, 并没有直接写入 `dest` 中, 而是先放在一个比较大的缓冲区中, 等待写入(消费) `dest` 中. 即, 在缓冲区的帮助下可以使读与写的过程分离. + +Readable 和 Writable 流都会将数据储存在内部的缓冲区中. 缓冲区可以分别通过 `writable._writableState.getBuffer()` 和 `readable._readableState.buffer` 来访问. 缓冲区的大小, 由构造 stream 时候的 `highWaterMark` 标志指定可容纳的 byte 大小, 对于 `objectMode` 的 stream, 该标志表示可以容纳的对象个数. + +#### 可读流 + +当一个可读实例调用 `stream.push()` 方法的时候, 数据将会被推入缓冲区. 如果数据没有被消费, 即调用 `stream.read()` 方法读取的话, 那么数据会一直留在缓冲队列中. 当缓冲区中的数据到达 `highWaterMark` 指定的阈值, 可读流将停止从底层汲取数据, 直到当前缓冲的报备成功消耗为止. + +#### 可写流 + +在一个在可写实例上不停地调用 writable.write(chunk) 的时候数据会被写入可写流的缓冲区. 如果当前缓冲区的缓冲的数据量低于 `highWaterMark` 设定的值, 调用 writable.write() 方法会返回 true (表示数据已经写入缓冲区), 否则当缓冲的数据量达到了阈值, 数据无法写入缓冲区 write 方法会返回 false, 直到 drain 事件触发之后才能继续调用 write 写入. + +```javascript +// Write the data to the supplied writable stream one million times. +// Be attentive to back-pressure. +function writeOneMillionTimes(writer, data, encoding, callback) { + let i = 1000000; + write(); + function write() { + var ok = true; + do { + i--; + if (i === 0) { + // last time! + writer.write(data, encoding, callback); + } else { + // see if we should continue, or wait + // don't pass the callback, because we're not done yet. + ok = writer.write(data, encoding); + } + } while (i > 0 && ok); + if (i > 0) { + // had to stop early! + // write some more once it drains + writer.once('drain', write); + } + } +} +``` + +#### Duplex 与 Transform + +Duplex 流和 Transform 流都是同时可读写的, 他们会在内部维持两个缓冲区, 分别对应读取和写入, 这样就可以允许两边同时独立操作, 维持高效的数据流. 比如说 net.Socket 是一个 Duplex 流, Readable 端允许从 socket 获取、消耗数据, Writable 端允许向 socket 写入数据. 数据写入的速度很有可能与消耗的速度有差距, 所以两端可以独立操作和缓冲是很重要的. + +### pipe + +stream 的 `.pipe()`, 将一个可写流附到可读流上, 同时将可写流切换到流模式, 并把所有数据推给可写流. 在 pipe 传递数据的过程中, `objectMode` 是传递引用, 非 `objectMode` 则是拷贝一份数据传递下去. + +pipe 方法最主要的目的就是将数据的流动缓冲到一个可接受的水平, 不让不同速度的数据源之间的差异导致内存被占满. 关于 pipe 的实现参见 David Cai 的 [通过源码解析 Node.js 中导流(pipe)的实现](https://cnodejs.org/topic/56ba030271204e03637a3870) + +## Console + +[console.log 正常情况下是异步的, 除非你使用 `new Console(stdout[, stderr])` 指定了一个文件为目的地](https://nodejs.org/dist/latest-v6.x/docs/api/console.html#console_asynchronous_vs_synchronous_consoles). 不过一般情况下的实现都是如下 ([6.x 源代码](https://github.com/nodejs/node/blob/v6.x/lib/console.js#L42)): + +```javascript +// As of v8 5.0.71.32, the combination of rest param, template string +// and .apply(null, args) benchmarks consistently faster than using +// the spread operator when calling util.format. +Console.prototype.log = function(...args) { + this._stdout.write(`${util.format.apply(null, args)}\n`); +}; +``` + +自己实现一个 console.log 可以参考如下代码: + +```javascript +let print = (str) => process.stdout.write(str + '\n'); + +print('hello world'); +``` + +注意: 该代码并没有处理多参数, 也没有处理占位符 (即 util.format 的功能). + +### console.log.bind(console) 问题 + +```javascript +// 源码出处 https://github.com/nodejs/node/blob/v6.x/lib/console.js +function Console(stdout, stderr) { + // ... init ... + + // bind the prototype functions to this Console instance + var keys = Object.keys(Console.prototype); + for (var v = 0; v < keys.length; v++) { + var k = keys[v]; + this[k] = this[k].bind(this); + } +} +``` + +## File + +“一切皆是文件”是 Unix/Linux 的基本哲学之一, 不仅普通的文件、目录、字符设备、块设备、套接字等在 Unix/Linux 中都是以文件被对待, 也就是说这些资源的操作对象均为 fd (文件描述符), 都可以通过同一套 system call 来读写. 在 linux 中你可以通过 ulimit 来对 fd 资源进行一定程度的管理限制. + +Node.js 封装了标准 POSIX 文件 I/O 操作的集合. 通过 require('fs') 可以加载该模块. 该模块中的所有方法都有异步执行和同步执行两个版本. 你可以通过 fs.open 获得一个文件的文件描述符. + +### 编码 + +// TODO + +UTF8, GBK, es6 中对编码的支持, 如何计算一个汉字的长度 + +BOM + +### stdio + +stdio (standard input output) 标准的输入输出流, 即输入流 (stdin), 输出流 (stdout), 错误流 (stderr) 三者. 在 Node.js 中分别对应 `process.stdin` (Readable), `process.stdout` (Writable) 以及 `process.stderr` (Writable) 三个 stream. + +输出函数是每个人在学习任何一门编程语言时所需要学到的第一个函数. 例如 C语言的 `printf("hello, world!");` python/ruby 的 `print 'hello, world!'` 以及 Javascript 中的 `console.log('hello, world!');` + +以 C语言的伪代码来看的话, 这类输出函数的实现思路如下: + +```c +int printf(FILE *stream, 要打印的内容) +{ + // ... + + // 1. 申请一个临时内存空间 + char *s = malloc(4096); + + // 2. 处理好要打印的的内容, 其值存储在 s 中 + // ... + + // 3. 将 s 上的内容写入到 stream 中 + fwrite(s, stream); + + // 4. 释放临时空间 + free(s); + + // ... +} +``` + +我们需要了解的是第 3 步, 其中的 stream 则是指 stdout (输出流). 实际上在 shell 上运行一个应用程序的时候, shell 做的第一个操作是 fork 当前 shell 的进程 (所以, 如果你通过 ps 去查看你从 shell 上启动的进程, 其父进程 pid 就是当前 shell 的 pid), 在这个过程中也把 shell 的 stdio 继承给了你当前的应用进程, 所以你在当前进程里面将数据写入到 stdout, 也就是写入到了 shell 的 stdout, 即在当前 shell 上显示了. + +输入也是同理, 当前进程继承了 shell 的 stdin, 所以当你从 stdin 中读取数据时, 其实就获取到你在 shell 上输入的数据. (PS: shell 可以是 windows 下的 cmd, powershell, 也可以是 linux 下 bash 或者 zsh 等) + +当你使用 ssh 在远程服务器上运行一个命令的时候, 在服务器上的命令输出虽然也是写入到服务器上 shell 的 stdout, 但是这个远程的 shell 是从 sshd 服务上 fork 出来的, 其 stdout 是继承自 sshd 的一个 fd, 这个 fd 其实是个 socket, 所以最终其实是写入到了一个 socket 中, 通过这个 socket 传输你本地的计算机上的 shell 的 stdout. + +如果你理解了上述情况, 那么你也就能理解为什么守护进程需要关闭 stdio, 如果切到后台的守护进程没有关闭 stdio 的话, 那么你在用 shell 操作的过程中, 屏幕上会莫名其妙的多出来一些输出. 此处对应[守护进程](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#守护进程)的 C 实现中的这一段: + +```c +for (; i < getdtablesize(); ++i) { + close(i); // 关闭打开的 fd +} +``` + +Linux/unix 的 fd 都被设计为整型数字, 从 0 开始. 你可以尝试运行如下代码查看. + +``` +console.log(process.stdin.fd); // 0 +console.log(process.stdout.fd); // 1 +console.log(process.stderr.fd); // 2 +``` + +在上一节中的 [在 IPC 通道建立之前, 父进程与子进程是怎么通信的? 如果没有通信, 那 IPC 是怎么建立的?](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#q-child) 中使用环境变量传递 fd 的方法, 这么看起来就很直白了, 因为传递 fd 其实是直接传递了一个整型数字. + +### 如何同步的获取用户的输入? + +如果你理解了上述的内容, 那么放到 Node.js 中来看, 获取用户的输入其实就是读取 Node.js 进程中的输入流 (即 process.stdin 这个 stream) 的数据. + +而要同步读取, 则是不用异步的 read 接口, 而是用同步的 readSync 接口去读取 stdin 的数据即可实现. 以下来自万能的 stackoverflow: + +```javascript +/* + * http://stackoverflow.com/questions/3430939/node-js-readsync-from-stdin + * @mklement0 + */ +var fs = require('fs'); + +var BUFSIZE = 256; +var buf = new Buffer(BUFSIZE); +var bytesRead; + +module.exports = function() { + var fd = ('win32' === process.platform) ? process.stdin.fd : fs.openSync('/dev/stdin', 'rs'); + bytesRead = 0; + + try { + bytesRead = fs.readSync(fd, buf, 0, BUFSIZE); + } catch (e) { + if (e.code === 'EAGAIN') { // 'resource temporarily unavailable' + // Happens on OS X 10.8.3 (not Windows 7!), if there's no + // stdin input - typically when invoking a script without any + // input (for interactive stdin input). + // If you were to just continue, you'd create a tight loop. + console.error('ERROR: interactive stdin input not supported.'); + process.exit(1); + } else if (e.code === 'EOF') { + // Happens on Windows 7, but not OS X 10.8.3: + // simply signals the end of *piped* stdin input. + return ''; + } + throw e; // unexpected exception + } + + if (bytesRead === 0) { + // No more stdin input available. + // OS X 10.8.3: regardless of input method, this is how the end + // of input is signaled. + // Windows 7: this is how the end of input is signaled for + // *interactive* stdin input. + return ''; + } + // Process the chunk read. + + var content = buf.toString(null, 0, bytesRead - 1); + + return content; +}; +``` + +## Readline + +`readline` 模块提供了一个用于从 Readble 的 stream (例如 process.stdin) 中一次读取一行的接口. 当然你也可以用来读取文件或者 net, http 的 stream, 比如: + +```javascript +const readline = require('readline'); +const fs = require('fs'); + +const rl = readline.createInterface({ + input: fs.createReadStream('sample.txt') +}); + +rl.on('line', (line) => { + console.log(`Line from file: ${line}`); +}); +``` + +实现上, realine 在读取 TTY 的数据时, 是通过 `input.on('keypress', onkeypress)` 时发现用户按下了回车键来判断是新的 line 的, 而读取一般的 stream 时, 则是通过缓存数据然后用正则 .test 来判断是否为 new line 的. + +PS: 打个广告, 如果在编写脚本时, 不习惯这样异步获取输入, 想要同步获取同步的用户输入可以看一看这个 Node.js 版本类 C语言使用的 [scanf](https://github.com/Lellansin/node-scanf/) 模块 (支持 ts). + +## REPL + +Read-Eval-Print-Loop (REPL) + +整理中 diff --git a/sections/zh-cn/js-basic.md b/sections/zh-cn/js-basic.md new file mode 100644 index 0000000..b778d42 --- /dev/null +++ b/sections/zh-cn/js-basic.md @@ -0,0 +1,126 @@ +# Javascript 基础问题 + +* [`[Basic]` 类型判断](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#类型判断) +* [`[Basic]` 作用域](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#作用域) +* [`[Basic]` 引用传递](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#引用传递) +* [`[Basic]` 内存释放](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#内存释放) +* [`[Basic]` ES6 新特性](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#es6-新特性) + + +## 简述 + +与前端 Js 不同, 后端方面除了SSR/爬虫之外很少会接触 DOM, 所以关于 DOM 方面的各种知识基本不会讨论. 前端很少碰到内存问题, 但是后端几乎是直面服务器内存的, 更加偏向内存方面, 对于一些更基础的问题也会更加关注. + +不过由于 Js 方面的知识点是在太多, 《Javascript 权威指南》的厚度完全可以说明问题, 所以本教程并不会完整的带大家过一遍 Js 的基础问题, 只是简单列举一些饿了么在面试 Node.js 程序的时候通常会问的一些 Js 基础问题, 有的详细的地方会直接留下书名或者博文链接, 以供大家深入了解, 这里就不赘述了. + +> 希望大家更多的是带着本文抛出的问题去学习, 而不是期待本文把所有答案列出来. + +## 类型判断 + +Javascript 的类型判断其实是个挺折磨人的话题, 不然也不会有 Typescript 出现了. 在类型判断的问题上, 基础上 推荐阅读 [lodash](https://github.com/lodash/lodash) 的源代码. + +这类问题一般只是简单的开场, 不会因为说你不知道 `undefined == null` 的结果是 `true` 就一票否决一个人. 只是根据个人经验看来,这个问题答不清楚的有不小的概率属于基础较差. 如果你对这种问题没有任何概念, 也许要反思一下是不是该找本书过一下 Js 的基础了. + +另外在这个问题上, 对使用 TypeScript 以及 flow 同学会有一定的加分. + +## 作用域 + +在面试时, 作用域并不是一个很好问的知识点, 一般会问的是 `es6 中 let 与 var 的区别`, 或者列举代码, 然后通过对代码的解读来看你对作用域的掌握比较方便. + +印象中那本 [《你不知道的 Javascript》](https://book.douban.com/subject/26351021/) 讲的很好了, 有兴趣可以去看那本书, 以下是该书的部分目录: + +* 第1章 作用域是什么 +* 第2章 词法作用域 +* 第3章 函数作用域和块作用域 +* 第4章 提升 +* 第5章 作用域闭包 +* ... + +## 引用传递 + +> js 中什么类型是引用传递, 什么类型是值传递? 如何将值类型的变量以引用的方式传递? + +简单点说, 对象是引用传递, 基础类型是值传递, 通过将基础类型包装 (boxing) 可以以引用的方式传递.(复杂见注①) + +引用传递和值传递是一个非常简单的问题, 也是理解 Javascript 中的内存方面问题的一个基础. 如果不了解引用可能很难去看很多问题. + +面试写代码的话, 可以通过 `如何编写一个 json 对象的拷贝函数` 等类似的问题来考察对引用的了解. +不过笔者偶尔会有恶趣味, 喜欢先问应聘者对于 `==` 的 `===` 的区别的了解. 然后再问 `[1] == [1]` 是 `true` 还是 `false`. 如果基础不好的同学可能会被自己对于 `==` 和 `===` 的结论影响然后得出错误的结论. + +注①: 对于技术好的, 希望能直接反驳这个问题本身是有问题的, 比如讲清楚 Javascript 中没有引用传递只是传递引用. 参见 [Is JavaScript a pass-by-reference or pass-by-value language?](http://stackoverflow.com/questions/518000/is-javascript-a-pass-by-reference-or-pass-by-value-language). 虽然说是复杂版, 但是这些知识对于 3年经验的同学真的应该是很简单的问题了. + +另外如果简历中有写 C++, 则必问 `指针与引用的区别`. + +## 内存释放 + +> Javascript 中不同类型以及不同环境下变量的内存都是何时释放? + +引用类型是在没有引用之后, 通过 v8 的 GC 自动回收, 值类型如果是处于闭包的情况下, 要等闭包没有引用才会被 GC 回收, 非闭包的情况下等待 v8 的新生代 (new space) 切换的时候回收. + +与前端 Js 不同, 2年以上经验的 Node.js 一定要开始注意内存了, 不说对 v8 的 GC 有多了解, 基础的内存释放一定有概念了, 并且要开始注意内存泄漏的问题了. + +你需要了解哪些操作一定会导致内存泄漏, 或者可以崩掉内存. 比如如下代码能否爆掉 V8 的内存? + +```javascript +let arr = []; +while(true) + arr.push(1); +``` + +然后上述代码与下方的情况有什么区别? + +```javascript +let arr = []; +while(true) + arr.push(); +``` + +如果 push 的是 `Buffer` 情况又会有什么区别? + +```javascript +let arr = []; +while(true) + arr.push(new Buffer(1000)); +``` + +思考完之后可以尝试找找别的情况如何爆掉 V8 的内存. 以及来聊聊内存泄漏? + +```javascript +var theThing = null +var replaceThing = function () { + var originalThing = theThing + var unused = function () { + if (originalThing) + console.log("hi") + } + theThing = { + longStr: new Array(1000000).join('*'), + someMethod: function () { + console.log(someMessage) + } + }; +}; +setInterval(replaceThing, 1000) +``` + +比如上述情况中 `unused` 的函数中持有了 `originalThing` 的引用, 使得每次旧的对象不会释放从而导致内存泄漏 (例子出自[《Node.js 垃圾回收》](https://eggggger.xyz/2016/10/22/node-gc/)) + +当然对于一些高水平的同学, 要求能清楚的了解 v8 内存 GC 的机制, 懂得内存快照等 (之后会在`调试/优化`的小结中讨论) 了. 比如 V8 中不同类型的数据存储的位置, 在内存释放的时候不同区域的不同策略等等. + +## ES6 新特性 + +推荐阅读阮一峰的 [《ECMAScript 6 入门》](http://es6.ruanyifeng.com/) + +比较简单的会问 `let` 与 `var` 的区别, 以及 `箭头函数` 与 `function` 的区别等等. + +深入的话, es6 有太多细节可以深入了. 比如结合 `引用` 的知识点来询问 `const` 方面的知识. 结合 `{}` 的使用与缺点来谈 `Set, Map` 等. 比如私有化的问题与 `symbol` 等等. + +其他像是 `闭包是什么?` 这种问烂了问题已经感觉没必要问了, 取而代之的是询问闭包应用的场景更加合理. 比如说, 如果回答者通常使用闭包实现数据的私有, 那么可以接着问 es6 的一些新特性 (例如 `class`, `symbol`) 能否实现私有, 如果能的话那为什么要用闭包? 亦或者是什么闭包中的数据/私有化的数据的内存什么时候释放? 等等. + +`...` 的使用上, 如何实现一个数组的去重 (使用 Set 可以加分). + +> const 定义的 Array 中间元素能否被修改? 如果可以, 那 const 修饰对象有什么意义? + +其中的值可以被修改. 意义上, 主要保护引用不被修改 (如用 [Map](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map) 等接口对引用的变化很敏感, 使用 const 保护引用始终如一是有意义的), 也适合用在 immutable 的场景. + +暂时写上这些, 之后会慢慢整理, 如果内容比较多可能单独归一类来讨论. diff --git a/sections/zh-cn/module.md b/sections/zh-cn/module.md new file mode 100644 index 0000000..d7d69b8 --- /dev/null +++ b/sections/zh-cn/module.md @@ -0,0 +1,134 @@ +# 模块 + +* [`[Basic]` 模块机制](#模块机制) +* [`[Basic]` 热更新](#热更新) +* [`[Basic]` 上下文](#上下文) +* [`[Basic]` 包管理](#包管理) + +## 常见问题 + + +> 如何在不重启 node 进程的情况下热更新一个 js/json 文件? 这个问题本身是否有问题? + +可以清除掉 `require.cache` 的缓存重新 `require(xxx)`, 视具体情况还可以用 VM 模块重新执行. + +当然这个问题可能是典型的 [`X-Y Problem`](http://coolshell.cn/articles/10804.html), 使用 js 实现热更新很容易碰到 v8 优化之后各地拿到缓存的引用导致热更新 js 没意义. 当然热更新 json 还是可以简单一点比如用读取文件的方式来热更新, 但是这样也不如从 redis 之类的数据库中读取比较合理. + +## 简述 + +其他还有很多内容也是属于很 '基础' 的 Node.js 问题 (例如异步/线程等等), 但是由于归类的问题并没有放在这个分类中. 所以这里只简单讲几个之后没归类的基础问题. + + +## 模块机制 + +node 的基础中毫无疑问的应该是有关于模块机制的方面的, 也即 `require` 这个内置功能的一些原理的问题. + +关于模块互相引用之类的, 不了解的推荐先好好读读[官方文档](https://nodejs.org/dist/latest-v6.x/docs/api/modules.html). + +其实官方文档已经说得很清楚了, 每个 node 进程只有一个 VM 的上下文, 不会跟浏览器相差多少, 模块机制在文档中也描述的非常清楚了: + +```javascript +function require(...) { + var module = { exports: {} }; + ((module, exports) => { + // Your module code here. In this example, define a function. + function some_func() {}; + exports = some_func; + // At this point, exports is no longer a shortcut to module.exports, and + // this module will still export an empty default object. + module.exports = some_func; + // At this point, the module will now export some_func, instead of the + // default object. + })(module, module.exports); + return module.exports; +} +``` + +> 如果 a.js require 了 b.js, 那么在 b 中定义全局变量 `t = 111` 能否在 a 中直接打印出来? + +① 每个 `.js` 能独立一个环境只是因为 node 帮你在外层包了一圈自执行, 所以你使用 `t = 111` 定义全局变量在其他地方当然能拿到. 情况如下: + +```javascript + +// b.js +(function (exports, require, module, __filename, __dirname) { + t = 111; +})(); + +// a.js +(function (exports, require, module, __filename, __dirname) { + // ... + console.log(t); // 111 +})(); +``` + +> a.js 和 b.js 两个文件互相 require 是否会死循环? 双方是否能导出变量? 如何从设计上避免这种问题? + +② 不会, 先执行的导出空对象, 通过导出工厂函数让对方从函数去拿比较好避免. 模块在导出的只是 `var module = { exports: {} };` 中的 exports, 以从 a.js 启动为例, a.js 还没执行完 exports 就是 `{}` 在 b.js 的开头拿到的就是 `{}` 而已. + +另外还有非常基础和常见的问题, 比如 module.exports 和 exports 的区别这里也能一并解决了 exports 只是 module.exports 的一个引用. 没看懂可以在细看我以前发的[帖子](https://cnodejs.org/topic/5734017ac3e4ef7657ab1215). + +再晋级一点, 众所周知, node 的模块机制是基于 [`CommonJS`](http://javascript.ruanyifeng.com/nodejs/module.html) 规范的. 对于从前端转 node 的同学, 如果面试官想问的难一点会考验关于 [`CommonJS`](http://javascript.ruanyifeng.com/nodejs/module.html) 的一些问题. 比如比较 `AMD`, `CMD`, [`CommonJS`](http://javascript.ruanyifeng.com/nodejs/module.html) 三者的区别, 包括询问关于 node 中 `require` 的实现原理等. + +## 热更新 + +从面试官的角度看, `热更新` 是很多程序常见的问题. 对客户端而言, 热更新意味着不用换包, 当然也包含着 md5 校验/差异更新等复杂问题; 对服务端而言, 热更新意味着服务不用重启, 这样可用性较高同时也优雅和有逼格. 问的过程中可以一定程度的暴露应聘程序员的水平. + +从 PHP 转 node 的同学可能会有些想法, 比如 PHP 的代码直接刷上去就好了, 并没有所谓的重启. 而 node 重启看起来动作还挺大. 当然这里面的区别, 主要是与同时有 PHP 与 node 开发经验的同学可以讨论, 也是很好的切入点. + +在 Node.js 中做热更新代码, 牵扯到的知识点可能主要是 `require` 会有一个 `cache`, 有这个 `cache` 在, 即使你更新了 `.js` 文件, 在代码中再次 `require` 还是会拿到之前的编译好缓存在 v8 内存 (code space) 中的的旧代码. 但是如果只是单纯的清除掉 `require` 中的 `cache`, 再次 `require` 确实能拿到新的代码, 但是这时候很容易碰到各地维持旧的引用依旧跑的旧的代码的问题. 如果还要继续推行这种热更新代码的话, 可能要推翻当前的架构, 从头开始从新设计一下目前的框架. + +不过热更新 json 之类的配置文件的话, 还是可以简单的实现的, 更新 `require` 的 `cache` 可以实现, 不会有持有旧引用的问题, 可以参见我 2 年前写着玩的[例子](https://www.npmjs.com/package/auto-reload), 但是如果旧的引用一直被持有很容易出现内存泄漏, 而要热更新配置的话, 为什么不存数据库? 或者用 `zookeeper` 之类的服务? 通过更新文件还要再发布一次, 但是存数据库直接写个接口配个界面多爽你说是不是? + +所以这个问题其实本身其实是值得商榷的, 可能是典型的 [`X-Y Problem`](http://coolshell.cn/articles/10804.html), 不过聊起来确实是可以暴露水平. + +## 上下文 + +如果你已经了解 ①② 那么你也应该了解, 对于 Node.js 而言, 正常情况下只有一个上下文, 甚至于内置的很多方面例如 `require` 的实现只是在启动的时候运行了[内置的函数](https://github.com/nodejs/node/tree/master/lib). + +每个单独的 `.js` 文件并不意味着单独的上下文, 在某个 `.js` 文件中污染了全局的作用域一样能影响到其他的地方. + +而目前的 Node.js 将 VM 的接口暴露了出来, 可以让你自己创建一个新的 js 上下文, 这一点上跟前端 js 还是区别挺大的. 在执行外部代码的时候, 通过创建新的上下文沙盒 (sandbox) 可以避免上下文被污染: + +```javascript +'use strict'; +const vm = require('vm'); + +let code = +`(function(require) { + + const http = require('http'); + + http.createServer( (request, response) => { + response.writeHead(200, {'Content-Type': 'text/plain'}); + response.end('Hello World\\n'); + }).listen(8124); + + console.log('Server running at http://127.0.0.1:8124/'); +})`; + +vm.runInThisContext(code)(require); +``` + +这种执行方式与 eval 和 Function 有明显的区别. 关于 VM 更多的一些接口可以先阅读[官方文档 VM (虚拟机)](https://nodejs.org/dist/latest-v6.x/docs/api/vm.html) + +讲完这个知识点, 这里留下一个简单的问题, 既然可以通过新的上下文来避免污染, 那么`为什么 Node.js 不给每一个 `.js` 文件以独立的上下文来避免作用域被污染?` (反应不过来的同学还是别投简历了, 微笑脸) + + +## 包管理 + + +整理中... + +为什么我装了全局, 但是提示我 not found + +npm +yarn + +锁版本 + +lerna:一个用户管理多个包模块的工具。 + +left-pad事件 + +greenkeeper 等 diff --git a/sections/zh-cn/network.md b/sections/zh-cn/network.md new file mode 100644 index 0000000..e8f1605 --- /dev/null +++ b/sections/zh-cn/network.md @@ -0,0 +1,334 @@ +# Network + +* [`[Doc]` Net (网络)](#net) +* [`[Doc]` UDP/Datagram](#udp) +* [`[Doc]` HTTP](#http) +* [`[Doc]` DNS (域名服务器)](#dns) +* [`[Doc]` ZLIB (压缩)](#zlib) +* [`[Point]` RPC](#rpc) + + +## Net + +目前互联化的核心是建立在 TCP/IP 协议的基础上的, 这些协议将数据分割成小的数据包进行传输, 并且解决传输过程中各种各样复杂的问题. 关于协议的具体细节推荐阅读 W.Richard Stevens 的[《TCP/IP 详解 卷1:协议》](https://www.amazon.cn/TCP-IP%E8%AF%A6%E8%A7%A3%E5%8D%B71-%E5%8D%8F%E8%AE%AE-W-Richard-Stevens/dp/B00116OTVS/), 本文不做赘述, 只是列举一些常见的知识点, 新人推荐看[《图解TCP/IP》](https://www.amazon.cn/%E5%9B%BE%E4%B9%A6/dp/B00DMS9990/), 抓包工具推荐看[《Wireshark网络分析就这么简单》](https://www.amazon.cn/%E5%9B%BE%E4%B9%A6/dp/B00PB5QQ84/). + +### 粘包 + +默认情况下, TCP 连接会启用延迟传送算法 (Nagle 算法), 在数据发送之前缓存他们. 如果短时间有多个数据发送, 会缓冲到一起作一次发送 (缓冲大小见 `socket.bufferSize`), 这样可以减少 IO 消耗提高性能. + +如果是传输文件的话, 那么根本不用处理粘包的问题, 来一个包拼一个包就好了. 但是如果是多条消息, 或者是别的用途的数据那么久需要处理粘包. + +可以参见网上流传比较广的一个例子, 连续调用两次 send 分别发送两段数据 data1 和 data2, 在接收端有以下几种常见的情况: + +* A. 先接收到 data1, 然后接收到 data2 . +* B. 先接收到 data1 的部分数据, 然后接收到 data1 余下的部分以及 data2 的全部. +* C. 先接收到了 data1 的全部数据和 data2 的部分数据, 然后接收到了 data2 的余下的数据. +* D. 一次性接收到了 data1 和 data2 的全部数据. + +其中的 BCD 就是我们常见的粘包的情况. 而对于处理粘包的问题, 常见的解决方案有: + +* 1. 多次发送之前间隔一个等待时间 +* 2. 关闭 Nagle 算法 +* 3. 进行封包/拆包 + +***方案1*** + +只需要等上一段时间再进行下一次 send 就好, 适用于交互频率特别低的场景. 缺点也很明显, 对于比较频繁的场景而言传输效率实在太低. 不过几乎用做什么处理. + +***方案2*** + +关闭 Nagle 算法, 在 Node.js 中你可以通过 [`socket.setNoDelay()`](https://nodejs.org/dist/latest-v6.x/docs/api/net.html#net_socket_setnodelay_nodelay) 方法来关闭 Nagle 算法, 让每一次 send 都不缓冲直接发送. + +该方法比较适用于每次发送的数据都比较大 (但不是文件那么大), 并且频率不是特别高的场景. 如果是每次发送的数据量比较小, 并且频率特别高的, 关闭 Nagle 纯属自废武功. + +另外, 该方法不适用于网络较差的情况, 因为 Nagle 算法是在服务端进行的包合并情况, 但是如果短时间内客户端的网络情况不好, 或者应用层由于某些原因不能及时将 TCP 的数据 recv, 就会造成多个包在客户端缓冲从而粘包的情况. (如果是在稳定的机房内部通信那么这个概率是比较小可以选择忽略的) + +***方案3*** + +封包/拆包是目前业内常见的解决方案了. 即给每个数据包在发送之前, 于其前/后放一些有特征的数据, 然后收到数据的时候根据特征数据分割出来各个数据包. + +### 可靠传输 + +为每一个发送的数据包分配一个序列号(SYN, Synchronise packet), 每一个包在对方收到后要返回一个对应的应答数据包(ACK, Acknowledgedgement),. 发送方如果发现某个包没有被对方 ACK, 则会选择重发. 接收方通过 SYN 序号来保证数据的不会乱序(reordering), 发送方通过 ACK 来保证数据不缺漏, 以此参考决定是否重传. 关于具体的序号计算, 丢包时的重传机制等可以参见阅读陈皓的 [《TCP的那些事儿(上)》](http://coolshell.cn/articles/11564.html) 此处不做赘述. + +### window + +TCP 头里有一个 Window 字段, 是接收端告诉发送端自己还有多少缓冲区可以接收数据的. 发送端就可以根据接收端的处理能力来发送数据, 从而避免接收端处理不过来. 详细参见陈皓的 [《TCP的那些事儿(下)》](http://coolshell.cn/articles/11609.html) + +> window 是否设置的越大越好? + +类似木桶理论, 一个木桶能装多少水, 是由最短的那块木板决定的. 一个 TCP 连接的 window 是由该连接中间一连串设备中 window 最小的那一个设备决定的. + +### backlog + +![图片出处 http://www.cnxct.com/something-about-phpfpm-s-backlog/](/assets/socket-backlog.png) + +关于该 backlog 的定义参见 [man](https://linux.die.net/man/2/listen) 手册: + +> The behavior of the backlog argument on TCP sockets changed with Linux 2.2. Now it specifies the queue length for completely established sockets waiting to be accepted, instead of the number of incomplete connection requests. + +backlog 用于设置客户端与服务端 `ESTABLISHED` 之后等待 accept 的队列长图 (如上图中的 accept queue). 如果 backlog 过小, 在并发连接大的情况下容易导致 accept queue 装满之后断开连接. 但是如果将这个队列设置的特别大, 那么假定连接数并发量是 65525, 以 php-fpm 的 qps 5000 为例, 处理完约耗时 13s, 而这段时间中连接可能早已被 nginx 或者客户端断开, 那么我们去 accept 这个 socket 时只会拿到一个 broken pipe (该例子出处见 [PHP 源码 Set FPM_BACKLOG_DEFAULT to 511](https://github.com/php/php-src/commit/ebf4ffc9354f316f19c839a114b26a564033708a)). 经过我也不懂的计算 backlog 的长度默认是 511. + +另外提一句, 这个 backlog 是通过系统指定时是通过 `somaxconn` 参数来指定 accept queue 的. 而 `tcp_max_syn_backlog` 参数指定的是 SYN queue 的长度. + +### 状态机 + +![tcpfsm.png](/assets/tcpfsm.png) + +关于网络连接的建立以及断开, 存在着一个复杂的状态转换机制, 完整的状态表参见 [《The TCP/IP Guide》](http://www.tcpipguide.com/free/t_TCPOperationalOverviewandtheTCPFiniteStateMachineF-2.htm) + +state|简述 +-----|--- +CLOSED|连接关闭, 所有连接的初始状态 +LISTEN|监听状态, 等待客户端发送 SYN +SYN-SENT|客户端发送了 SYN, 等待服务端回复 +SYN-RECEIVED|双方都收到了 SYN, 等待 ACK +ESTABLISHED| SYN-RECEIVED 收到 ACK 之后, 状态切换为连接已建立. +CLOSE-WAIT|被动方收到了关闭请求(FIN)后, 发送 ACK, 如果有数据要发送, 则发送数据, 无数据发送则回复 FIN. 状态切换到 LAST-ACK +LAST-ACK|等待对方 ACK 当前设备的 CLOSE-WAIT 时发送的 FIN, 等到则切换 CLOSED +FIN-WAIT-1|主动方发送 FIN, 等待 ACK +FIN-WAIT-2|主动方收到被动方的 ACK, 等待 FIN +CLOSING|主动方收到了FIN, 却没收到 FIN-WAIT-1 时发的 ACK, 此时等待那个 ACK +TIME-WAIT|主动方收到 FIN, 返回收到对方 FIN 的 ACK, 等待对方是否真的收到了 ACK, 如果过一会又来一个 FIN, 表示对方没收到, 这时要再 ACK 一次 + +> `TIME_WAIT` 是什么情况? 出现过多的 `TIME_WAIT` 可能是什么原因? + +`TIME_WAIT` 是连接的某一方 (可能是服务端也可能是客户端) 主动断开连接时, 四次挥手等待被断开的一方是否收到最后一次挥手 (ACK) 的状态. 如果在等待时间中, 再次收到第三次挥手 (FIN) 表示对方没收到最后一次挥手, 这时要再 ACK 一次. 这个等待的作用是避免出现连接混用的情况 (`prevent potential overlap with new connections` see [TCP Connection Termination](http://www.tcpipguide.com/free/t_TCPConnectionTermination.htm) for more). + +出现大量的 `TIME_WAIT` 比较常见的情况是, 并发量大, 服务器在短时间断开了大量连接. 对应 HTTP server 的情况可能是没开启 `keepAlive`. 如果有开 `keepAlive`, 一般是等待客户端自己主动断开, 那么`TIME_WAIT` 就只存在客户端, 而服务端则是 `CLOSE_WAIT` 的状态, 如果服务端出现大量 `CLOSE_WAIT`, 意味着当前服务端建立的连接大面积的被断开, 可能是目标服务集群重启之类. + + +## UDP + +> TCP/UDP 的区别? UDP 有粘包吗? + +协议|连接性|双工性|可靠性|有序性|有界性|拥塞控制|传输速度|量级|头部大小 +---|---|---|---|---|---|---|---|---|--- +TCP|面向连接
(Connection oriented)|全双工(1:1)|可靠
(重传机制)|有序
(通过SYN排序)|无, 有[粘包情况](#粘包)|有|慢|低|20~60字节 +UDP|无连接
(Connection less)|n:m|不可靠
(丢包后数据丢失)|无序|有消息边界, **无粘包**|无|快|高|8字节 + +UDP socket 支持 n 对 m 的连接状态, 在[官方文档](https://nodejs.org/dist/latest-v6.x/docs/api/dgram.html)中有写到在 `dgram.createSocket(options[, callback])` 中的 option 可以指定 `reuseAddr` 即 `SO_REUSEADDR`标志. 通过 `SO_REUSEADDR` 可以简单的实现 n 对 m 的多播特性 (不过仅在支持多播的系统上才有). + + +### 常见的应用场景 + + + + + + + + + + + + + + + +
传输层协议应用应用层协议
TCP电子邮件SMTP
终端连接TELNET
终端连接SSH
万维网HTTP
文件传输FTP
UDP域名解析DNS
简单文件传输TFTP
网络时间校对NTP
网络文件系统NFS
路由选择RIP
IP电话-
流式多媒体通信-
+ +简单的说, UDP 速度快, 开销低, 不用封包/拆包允许丢一部分数据, 监控统计/日志数据上报/流媒体通信等场景都可以用 UDP. 目前 Node.js 的项目中使用 UDP 比较流行的是 [StatsD](https://github.com/etsy/statsd) 监控服务. + + +## HTTP + +目前世界上运行最良好的分布式集群, 莫过于当前的万维网了 (http servers) 了. 目前前端工程师也都是靠 HTTP 协议吃饭的, 所以 2-3 年的前端同学都应该对 HTTP 有比较深的理解了, 所以这里不做太多的赘述. 推荐书籍[《图解HTTP》](https://www.amazon.cn/%E5%9B%BE%E4%B9%A6/dp/B00JTQK1L4/), 博客[HTTP 协议入门](http://www.ruanyifeng.com/blog/2016/08/http.html). + +另外最近几年开始大家对 HTTP 的面试的考察也渐渐偏向[理解 RESTful 架构](http://www.ruanyifeng.com/blog/2011/09/restful.html). 简单的说, RESTful 是把每个 URI 当做资源 (Resources), 通过 method 作为动词来对资源做不同的动作, 然后服务器返回 status 来得知资源状态的变化 (State Transfer); + +### method/status + +因为 HTTP 的方法 (method) 与状态码 (status) 讲解太常见, 你可以使用如下代码打印出来自己看 Node.js 官方定义的, 完整的就不列举了. + +```javascript +const http = require('http'); + +console.log(http.METHODS); +console.log(http.STATUS_CODES); +``` + +一个常见的 method 列表, 关于这些 method 在 RESTful 中的一些应用的详细可以参见[Using HTTP Methods for RESTful Services](http://www.restapitutorial.com/lessons/httpmethods.html) + +methods|CRUD|幂等|缓存 +---|---|---|--- +GET|Read|✓|✓ +POST|Create|| +PUT|Update/Replace|✓ +PATCH|Update/Modify|✓ +DELETE|Delete|✓ + +> GET 和 POST 有什么区别? + +网上有很多讲这个的, 比如从书签, url 等前端的角度去看他们的区别这里不赘述. 而从后端的角度看, 前两年出来一个 《GET 和 POST 没有区别》(出处不好考究, 就没贴了) 的文章比较有名, 早在我刚学 PHP 的时候也有过这种疑惑, 刚学 Node 的时候发现不能像 PHP 那样同时处理 GET 和 POST 的时候还很不适应. 后来接触 RESTful 才意识到, 这两个东西最根本的差别是语义, 引申了看, 协议 (protocol) 这种东西就是人与人之间协商的约定, 什么行为是什么作用都是"约定"好的, 而不是强制使用的, 非要把 GET 当 POST 这样不遵守约定的做法我们也爱莫能助. + +跑题了, 简而言之, 讨论这二者的区别最好从 RESTful 提倡的语义角度来讲比较符合当代程序员的逼格比较合理. + +> POST 和 PUT 有什么区别? + +POST 是新建 (create) 资源, 非幂等, 同一个请求如果重复 POST 会新建多个资源. PUT 是 Update/Replace, 幂等, 同一个 PUT 请求重复操作会得到同样的结果. + + +### headers + +HTTP headers 是在进行 HTTP 请求的交互过程中互相支会对方一些信息的主要字段. 比如请求 (Request) 的时候告诉服务端自己能接受的各项参数, 以及之前就存在本地的一些数据等. 详细各位可以参见 wikipedia: + +* [Request fields](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields) +* [Response fields](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Response_fields) + +> cookie 与 session 的区别? 服务端如何清除 cookie? + +主要区别在于, session 存在服务端, cookie 存在客户端. session 比 cookie 更安全. 而且 cookie 不一定一直能用 (可能被浏览器关掉). 服务端可以通过设置 cookie 的值为空并设置一个及时的 expires 来清除存在客户端上的 cookie. + +> 什么是跨域请求? 如何允许跨域? + +出于安全考虑, 默认情况下使用 XMLHttpRequest 和 Fetch 发起 HTTP 请求必须遵守同源策略, 即只能向相同域名请求. 向不同域名的请求被称作跨域请求 (cross-origin HTTP request). 可以通过设置 [CORS headers](https://developer.mozilla.org/en-US/docs/Glossary/CORS) 即 `Access-Control-Allow-` 系列来允许跨域. 例如: + +``` +location ~* ^/(?:v1|_) { + if ($request_method = OPTIONS) { return 200 ''; } + header_filter_by_lua ' + ngx.header["Access-Control-Allow-Origin"] = ngx.var.http_origin; # 这样相当于允许所有来源了 + ngx.header["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, PATCH, OPTIONS"; + ngx.header["Access-Control-Allow-Credentials"] = "true"; + ngx.header["Access-Control-Allow-Headers"] = "Content-Type"; + '; + proxy_pass http://localhost:3001; +} +``` + +> `Script error.` 是什么错误? 如何拿到更详细的信息? + +接上题, 由于同源性策略 (CORS), 如果你引用的 js 脚本所在的域与当前域不同, 那么浏览器会把 onError 中的 msg 替换为 `Script error.` 要拿到详细错误的方法, 处理配好 `Access-Control-Allow-Origin` 还有在引用脚本的时候指定 `crossorigin` 例如: + +```html + +``` + +详见 [Javascript Script Error.](https://sentry.io/answers/javascript-script-error/) + + +### Agent + +Node.js 中的 `http.Agent` 用于池化 HTTP 客户端请求的 socket (pooling sockets used in HTTP client requests). 也就是复用 HTTP 请求时候的 socket. 如果你没有指定 Agent 的话, 默认用的是 `http.globalAgent`. + +另外, 目前在 Node.js 的 6.8.1(包括)到 6.10(不包括)版本中发现一个问题: + +* 1. 你将 keepAlive 设置为 `true` 时, socket 有复用 +* 2. 即使 keepAlive 没有设置成 `true` 但是长时间内有大量请求时, 同样有复用 socket (复用情况参见[@zcs19871221](https://github.com/zcs19871221)的[解析](https://github.com/zcs19871221/mydoc/blob/master/nodejsAgent.md)) + +1 和 2 这两种情况下, 一旦设置了 request timeout, 由于 socket 一直未销毁, 如果你在请求完成以后没有注意清除该事件, 会导致事件重复监听, 且该事件闭包引用了 req, 会导致内存泄漏. + +如果有疑虑的话可以参见 Node 官方讨论的 [issue](https://github.com/nodejs/node/issues/9268) 以及引入此 bug 的 [commit](https://github.com/nodejs/node/blob/ee7af01b93cc46f1848f6962ad2d6c93f319341a/lib/_http_client.js#L565), 如果此处描述有疑问可以在本 repo 的 [issue](https://github.com/ElemeFE/node-interview/issues/19) 中指出. + + +### socket hang up + +hang up 有挂断的意思, socket hang up 也可以理解为 socket 被挂断. 在 Node.js 中当你要 response 一个请求的时候, 发现该这个 socket 已经被 "挂断", 就会就会报 socket hang up 错误. + +[Node.js 中源码的情况:](https://github.com/nodejs/node/blob/v6.x/lib/_http_client.js#L286) + +```javascript +function socketCloseListener() { + var socket = this; + var req = socket._httpMessage; + + // Pull through final chunk, if anything is buffered. + // the ondata function will handle it properly, and this + // is a no-op if no final chunk remains. + socket.read(); + + // NOTE: It's important to get parser here, because it could be freed by + // the `socketOnData`. + var parser = socket.parser; + req.emit('close'); + if (req.res && req.res.readable) { + // Socket closed before we emitted 'end' below. + req.res.emit('aborted'); + var res = req.res; + res.on('end', function() { + res.emit('close'); + }); + res.push(null); + } else if (!req.res && !req.socket._hadError) { + // This socket error fired before we started to + // receive a response. The error needs to + // fire on the request. + req.emit('error', createHangUpError()); // <------------------- socket hang up + req.socket._hadError = true; + } + + // Too bad. That output wasn't getting written. + // This is pretty terrible that it doesn't raise an error. + // Fixed better in v0.10 + if (req.output) + req.output.length = 0; + if (req.outputEncodings) + req.outputEncodings.length = 0; + + if (parser) { + parser.finish(); + freeParser(parser, req, socket); + } +} +``` + +典型的情况是用户使用浏览器, 请求的时间有点长, 然后用户简单的按了一下 F5 刷新页面. 这个操作会让浏览器取消之前的请求, 然后导致服务端 throw 了一个 socket hang up. + +详见万能的 stackoverflow: [NodeJS - What does “socket hang up” actually mean?](http://stackoverflow.com/questions/16995184/nodejs-what-does-socket-hang-up-actually-mean) + + +## DNS + +早期可以用 TCP/IP 通信之后, 有一个比较蛋疼的问题, 就是 ip 都是一串比较长的数字, 比较难记, 于是大家想了个办法, 给每个 ip 取个好记一点的名字比如 `Alan -> 192.168.0.11` 这样只需要记住好记的名字即可, 随着这个名字的规范化最终变成了今天的域名 (Domain name), 而帮助别人记录这个名字的服务就叫域名解析服务 (Domain Name Service). + +DNS 服务主要基于 UDP, 这里简单介绍 Node.js 实现的接口中的两个方法: + +方法|功能|同步|网络请求|速度 +---|---|---|---|--- +.lookup(hostname[, options], cb)|通过系统自带的 DNS 缓存 (如 `/etc/hosts`)|同步|无|快 +.resolve(hostname[, rrtype], cb)|通过系统配置的 DNS 服务器指定的记录 (rrtype指定)|异步|有|慢 + +> DNS 模块中 .lookup 与 .resolve 的区别? + +当你要解析一个域名的 ip 时, 通过 .lookup 查询直接调用 `getaddrinfo` 来拿取地址, 速度很快, 但是如果本地的 hosts 文件被修改了, .lookup 就会拿 hosts 文件中的地方, 而 .resolve 依旧是外部正常的地址. + +由于 .lookup 是同步的, 所以如果由于什么不可控的原因导致 `getaddrinfo` 缓慢或者阻塞是会影响整个 Node 进程的, 参见[文档](https://nodejs.org/dist/latest-v6.x/docs/api/dns.html#dns_dns_lookup). + +> hosts 文件是什么? 什么叫 DNS 本地解析? + +hosts 文件是个没有扩展名的系统文件,其作用就是将网址域名与其对应的 IP 地址建立一个关联“数据库”,当用户在浏览器中输入一个需要登录的网址时,系统会首先自动从 hosts 文件中寻找对应的IP地址。 + +当我们访问一个域名时,实际上需要的是访问对应的 IP 地址。这时候,获取 IP 地址的方式,先是读取浏览器缓存,如果未命中 => 接着读取本地 hosts 文件,如果还是未命中 => 则向 DNS 服务器发送请求获取。在向 DNS 服务器获取 IP 地址之前的行为,叫做 DNS 本地解析。 + +## ZLIB + +在网络传输过程中, 如果网速稳定的情况下, 对数据进行压缩, 压缩比率越大, 那么传输的效率就越高等同于速度越快了. zlib 模块提供了 Gzip/Gunzip, Deflate/Inflate 和 DeflateRaw/InflateRaw 等压缩方法的类, 这些类接收相同的参数, 都属于可读写的 Stream 实例. + +TODO + +## RPC + +RPC (Remote Procedure Call Protocol) 基于 TCP/IP 来实现调用远程服务器的方法, 与 http 同属应用层. 常用于构建集群, 以及微服务 (推荐一本[《Node.js 微服务》](https://www.amazon.cn/%E5%9B%BE%E4%B9%A6/dp/B01MXY8ARP)虽然我还没看完) + +常见的 RPC 方式: + +* [Thrift](http://thrift.apache.org/) +* HTTP +* MQ + +### Thrift + +> **Thrift**是一种[接口描述语言](https://zh.wikipedia.org/wiki/%E6%8E%A5%E5%8F%A3%E6%8F%8F%E8%BF%B0%E8%AF%AD%E8%A8%80 "接口描述语言")和二进制通讯协议,它被用来定义和创建跨语言的服务。它被当作一个[远程过程调用](https://zh.wikipedia.org/wiki/%E8%BF%9C%E7%A8%8B%E8%BF%87%E7%A8%8B%E8%B0%83%E7%94%A8 "远程过程调用")(RPC)框架来使用,是由[Facebook](https://zh.wikipedia.org/wiki/Facebook "Facebook")为“大规模跨语言服务开发”而开发的。它通过一个代码生成引擎联合了一个软件栈,来创建不同程度的、无缝的[跨平台](https://zh.wikipedia.org/wiki/%E8%B7%A8%E5%B9%B3%E5%8F%B0 "跨平台")高效服务,可以使用[C#](https://zh.wikipedia.org/wiki/C%E2%99%AF "C♯")、[C++](https://zh.wikipedia.org/wiki/C%2B%2B "C++")(基于[POSIX](https://zh.wikipedia.org/wiki/POSIX "POSIX")兼容系统)、Cappuccino、[Cocoa](https://zh.wikipedia.org/wiki/Cocoa "Cocoa")、[Delphi](https://zh.wikipedia.org/wiki/Delphi "Delphi")、[Erlang](https://zh.wikipedia.org/wiki/Erlang "Erlang")、[Go](https://zh.wikipedia.org/wiki/Go "Go")、[Haskell](https://zh.wikipedia.org/wiki/Haskell "Haskell")、[Java](https://zh.wikipedia.org/wiki/Java "Java")、[Node.js](https://zh.wikipedia.org/wiki/Node.js "Node.js")、[OCaml](https://zh.wikipedia.org/wiki/OCaml "OCaml")、[Perl](https://zh.wikipedia.org/wiki/Perl "Perl")、[PHP](https://zh.wikipedia.org/wiki/PHP "PHP")、[Python](https://zh.wikipedia.org/wiki/Python "Python")、[Ruby](https://zh.wikipedia.org/wiki/Ruby "Ruby")和[Smalltalk](https://zh.wikipedia.org/wiki/Smalltalk "Smalltalk")。虽然它以前是由Facebook开发的,但它现在是[Apache软件基金会](https://zh.wikipedia.org/wiki/Apache%E8%BD%AF%E4%BB%B6%E5%9F%BA%E9%87%91%E4%BC%9A "Apache软件基金会")的[开源](https://zh.wikipedia.org/wiki/%E5%BC%80%E6%BA%90 "开源")项目了。该实现被描述在2007年4月的一篇由Facebook发表的技术论文中,该论文现由Apache掌管。 + +### HTTP + +使用 HTTP 协议来进行 RPC 调用也是很常见的, 相比 TCP 连接, 通过通过 HTTP 的方式性能会差一些, 但是在使用以及调试上会简单一些. 近期比较有名的框架参见 [gRPC](http://www.grpc.io/): + +> gRPC is an open source remote procedure call (RPC) system initially developed at Google. It uses HTTP/2 for transport, Protocol Buffers as the interface description language, and provides features such as authentication, bidirectional streaming and flow control, blocking or nonblocking bindings, and cancellation and timeouts. It generates cross-platform client and server bindings for many languages. + +### MQ + +使用消息队列 (Message Queue) 来进行 RPC 调用 (RPC over mq) 在业内有不少例子, 比较适合业务解耦/广播/限流等场景. + +TODO diff --git a/sections/zh-cn/os.md b/sections/zh-cn/os.md new file mode 100644 index 0000000..a8d4c31 --- /dev/null +++ b/sections/zh-cn/os.md @@ -0,0 +1,374 @@ +# OS + +* `[Doc]` TTY +* `[Doc]` OS (操作系统) +* `[Doc]` 命令行参数 +* `[Basic]` 负载 +* `[Point]` CheckList +* `[Basic]` 指标 + +## TTY + +"tty" 原意是指 "teletype" 即打字机, "pty" 则是 "pseudo-teletype" 即伪打字机. 在 Unix 中, `/dev/tty*` 是指任何表现的像打字机的设备, 例如终端 (terminal). + +你可以通过 `w` 命令查看当前登录的用户情况, 你会发现每登录了一个窗口就会有一个新的 tty. + +```shell +$ w + 11:49:43 up 482 days, 19:38, 3 users, load average: 0.03, 0.08, 0.07 +USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT +dev pts/0 10.0.128.252 10:44 1:01m 0.09s 0.07s -bash +dev pts/2 10.0.128.252 11:08 2:07 0.17s 0.14s top +root pts/3 10.0.240.2 11:43 7.00s 0.04s 0.00s w +``` + +使用 ps 命令查看进程信息中也有 tty 的信息: + +```shell +$ ps -x + PID TTY STAT TIME COMMAND + 5530 ? S 0:00 sshd: dev@pts/3 + 5531 pts/3 Ss+ 0:00 -bash +11296 ? S 0:00 sshd: dev@pts/4 +11297 pts/4 Ss 0:00 -bash +13318 pts/4 R+ 0:00 ps -x +23733 ? Ssl 2:53 PM2 v1.1.2: God Daemon +``` + +其中为 `?` 的是没有依赖 TTY 的进程, 即[守护进程](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#%E5%AE%88%E6%8A%A4%E8%BF%9B%E7%A8%8B). + +在 Node.js 中你可以通过 stdio 的 isTTY 来判断当前进程是否处于 TTY (如终端) 的环境. + +```shell +$ node -p -e "Boolean(process.stdout.isTTY)" +true +$ node -p -e "Boolean(process.stdout.isTTY)" | cat +false +``` + +## OS + +通过 OS 模块可以获取到当前系统一些基础信息的辅助函数. + +|属性|描述| +|---|---| +|os.EOL|根据当前系统, 返回当前系统的 `End Of Line`| +|os.arch()|返回当前系统的 CPU 架构, 如 `'x86'` 和 `'x64'`| +|os.constants|返回系统常量| +|os.cpus()|返回 CPU 每个核的信息| +|os.endianness()|返回 CPU 字节序, 如果是大端字节序返回 `BE`, 小端字节序则 `LE`| +|os.freemem()|返回系统空闲内存的大小, 单位是字节| +|os.homedir()|返回当前用户的根目录| +|os.hostname()|返回当前系统的主机名| +|os.loadavg()|返回负载信息| +|os.networkInterfaces()|返回网卡信息 (类似 `ifconfig`)| +|os.platform()|返回编译时指定的平台信息, 如 `win32`, `linux`, 同 `process.platform()`| +|os.release()|返回操作系统的分发版本号| +|os.tmpdir()|返回系统默认的临时文件夹| +|os.totalmem()|返回总内存大小(同内存条大小)| +|os.type()|根据 `[uname](https://en.wikipedia.org/wiki/Uname#Examples)` 返回系统的名称| +|os.uptime()|返回系统的运行时间,单位是秒| +|os.userInfo([options])|返回当前用户信息| + +> 不同操作系统的换行符 (EOL) 有什么区别? + +end of line (EOL) 同 newline, line ending, 以及 line break. + +通常由 line feed (LF, `\n`) 和 carriage return (CR, `\r`) 组成. 常见的情况: + +|符号|系统| +|---|---| +|LF|在 Unix 或 Unix 相容系统 (GNU/Linux, AIX, Xenix, Mac OS X, ...)、BeOS、Amiga、RISC OS| +|CR+LF|MS-DOS、微软视窗操作系统 (Microsoft Windows)、大部分非 Unix 的系统| +|CR|Apple II 家族, Mac OS 至版本9| + +如果不了解 EOL 跨系统的兼容情况, 那么在处理文件的行分割/行统计等情况时可能会被坑. + +### OS 常量 + +* 信号常量 (Signal Constants), 如 `SIGHUP`, `SIGKILL` 等. +* POSIX 错误常量 (POSIX Error Constants), 如 `EACCES`, `EADDRINUSE` 等. +* Windows 错误常量 (Windows Specific Error Constants), 如 `WSAEACCES`, `WSAEBADF` 等. +* libuv 常量 (libuv Constants), 仅 `UV_UDP_REUSEADDR`. + + +## Path + +Node.js 内置的 path 是用于处理路径问题的模块. 不过众所周知, 路径在不同操作系统下又不可调和的差异. + +### Windows vs. POSIX + +|POSIX|值|Windows|值| +|---|---|---|---| +|path.posix.sep|`'/'`|path.win32.sep|`'\\'`| +|path.posix.normalize('/foo/bar//baz/asdf/quux/..')|`'/foo/bar/baz/asdf'`|path.win32.normalize('C:\\temp\\\\foo\\bar\\..\\')|`'C:\\temp\\foo\\'`| +|path.posix.basename('/tmp/myfile.html')|`'myfile.html'`|path.win32.basename('C:\\temp\\myfile.html')|`'myfile.html'`| +|path.posix.join('/asdf', '/test.html')|`'/asdf/test.html'`|path.win32.join('/asdf', '/test.html')|`'\\asdf\\test.html'`| +|path.posix.relative('/root/a', '/root/b')|`'../b'`|path.win32.relative('C:\\a', 'c:\\b')|`'..\\b'` +|path.posix.isAbsolute('/baz/..')|`true`|path.win32.isAbsolute('C:\\foo\\..')|`true`| +|path.posix.delimiter|`':'`|path.win32.delimiter|`','`| +|process.env.PATH|`'/usr/bin:/bin'`|process.env.PATH|`C:\Windows\system32;C:\Program Files\node\'`| +|PATH.split(path.posix.delimiter)|`['/usr/bin', '/bin']`|PATH.split(path.win32.delimiter)|`['C:\\Windows\\system32', 'C:\\Program Files\\node\\']`| + + +看了上表之后, 你应该了解到当你处于某个平台之下的时候, 所使用的 `path` 模块的方法其实就是对应的平台的方法, 例如笔者这里用的是 mac, 所以: + +```javascript +const path = require('path'); +console.log(path.basename === path.posix.basename); // true +``` + +如果你处于其中某一个平台, 但是要处理另外一个平台的路径, 需要注意这个跨平台的问题. + +### path 对象 + +on POSIX: + +```javascript +path.parse('/home/user/dir/file.txt') +// Returns: +// { +// root : "/", +// dir : "/home/user/dir", +// base : "file.txt", +// ext : ".txt", +// name : "file" +// } +``` + +```javascript +┌─────────────────────┬────────────┐ +│ dir │ base │ +├──────┬ ├──────┬─────┤ +│ root │ │ name │ ext │ +" / home/user/dir / file .txt " +└──────┴──────────────┴──────┴─────┘ +``` + +on Windows: + +```javascript +path.parse('C:\\path\\dir\\file.txt') +// Returns: +// { +// root : "C:\\", +// dir : "C:\\path\\dir", +// base : "file.txt", +// ext : ".txt", +// name : "file" +// } +``` + +```javascript +┌─────────────────────┬────────────┐ +│ dir │ base │ +├──────┬ ├──────┬─────┤ +│ root │ │ name │ ext │ +" C:\ path\dir \ file .txt " +└──────┴──────────────┴──────┴─────┘ +``` + +### path.extname(path) + +|case|return| +|---|---| +|path.extname('index.html')|`'.html'`| +|path.extname('index.coffee.md')|`'.md'`| +|path.extname('index.')|`'.'`| +|path.extname('index')|`''`| +|path.extname('.index')|`''`| + + +## 命令行参数 + +命令行参数 (Command Line Options), 即对 CLI 使用上的一些文档. 关于 CLI 主要有 4 种使用方式: + +* node [options] [v8 options] [script.js | -e "script"] [arguments] +* node debug [script.js | -e "script" | :] … +* node --v8-options +* 无参数直接启动 REPL 环境 + +### Options + +|参数|简介| +|---|---| +|-v, --version|查看当前 node 版本| +|-h, --help|查看帮助文档| +|-e, --eval "script"|将参数字符串当做代码执行 +|-p, --print "script"|打印 `-e` 的返回值 +|-c, --check|检查语法并不执行 +|-i, --interactive|即使 stdin 不是终端也打开 REPL 模式 +|-r, --require module|在启动前预先 `require` 指定模块 +|--no-deprecation|关闭废弃模块警告 +|--trace-deprecation|打印废弃模块的堆栈跟踪信息 +|--throw-deprecation|执行废弃模块时抛出错误 +|--no-warnings|无视报警(包括废弃警告) +|--trace-warnings|打印警告的 stack (包括废弃模块) +|--trace-sync-io|只要检测到异步 I/O 出于 Event loop 的开头就打印 stack trace +|--zero-fill-buffers|自动初始化(zero-fill) **Buffer** 和 **SlowBuffer** +|--preserve-symlinks|在解析和缓存模块时指示模块加载程序保存符号链接 +|--track-heap-objects|为堆快照跟踪堆对象的分配情况 +|--prof-process|使用 v8 选项 `--prof` 生成 Profilling 报告 +|--v8-options|显示 v8 命令行选项 +|--tls-cipher-list=list|指明替代的默认 TLS 加密器列表 +|--enable-fips|在启动时开启 FIPS-compliant crypto +|--force-fips|在启动时强制实施 FIPS-compliant +|--openssl-config=file|启动时加载 OpenSSL 配置文件 +|--icu-data-dir=file|指定ICU数据加载路径 + +### 环境变量 + +|环境变量|简介| +|----|----| +|`NODE_DEBUG=module[,…]`|指定要打印调试信息的核心模块列表 +|`NODE_PATH=path[:…]`|指定搜索目录模块路径的前缀列表 +|`NODE_DISABLE_COLORS=1`|关闭 REPL 的颜色显示 +|`NODE_ICU_DATA=file`|ICU (Intl object) 数据路径 +|`NODE_REPL_HISTORY=file`|持久化存储REPL历史文件的路径 +|`NODE_TTY_UNSAFE_ASYNC=1`|设置为1时, 将同步操作 stdio (如 console.log 变成同步) +|`NODE_EXTRA_CA_CERTS=file`|指定 CA (如 VeriSign) 的额外证书路径 + +## 负载 + +负载是衡量服务器运行状态的一个重要概念. 通过负载情况, 我们可以知道服务器目前状态是清闲, 良好, 繁忙还是即将 crash. + +通常我们要查看的负载是 CPU 负载, 详细一点的情况你可以通过阅读这篇博客: [Understanding Linux CPU Load](http://blog.scoutapp.com/articles/2009/07/31/understanding-load-averages) 来了解. + +命令行上可以通过 `uptime`, `top` 命令, Node.js 中可以通过 `os.loadavg()` 来获取当前系统的负载情况: + +``` +load average: 0.09, 0.05, 0.01 +``` + +其中分别是最近 1 分钟, 5 分钟, 15 分钟内系统 CPU 的平均负载. 当 CPU 的一个核工作饱和的时候负载为 1, 有几核 CPU 那么饱和负载就是几. + +在 Node.js 中单个进程的 CPU 负载查看可以使用 [pidusage](https://github.com/soyuka/pidusage) 模块. + +除了 CPU 负载, 对于服务端 (偏维护) 还需要了解网络负载, 磁盘负载等. + +## CheckList + +> 有一个醉汉半夜在路灯下徘徊,路过的人奇怪地问他:“你在路灯下找什么?”醉汉回答:“我在找我的KEY”,路人更奇怪了:“找钥匙为什么在路灯下?”,醉汉说:“因为这里最亮!”。 + +很多服务端的同学在说到检查服务器状态时只知道使用 `top` 命令, 其实情况就和上面的笑话一样, 因为对于他们而言 `top` 是最亮的那盏路灯. + +对于服务端程序员而言, 完整的服务器 checklist 首推 [《性能之巅》](https://www.amazon.cn/%E5%9B%BE%E4%B9%A6/dp/B0140I5WPK) 第二章中讲述的 [USE 方法](http://www.brendangregg.com/USEmethod/use-linux.html). + +The USE Method provides a strategy for performing a complete check of system health, identifying common bottlenecks and errors. For each system resource, metrics for utilization, saturation and errors are identified and checked. Any issues discovered are then investigated using further strategies. + +This is an example USE-based metric list for Linux operating systems (eg, Ubuntu, CentOS, Fedora). This is primarily intended for system administrators of the physical systems, who are using command line tools. Some of these metrics can be found in remote monitoring tools. + +### Physical Resources + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
componenttypemetric
CPUutilizationsystem-wide: vmstat 1, "us" + "sy" + "st"; sar -u, sum fields except "%idle" and "%iowait"; dstat -c, sum fields except "idl" and "wai"; per-cpu: mpstat -P ALL 1, sum fields except "%idle" and "%iowait"; sar -P ALL, same as mpstat; per-process: top, "%CPU"; htop, "CPU%"; ps -o pcpu; pidstat 1, "%CPU"; per-kernel-thread: top/htop ("K" to toggle), where VIRT == 0 (heuristic). [1]
CPUsaturationsystem-wide: vmstat 1, "r" > CPU count [2]; sar -q, "runq-sz" > CPU count; dstat -p, "run" > CPU count; per-process: /proc/PID/schedstat 2nd field (sched_info.run_delay); perf sched latency (shows "Average" and "Maximum" delay per-schedule); dynamic tracing, eg, SystemTap schedtimes.stp "queued(us)" [3]
CPUerrorsperf (LPE) if processor specific error events (CPC) are available; eg, AMD64's "04Ah Single-bit ECC Errors Recorded by Scrubber" [4]
Memory capacityutilizationsystem-wide: free -m, "Mem:" (main memory), "Swap:" (virtual memory); vmstat 1, "free" (main memory), "swap" (virtual memory); sar -r, "%memused"; dstat -m, "free"; slabtop -s c for kmem slab usage; per-process: top/htop, "RES" (resident main memory), "VIRT" (virtual memory), "Mem" for system-wide summary
Memory capacitysaturationsystem-wide: vmstat 1, "si"/"so" (swapping); sar -B, "pgscank" + "pgscand" (scanning); sar -W; per-process: 10th field (min_flt) from /proc/PID/stat for minor-fault rate, or dynamic tracing [5]; OOM killer: dmesg | grep killed
Memory capacityerrorsdmesg for physical failures; dynamic tracing, eg, SystemTap uprobes for failed malloc()s
Network Interfacesutilizationsar -n DEV 1, "rxKB/s"/max "txKB/s"/max; ip -s link, RX/TX tput / max bandwidth; /proc/net/dev, "bytes" RX/TX tput/max; nicstat "%Util" [6]
Network Interfacessaturationifconfig, "overruns", "dropped"; netstat -s, "segments retransmited"; sar -n EDEV, *drop and *fifo metrics; /proc/net/dev, RX/TX "drop"; nicstat "Sat" [6]; dynamic tracing for other TCP/IP stack queueing [7]
Network Interfaceserrorsifconfig, "errors", "dropped"; netstat -i, "RX-ERR"/"TX-ERR"; ip -s link, "errors"; sar -n EDEV, "rxerr/s" "txerr/s"; /proc/net/dev, "errs", "drop"; extra counters may be under /sys/class/net/...; dynamic tracing of driver function returns 76]
Storage device I/Outilizationsystem-wide: iostat -xz 1, "%util"; sar -d, "%util"; per-process: iotop; pidstat -d; /proc/PID/sched "se.statistics.iowait_sum"
Storage device I/Osaturationiostat -xnz 1, "avgqu-sz" > 1, or high "await"; sar -d same; LPE block probes for queue length/latency; dynamic/static tracing of I/O subsystem (incl. LPE block probes)
Storage device I/Oerrors/sys/devices/.../ioerr_cnt; smartctl; dynamic/static tracing of I/O subsystem response codes [8]
Storage capacityutilizationswap: swapon -s; free; /proc/meminfo "SwapFree"/"SwapTotal"; file systems: "df -h"
Storage capacitysaturationnot sure this one makes sense - once it's full, ENOSPC
Storage capacityerrorsstrace for ENOSPC; dynamic tracing for ENOSPC; /var/log/messages errs, depending on FS
Storage controllerutilizationiostat -xz 1, sum devices and compare to known IOPS/tput limits per-card
Storage controllersaturationsee storage device saturation, ...
Storage controllererrorssee storage device errors, ...
Network controllerutilizationinfer from ip -s link (or /proc/net/dev) and known controller max tput for its interfaces
Network controllersaturationsee network interface saturation, ...
Network controllererrorssee network interface errors, ...
CPU interconnectutilizationLPE (CPC) for CPU interconnect ports, tput / max
CPU interconnectsaturationLPE (CPC) for stall cycles
CPU interconnecterrorsLPE (CPC) for whatever is available
Memory interconnectutilizationLPE (CPC) for memory busses, tput / max; or CPI greater than, say, 5; CPC may also have local vs remote counters
Memory interconnectsaturationLPE (CPC) for stall cycles
Memory interconnecterrorsLPE (CPC) for whatever is available
I/O interconnectutilizationLPE (CPC) for tput / max if available; inference via known tput from iostat/ip/...
I/O interconnectsaturationLPE (CPC) for stall cycles
I/O interconnecterrorsLPE (CPC) for whatever is available
+ + +### Software Resources + + + + + + + + + + + + + + + + + + + + +
componenttypemetric
Kernel mutexutilizationWith CONFIG_LOCK_STATS=y, /proc/lock_stat "holdtime-totat" / "acquisitions" (also see "holdtime-min", "holdtime-max") [8]; dynamic tracing of lock functions or instructions (maybe)
Kernel mutexsaturationWith CONFIG_LOCK_STATS=y, /proc/lock_stat "waittime-total" / "contentions" (also see "waittime-min", "waittime-max"); dynamic tracing of lock functions or instructions (maybe); spinning shows up with profiling (perf record -a -g -F 997 ..., oprofile, dynamic tracing)
Kernel mutexerrorsdynamic tracing (eg, recusive mutex enter); other errors can cause kernel lockup/panic, debug with kdump/crash
User mutexutilizationvalgrind --tool=drd --exclusive-threshold=... (held time); dynamic tracing of lock to unlock function time
User mutexsaturationvalgrind --tool=drd to infer contention from held time; dynamic tracing of synchronization functions for wait time; profiling (oprofile, PEL, ...) user stacks for spins
User mutexerrorsvalgrind --tool=drd various errors; dynamic tracing of pthread_mutex_lock() for EAGAIN, EINVAL, EPERM, EDEADLK, ENOMEM, EOWNERDEAD, ...
Task capacityutilizationtop/htop, "Tasks" (current); sysctl kernel.threads-max, /proc/sys/kernel/threads-max (max)
Task capacitysaturationthreads blocking on memory allocation; at this point the page scanner should be running (sar -B "pgscan*"), else examine using dynamic tracing
Task capacityerrors"can't fork()" errors; user-level threads: pthread_create() failures with EAGAIN, EINVAL, ...; kernel: dynamic tracing of kernel_thread() ENOMEM
File descriptorsutilizationsystem-wide: sar -v, "file-nr" vs /proc/sys/fs/file-max; dstat --fs, "files"; or just /proc/sys/fs/file-nr; per-process: ls /proc/PID/fd | wc -l vs ulimit -n
File descriptorssaturationdoes this make sense? I don't think there is any queueing or blocking, other than on memory allocation.
File descriptorserrorsstrace errno == EMFILE on syscalls returning fds (eg, open(), accept(), ...).
+ +#### ulimit + +ulimit 用于管理用户对系统资源的访问. + +``` +-a 显示目前全部限制情况 +-c 设定 core 文件的最大值, 单位为区块 +-d <数据节区大小> 程序数据节区的最大值, 单位为KB +-f <文件大小> shell 所能建立的最大文件, 单位为区块 +-H 设定资源的硬性限制, 也就是管理员所设下的限制 +-m <内存大小> 指定可使用内存的上限, 单位为 KB +-n <文件描述符数目> 指定同一时间最多可开启的 fd 数 +-p <缓冲区大小> 指定管道缓冲区的大小, 单位512字节 +-s <堆叠大小> 指定堆叠的上限, 单位为 KB +-S 设定资源的弹性限制 +-t 指定CPU使用时间的上限, 单位为秒 +-u <进程数目> 用户最多可开启的进程数目 +-v <虚拟内存大小> 指定可使用的虚拟内存上限, 单位为 KB +``` + +例如: + +``` +$ ulimit -a +core file size (blocks, -c) 0 +data seg size (kbytes, -d) unlimited +scheduling priority (-e) 0 +file size (blocks, -f) unlimited +pending signals (-i) 127988 +max locked memory (kbytes, -l) 64 +max memory size (kbytes, -m) unlimited +open files (-n) 655360 +pipe size (512 bytes, -p) 8 +POSIX message queues (bytes, -q) 819200 +real-time priority (-r) 0 +stack size (kbytes, -s) 8192 +cpu time (seconds, -t) unlimited +max user processes (-u) 4096 +virtual memory (kbytes, -v) unlimited +file locks (-x) unlimited +``` + +注意, open socket 等资源拿到的也是 fd, 所以 `ulimit -n` 比较小除了文件打不开, 还可能建立不了 socket 链接. + diff --git a/sections/zh-cn/process.md b/sections/zh-cn/process.md new file mode 100644 index 0000000..062f446 --- /dev/null +++ b/sections/zh-cn/process.md @@ -0,0 +1,248 @@ +# 进程 + +* [`[Doc]` Process (进程)](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#process) +* [`[Doc]` Child Processes (子进程)](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#child-process) +* [`[Doc]` Cluster (集群)](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#cluster) +* [`[Basic]` 进程间通信](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#进程间通信) +* [`[Basic]` 守护进程](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#守护进程) + +## 简述 + +关于 Process, 我们需要讨论的是两个概念, ①操作系统的进程, ② Node.js 中的 Process 对象. 操作进程对于服务端而言, 好比 html 之于前端一样基础. 想做服务端编程是不可能绕过 Unix/Linux 的. 在 Linux/Unix/Mac 系统中运行 `ps -ef` 命令可以看到当前系统中运行的进程. 各个参数如下: + +|列名称|意义| +|-----|---| +|UID|执行该进程的用户ID| +|PID|进程编号| +|PPID|该进程的父进程编号| +|C|该进程所在的CPU利用率| +|STIME|进程执行时间| +|TTY|进程相关的终端类型| +|TIME|进程所占用的CPU时间| +|CMD|创建该进程的指令| + +关于进程以及操作系统一些更深入的细节推荐阅读 APUE, 即《Unix 高级编程》等书籍来了解. + +## Process + +这里来讨论 Node.js 中的 `process` 对象. 直接在代码中通过 `console.log(process)` 即可打印出来. 可以看到 process 对象暴露了非常多有用的属性以及方法, 具体的细节见[官方文档](https://nodejs.org/dist/latest-v6.x/docs/api/process.html), 已经说的挺详细了. 其中包括但不限于: + +* 进程基础信息 +* 进程 Usage +* 进程级事件 +* 依赖模块/版本信息 +* OS 基础信息 +* 账户信息 +* 信号收发 +* 三个标准流 + +### process.nextTick + +上一节已经提到过 `process.nextTick` 了, 这是一个你需要了解的, 重要的, 基础方法. + + +``` + ┌───────────────────────┐ +┌─>│ timers │ +│ └──────────┬────────────┘ +│ ┌──────────┴────────────┐ +│ │ I/O callbacks │ +│ └──────────┬────────────┘ +│ ┌──────────┴────────────┐ +│ │ idle, prepare │ +│ └──────────┬────────────┘ ┌───────────────┐ +│ ┌──────────┴────────────┐ │ incoming: │ +│ │ poll │<─────┤ connections, │ +│ └──────────┬────────────┘ │ data, etc. │ +│ ┌──────────┴────────────┐ └───────────────┘ +│ │ check │ +│ └──────────┬────────────┘ +│ ┌──────────┴────────────┐ +└──┤ close callbacks │ + └───────────────────────┘ +``` + +`process.nextTick` 并不属于 Event loop 中的某一个阶段, 而是在 Event loop 的每一个阶段结束后, 直接执行 `nextTickQueue` 中插入的 "Tick", 并且直到整个 Queue 处理完. 所以面试时又有可以问的问题了, 递归调用 process.nextTick 会怎么样? (doge + +```javascript +function test() { + process.nextTick(() => test()); +} +``` + +这种情况与以下情况, 有什么区别? 为什么? + +```javascript +function test() { + setTimeout(() => test(), 0); +} +``` + +### 配置 + +配置是开发部署中一个很常见的问题. 普通的配置有两种方式, 一是定义配置文件, 二是使用环境变量. + +![node-configuration](https://blog-assets.risingstack.com/2016/Sep/node-js-survey/node-js-survey-envvar-config-new.png) + +你可以通过[设置环境变量](http://cn.bing.com/search?q=linux+%E8%AE%BE%E7%BD%AE%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F)来指定配置, 然后通过 `process.env` 来获取配置项. 另外也可以通过读取定义好的配置文件来获取, 在这方面有很多不错的库例如 `dotenv`, `node-config` 等, 而在使用这些库来加载配置文件的时候, 通常都会碰到一个当前工作目录的问题. + +> 进程的当前工作目录是什么? 有什么作用? + +当前进程启动的目录, 通过 process.cwd() 获取当前工作目录 (current working directory), 通常是命令行启动的时候所在的目录 (也可以在启动时指定), 文件操作等使用相对路径的时候会相对当前工作目录来获取文件. + +一些获取配置的第三方模块就是通过你的当前目录来找配置文件的. 所以如果你错误的目录启动脚本, 可能没法得到正确的结果. 在程序中可以通过 `process.chdir()` 来改变当前的工作目录. + +### 标准流 + +在 process 对象上还暴露了 `process.stderr`, `process.stdout` 以及 `process.stdin` 三个标准流, 熟悉 C/C++/Java 的同学应该对此比较熟悉. 关于这几个流, 常见的面试问题是问 **console.log 是同步还是异步? 如何实现一个 console.log?** + +如果简历中有出现 C/C++ 关键字, 一般都会问到如何实现一个同步的输入 (类似实现C语言的 `scanf`, C++ 的 `cin`, Python 的 `raw_input` 等). + +### 维护方面 + +熟悉与进程有关的基础命令, 如 top, ps, pstree 等命令. + +## Child Process + +子进程 (Child Process) 是进程中一个重要的概念. 你可以通过 Node.js 的 `child_process` 模块来执行可执行文件, 调用命令行命令, 比如其他语言的程序等. 也可以通过该模块来将 .js 代码以子进程的方式启动. 比较有名的网易的分布式架构 [pomelo](https://github.com/NetEase/pomelo) 就是基于该模块 (而不是 `cluster`) 来实现多进程分布式架构的. + +> child_process.fork 与 POSIX 的 fork 有什么区别? + +Node.js 的 `child_process.fork()` 不像 POSIX [fork(2)](http://man7.org/linux/man-pages/man2/fork.2.html) 系统调用, 不会拷贝当前父进程. 这里对于其他语言转过的同学可能比较误导, 可以作为一个比较偏的面试题. + +* spawn() 启动一个子进程来执行命令 + * options.detached 父进程死后是否允许子进程存活 + * options.stdio 指定子进程的三个标准流 +* spawnSync() 同步版的 spawn, 可指定超时, 返回的对象可获得子进程的情况 +* exec() 启动一个子进程来执行命令, 带回调参数获知子进程的情况, 可指定进程运行的超时时间 +* execSync() 同步版的 exec(), 可指定超时, 返回子进程的输出 (stdout) +* execFile() 启动一个子进程来执行一个可执行文件, 可指定进程运行的超时时间 +* execFileSync() 同步版的 execFile(), 返回子进程的输出, 如何超时或者 exit code 不为 0, 会直接 throw Error +* fork() 加强版的 spawn(), 返回值是 ChildProcess 对象可以与子进程交互 + +其中 exec/execSync 方法会直接调用 bash 来解释命令, 所以如果有命令有外部参数, 则需要注意被注入的情况. + +### child.kill 与 child.send + +常见会问的面试题, 如 `child.kill` 与 `child.send` 的区别. 二者一个是基于信号系统, 一个是基于 IPC. + +> 父进程或子进程的死亡是否会影响对方? 什么是孤儿进程? + +子进程死亡不会影响父进程, 不过子进程死亡时(线程组的最后一个线程,通常是“领头”线程死亡时),会向它的父进程发送死亡信号. 反之父进程死亡, 一般情况下子进程也会随之死亡, 但如果此时子进程处于可运行态、僵死状态等等的话, 子进程将被`进程1`(init 进程)收养,从而成为孤儿进程. 另外, 子进程死亡的时候(处于“终止状态”),父进程没有及时调用 `wait()` 或 `waitpid()` 来返回死亡进程的相关信息,此时子进程还有一个 `PCB` 残留在进程表中,被称作僵尸进程. + +## Cluster + +Cluster 是常见的 Node.js 利用多核的办法. 它是基于 `child_process.fork()` 实现的, 所以 cluster 产生的进程之间是通过 IPC 来通信的, 并且它也没有拷贝父进程的空间, 而是通过加入 cluster.isMaster 这个标识, 来区分父进程以及子进程, 达到类似 POSIX 的 [fork](http://man7.org/linux/man-pages/man2/fork.2.html) 的效果. + +```javascript +const cluster = require('cluster'); // | | +const http = require('http'); // | | +const numCPUs = require('os').cpus().length; // | | 都执行了 + // | | +if (cluster.isMaster) { // |-|----------------- + // Fork workers. // | + for (var i = 0; i < numCPUs; i++) { // | + cluster.fork(); // | + } // | 仅父进程执行 (a.js) + cluster.on('exit', (worker) => { // | + console.log(`${worker.process.pid} died`); // | + }); // | +} else { // |------------------- + // Workers can share any TCP connection // | + // In this case it is an HTTP server // | + http.createServer((req, res) => { // | + res.writeHead(200); // | 仅子进程执行 (b.js) + res.end('hello world\n'); // | + }).listen(8000); // | +} // |------------------- + // | | +console.log('hello'); // | | 都执行了 +``` + +在上述代码中 numCPUs 虽然是全局变量但是, 在父进程中修改它, 子进程中并不会改变, 因为父进程与子进程是完全独立的两个空间. 他们所谓的共有仅仅只是都执行了, 并不是同一份. + +你可以把父进程执行的部分当做 `a.js`, 子进程执行的部分当做 `b.js`, 你可以把他们想象成是先执行了 `node a.js` 然后 cluster.fork 了几次, 就执行执行了几次 `node b.js`. 而 cluster 模块则是二者之间的一个桥梁, 你可以通过 cluster 提供的方法, 让其二者之间进行沟通交流. + +### How It Works + +worker 进程是由 child_process.fork() 方法创建的, 所以可以通过 IPC 在主进程和子进程之间相互传递服务器句柄. + +cluster 模块提供了两种分发连接的方式. + +第一种方式 (默认方式, 不适用于 windows), 通过时间片轮转法(round-robin)分发连接. 主进程监听端口, 接收到新连接之后, 通过时间片轮转法来决定将接收到的客户端的 socket 句柄传递给指定的 worker 处理. 至于每个连接由哪个 worker 来处理, 完全由内置的循环算法决定. + +第二种方式是由主进程创建 socket 监听端口后, 将 socket 句柄直接分发给相应的 worker, 然后当连接进来时, 就直接由相应的 worker 来接收连接并处理. + +使用第二种方式时, 多个 worker 之间会存在竞争关系, 产生一个老生常谈的 "[惊群效应](https://www.google.com.hk/search?q=%E6%83%8A%E7%BE%A4%E6%95%88%E5%BA%94)" 从而导致效率变低的问题. 该问题常见于 Apache. 并且各自竞争的情况下无法控制一个新的连接由哪个进程来处理, 从而导致各 worker 进程之间的负载不均衡, 比如通常 70% 的连接仅被 8 个进程中的 2 个处理, 而其他进程比较清闲. + +## 进程间通信 + +IPC (Inter-process communication) 进程间通信技术. 常见的进程间通信技术列表如下: + +类型|无连接|可靠|流控制|优先级 +---|-----|----|-----|----- +普通PIPE|N|Y|Y|N +命名PIPE|N|Y|Y|N +消息队列|N|Y|Y|N +信号量|N|Y|Y|Y +共享存储|N|Y|Y|Y +UNIX流SOCKET|N|Y|Y|N +UNIX数据包SOCKET|Y|Y|N|N + +Node.js 中的 IPC 通信是由 libuv 通过管道技术实现的, 在 windows 下由命名管道(named pipe)实现也就是上表中的最后第二个, *nix 系统则采用 UDS (Unix Domain Socket) 实现. + +普通的 socket 是为网络通讯设计的, 而网络本身是不可靠的, 而为 IPC 设计的 socket 则不然, 因为默认本地的网络环境是可靠的, 所以可以简化大量不必要的 encode/decode 以及计算校验等, 得到效率更高的 UDS 通信. + +如果了解 Node.js 的 IPC 的话, 可以问个比较有意思的问题 + +> 在 IPC 通道建立之前, 父进程与子进程是怎么通信的? 如果没有通信, 那 IPC 是怎么建立的? + +这个问题也挺简单, 只是个思路的问题. 在通过 child_process 建立子进程的时候, 是可以指定子进程的 env (环境变量) 的. 所以 Node.js 在启动子进程的时候, 主进程先建立 IPC 频道, 然后将 IPC 频道的 fd (文件描述符) 通过环境变量 (`NODE_CHANNEL_FD`) 的方式传递给子进程, 然后子进程通过 fd 连上 IPC 与父进程建立连接. + +最后于进程间通信 (IPC) 的问题, 一般不会直接问 IPC 的实现, 而是会问什么情况下需要 IPC, 以及使用 IPC 处理过什么业务场景等. + + +## 守护进程 + +最后的守护进程, 是服务端方面一个很基础的概念了. 很多人可能只知道通过 pm2 之类的工具可以将进程以守护进程的方式启动, 却不了解什么是守护进程, 为什么要用守护进程. 对于水平好的同学, 我们是希望能了解守护进程的实现的. + +普通的进程, 在用户退出终端之后就会直接关闭. 通过 `&` 启动到后台的进程, 之后会由于会话(session组)被回收而终止进程. 守护进程是不依赖终端(tty)的进程, 不会因为用户退出终端而停止运行的进程. + +```c +// 守护进程实现 (C语言版本) +void init_daemon() +{ + pid_t pid; + int i = 0; + + if ((pid = fork()) == -1) { + printf("Fork error !\n"); + exit(1); + } + + if (pid != 0) { + exit(0); // 父进程退出 + } + + setsid(); // 子进程开启新会话, 并成为会话首进程和组长进程 + if ((pid = fork()) == -1) { + printf("Fork error !\n"); + exit(-1); + } + if (pid != 0) { + exit(0); // 结束第一子进程, 第二子进程不再是会话首进程 + // 避免当前会话组重新与tty连接 + } + chdir("/tmp"); // 改变工作目录 + umask(0); // 重设文件掩码 + for (; i < getdtablesize(); ++i) { + close(i); // 关闭打开的文件描述符 + } + + return; +} +``` + +[Node.js 编写守护进程](https://cnodejs.org/topic/57adfadf476898b472247eac) + + diff --git a/sections/zh-cn/security.md b/sections/zh-cn/security.md new file mode 100644 index 0000000..fac89e3 --- /dev/null +++ b/sections/zh-cn/security.md @@ -0,0 +1,208 @@ +# 安全 + +* `[Doc]` Crypto (加密) +* `[Doc]` TLS/SSL +* `[Doc]` HTTPS +* `[Point]` XSS +* `[Point]` CSRF +* `[Point]` 中间人攻击 +* `[Point]` Sql/Nosql 注入攻击 + + +## Crypto + +Node.js 的 `crypto` 模块封装了诸多的加密功能, 包括 OpenSSL 的哈希、HMAC、加密、解密、签名和验证函数等. + +Node.js 的加密貌似有点问题, 某些算法算出来跟别的语言 (比如 Python) 不一样. 具体情况还在整理中 (时间不定), 欢迎补充. + +> 加密是如何保证用户密码的安全性? + +在客户端加密, 是增加传输的过程中被第三方嗅探到密码后破解的成本. 对于游戏, 在客户端加密是防止外挂/破解等. 在服务端加密 (如 md5) 是避免管理数据库的 DBA 或者攻击者攻击数据库之后直接拿到明文密码, 从而提高安全性. + + +## TLS/SSL + +早期的网络传输协议由于只在大学内使用, 所以是默认互相信任的. 所以传统的网络通信可以说是没有考虑网络安全的. 早年的浏览器大厂网景公司为了应对这个情况设计了 SSL (Secure Socket Layer), SSL 的主要用途是: + +1. 认证用户和服务器, 确保数据发送到正确的客户机和服务器; +2. 加密数据以防止数据中途被窃取; +3. 维护数据的完整性, 确保数据在传输过程中不被改变. + +存在三个特性: + +* 机密性:SSL协议使用密钥加密通信数据 +* 可靠性:服务器和客户都会被认证, 客户的认证是可选的 +* 完整性:SSL协议会对传送的数据进行完整性检查 + +1999年, SSL 因为应用广泛, 已经成为互联网上的事实标准. IETF 就在那年把 SSL 标准化/强化. 标准化之后的名称改为传输层安全协议 (Transport Layer Security, TLS). 很多相关的文章都把这两者并列称呼 (TLS/SSL), 因为这两者可以视作同一个东西的不同阶段. + + +## HTTPS + +在网络上, 每个网站都在各自的服务器上, 想要确保你访问的是一个正确的网站, 并且访问到这个网站正确的数据 (没有被劫持/篡改), 除了需要传输安全之外, 还需要安全的认证, 认证不能由目标网站进行, 否则恶意/钓鱼网站也可以自己说自己是对的, 所以为了能在网络上维护网络之间的基本信任, 早期的大厂们合力推动了一项名为 PKI 的基础设施, 通过第三方来认证网站. + +公钥基础设施 (Public Key Infrastructure, PKI) 是一种遵循标准的, 利用公钥加密技术为电子商务的开展提供一套安全基础平台的技术和规范. 其基础建置包含认证中心 (Certification Authority, CA) 、注册中心 (Register Authority, RA) 、目录服务 (Directory Service, DS) 服务器. + +由 RA 统筹、审核用户的证书申请, 将证书申请送至 CA 处理后发出证书, 并将证书公告至 DS 中. 在使用证书的过程中, 除了对证书的信任关系与证书本身的正确性做检查外, 并透过产生和发布证书废止列表 (Certificate Revocation List, CRL) 对证书的状态做确认检查, 了解证书是否因某种原因而遭废弃. 证书就像是个人的身分证, 其内容包括证书序号、用户名称、公开金钥 (Public Key) 、证书有效期限等. + +在 TLS/SLL 中你可以使用 OpenSSL 来生成 TLS/SSL 传输时用来认证的 public/private key. 不过这个 public/private key 是自己生成的, 而通过 PKI 基础设施可以获得权威的第三方证书 (key) 从而加密 HTTP 传输安全. 目前博客圈子里比较流行的是 [Let's Encrypt 签发免费的 HTTPS 证书](https://imququ.com/post/letsencrypt-certificate.html). + +需要注意的是, 如果 PKI 受到攻击, 那么 HTTPS 也一样不安全. 可以参见 [HTTPS 劫持 - 知乎讨论](https://www.zhihu.com/question/22795329) 中的情况, 证书由 CA 机构签发, 一般浏览器遇到非权威的 CA 机构是会告警的 (参见 [12306](https://kyfw.12306.cn/otn/)), 但是如果你在某些特殊的情况下信任了某个未知机构/证书, 那么也可能被劫持. + +此外有的 CA 机构以邮件方式认证, 那么当某个网站的邮件服务收到攻击/渗透, 那么攻击者也可能以此从 CA 机构获取权威的正确的证书. + + +## XSS + +跨站脚本 (Cross-Site Scripting, XSS) 是一种代码注入方式, 为了与 CSS 区分所以被称作 XSS. 早期常见于网络论坛, 起因是网站没有对用户的输入进行严格的限制, 使得攻击者可以将脚本上传到帖子让其他人浏览到有恶意脚本的页面, 其注入方式很简单包括但不限于 JavaScript / VBScript / CSS / Flash 等. + +当其他用户浏览到这些网页时, 就会执行这些恶意脚本, 对用户进行 Cookie 窃取/会话劫持/钓鱼欺骗等各种攻击. 其原理, 如使用 js 脚本收集当前用户环境的信息 (Cookie 等), 然后通过 img.src, Ajax, onclick/onload/onerror 事件等方式将用户数据传递到攻击者的服务器上. 钓鱼欺骗则常见于使用脚本进行视觉欺骗, 构建假的恶意的 Button 覆盖/替换真实的场景等情况 (该情况在用户上传 CSS 的时候也可能出现, 如早起淘宝网店装修, 使用 CSS 拼接假的评分数据等覆盖在真的评分数据上误导用户). + +> 过滤 Html 标签能否防止 XSS? 请列举不能的情况? + +用户除了上传 + +```html + +``` + +还可以使用图片 url 等方式来上传脚本进行攻击 + +```html +
+ +``` + +还可以使用各种方式来回避检查, 例如空格, 回车, Tab + +```html + +``` + +还可以通过各种编码转换 (URL 编码, Unicode 编码, HTML 编码, ESCAPE 等) 来绕过检查 + +``` + + +``` + +### CPS 策略 + +在百般无奈, 没有统一解决方案的情况下, 厂商们推出了 CPS 策略. + +以 Node.js 为例, 计算脚本的 hashes 值: +``` +const crypto = require('crypto'); + +function getHashByCode(code, algorithm = 'sha256') { + return algorithm + '-' + crypto.createHash(algorithm).update(code, 'utf8').digest("base64"); +} + +getHashByCode('console.log("hello world");'); // 'sha256-wxWy1+9LmiuOeDwtQyZNmWpT0jqCUikqaqVlJdtdh/0=' +``` + +设置 CSP 头: + +``` +content-security-policy: script-src 'sha256-wxWy1+9LmiuOeDwtQyZNmWpT0jqCUikqaqVlJdtdh/0=' +``` + +```html + + +``` + +策略指令可以参见 [CSP Policy Directives](https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives)以及[阮一峰的博文](http://www.ruanyifeng.com/blog/2016/09/csp.html), [屈大神的博文](https://imququ.com/post/content-security-policy-reference.html) + + +## CSRF + +跨站请求伪造 (Cross-Site Request Forgery, CSRF, https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet) 是一种伪造跨站请求的攻击方式. 例如利用你在 A 站 (攻击目标) 的 cookie / 权限等, 在 B 站 (恶意/钓鱼网站) 拼装 A 站的请求. + +比如 Q 君是某论坛管理员. 已知这个论坛 A 删除的接口是 post 到某个地址, 并指定一个帖子的 id. 那么我可以在自己的博客 B 上组织一个 CSRF 请求. 然后诱使 Q 君来访问我的博客. 就可以在 Q 君不知情的情况下删除掉我想删的某个帖子. + +钓鱼方式包括但不限于公开网站 (xss), 攻击者的恶意网站, email 邮件, 微博, 微信, 短信等及时消息. + +同源策略是最早用于防止 CSRF 的一种方式, 即关于跨站请求 (Cross-Site Request) 只有在同源/信任的情况下才可以请求. 但是如果一个网站群, 在互相信任的情况下, 某个网站出现了问题: + +``` +a.public.com +b.public.com +c.public.com +... +``` + +以上情况下, 如果 c.public.com 上没有预防 xss 等情况, 使得攻击者可以基于此站对其他信任的网站发起 CSRF 攻击. + +另外同源策略主要是浏览器来进行验证的, 并且不同浏览器的实现又各自不同, 所以在某些浏览器上可以直接绕过, 而且也可以直接通过短信等方式直接绕过浏览器. + +预防: + +1. A 站 (预防站) 检查 http 请求的 header 确认其 origin +2. 检查 CSRF token + +### 1.同源检查 + +通过检查来过滤简单的 CSRF 攻击, 主要检查一下两个 header: + +* Origin Header +* Referer Header + +### 2.CSRF token + +简单来说, 对需要预防的请求, 通过特别的算法生成 token 存在 session 中, 然后将 token 隐藏在正确的界面表单中, 正式请求时带上该 token 在服务端验证, 避免跨站请求. + + +## 中间人攻击 + +中间人 (Man-in-the-middle attack, MITM) 是指攻击者与通讯的两端分别创建独立的联系, 并交换其所收到的数据, 使通讯的两端认为他们正在通过一个私密的连接与对方直接对话, 但事实上整个会话都被攻击者完全控制. 在中间人攻击中, 攻击者可以拦截通讯双方的通话并插入新的内容. + +目前比较常见的是在公共场所放置精心准备的免费 wifi, 劫持/监控通过该 wifi 的流量. 或者攻击路由器, 连上你家 wifi 攻破你家 wifi 之后在上面劫持流量等. + +对于通信过程中的 MITM, 常见的方案是通过 PKI / TLS 预防, 及时是通过存在第三方中间人的 wifi 你通过 HTTPS 访问的页面依旧是安全的. 而 HTTP 协议是明文传输, 则没有任何防护可言. + +不常见的还有强力的互相认证, 你确认他之后, 他也确认你一下; 延迟测试, 统计传输时间, 如果通讯延迟过高则认为可能存在第三方中间人; 等等. + +## SQL/NoSQL 注入 + +注入攻击是指当所执行的一些操作中有部分由用户传入时, 用户可以将其恶意逻辑注入到操作中. 当你使用 eval, new Function 等方式执行的字符串中有用户输入的部分时, 就可能被注入攻击. 上文中的 XSS 就属于一种注入攻击. 前面的章节中也提到过 Node.js 的 child_process.exec 由于调用 bash 解析, 如果执行的命令中有部分属于用户输入, 也可能被注入攻击. + +### SQL + +Sql 注入是网站常见的一种注入攻击方式. 其原因主要是由于登录时需要验证用户名/密码, 其执行 sql 类似: + +```sql +SELECT * FROM users WHERE usernae = 'myName' AND password = 'mySecret'; +``` + +其中的用户名和密码属于用户输入的部分, 那么在未做检查的情况下, 用户可能拼接恶意的字符串来达到其某种目的, 例如上传密码为 `'; DROP TABLE users; --` 使得最终执行的内容为: + +```sql +SELECT * FROM users WHERE usernae = 'myName' AND password = ''; DROP TABLE users; --'; +``` + +其能实现的功能, 包括但不限于删除数据 (经济损失), 篡改数据 (密码等), 窃取数据 (网站管理权限, 用户数据) 等. 防治手段常见于: + +* 给表名/字段名加前缀 (避免被猜到) +* 报错隐藏表信息 (避免被看到, 12306 早起就出现过的问题) +* 过滤可以拼接 SQL 的关键字符 +* 对用户输入进行转义 +* 验证用户输入的类型 (避免 limit, order by 等注入) +* 等... + +### NoSQL + +看个简单的情况: + +```javascript +let {user, pass, age} = ctx.query; + +db.collection.find({ + user, pass, + $where: `this.age >= ${age}` +}) +``` + +那么这里的 age 就可以注入了. 另外 GET/POST 还可以传递深层结构 (比如 ?name[0]=alan 传递上来), 通过 qs 之类的模块解析后导致注入, 如 [cnodejs 遭遇 mongodb 注入](https://github.com/cnodejs/nodeclub/commit/0f6cc14f6bcbbe6b4de3199c6896efaec637693e). + diff --git a/sections/zh-cn/storage.md b/sections/zh-cn/storage.md new file mode 100644 index 0000000..5e6a6e0 --- /dev/null +++ b/sections/zh-cn/storage.md @@ -0,0 +1,168 @@ +# 存储 + +* `[Point]` Sql +* `[Point]` NoSql +* `[Point]` 缓存 +* `[Point]` 数据一致性 + +## 简介 + +科班的同学可以了解一下[数据库范式](http://www.cnblogs.com/CareySon/archive/2010/02/16/1668803.html), 在 ElemeFe 面试不会问, 但是其他地方可能会问 (比如阿里). + + +## Mysql + +SQL (Structured Query Language) 是[关系式数据库管理系统](https://en.wikipedia.org/wiki/Relational_database)的标准语言, 关于关系型数据库这里主要带大家看一下 Mysql 的几个问题 + +### 存储引擎 + +|attr|MyISAM|InnoDB| +|----|----|----| +|Locking|Table-level|Row-level| +|designed for|need of speed|high volume of data| +|foreign keys | × (DBMS) | ✓ (RDBMS)| +|transaction | × | ✓ | +|fulltext search | ✓ | × | +|scene| lots of select | lots of insert/update | +|count rows| fast | slow | +|auto_increment | fast | slow | + +* 你的数据库有外键吗? +* 你需要事务支持吗? +* 你需要全文索引吗? +* 你经常使用什么样的查询模式? +* 你的数据有多大? + +参见 [MYSQL: INNODB 还是 MYISAM?](http://coolshell.cn/articles/652.html) + +### 索引 + +索引是用空间换时间的一种优化策略. 推荐阅读: [mysql索引类型](http://www.cnblogs.com/cq-home/p/3482101.html) 以及 [主键与唯一索引的区别](http://blog.mimvp.com/2015/03/the-difference-between-primary-key-and-unique-index/) + + +## Mongodb + +> Monogdb 连接问题(超时/断开等)有可能是什么问题导致的? + +* 网络问题 +* 任务跑不完, 超过了 driver 的默认链接超时时间 (如 30s) +* Monogdb 宕机了 +* 超过了连接空闲时间 (connection idle time) 被断开 +* fd 不够用 (ulimit 设置) +* mongodb 最大连接数不够用 (可能是连接未复用导致) +* etc... + +### other + +populate + +aggregate + +pipeline + +Cursor + +整理中 + +## Replication + +> 备份数据库与 M/S, M/M 等部署方式的区别? + +关于数据库基于各种模式的特点全部可以通过以下图片分清: + +![storage](/assets/storage.jpeg) + +图片出处:Google App Engine 的 co-founder Ryan Barrett 在 2009 年的 google i/o 上的演讲 [《Transaction Across DataCenter》](http://snarfed.org/transactions_across_datacenters_io.html)(视频: http://www.youtube.com/watch?v=srOgpXECblk) + +根据上图, 我们可以知道 Master/Slave 与 Master/Master 的关系. + + + + + + + + + +
attrMaster/SlaveMaster/Master
一致性Eventually:当你写入一个新值后,有可能读不出来,但在某个时间窗口之后保证最终能读出来。比如:DNS,电子邮件、Amazon S3,Google搜索引擎这样的系统。
事务完整本地
延迟低延迟
吞吐高吞吐
数据丢失部分丢失
熔断只读读/写
+ +### 读写分离 + +读写分离是在 query 量大的情况下减轻单个 DB 节点压力, 优化数据库读/写速度的一种策略. 不论是 MySQL 还是 MongoDB 都可以进行读写分离. + +读写分离的配置方式直接搜索一下 `数据库名 + 读写分离` 即可找到. 通常是 M/S 的情况, 使用 Master 专门写, 用 Slave 节点专门读. 使用读写分离时, 请确认读的请求对一致性要求不高, 因为从写库同步读库是有延迟的. + + +## 数据一致性 + +关于数据一致性推荐看陈皓的[分布式系统的事务处理](http://www.infoq.com/cn/articles/distributed-system-transaction-processing) + +> 什么情况下数据会出现脏数据? 如何避免? + +* 从 A 帐号中把余额读出来 +* 对 A 帐号做减法操作 +* 把结果写回 A 帐号中 +* 从 B 帐号中把余额读出来 +* 对 B 帐号做加法操作 +* 把结果写回 B 帐号中 + +为了数据的一致性, 这6件事, 要么都成功做完, 要么都不成功, 而且这个操作的过程中, 对A、B帐号的其它访问必需锁死, 所谓锁死就是要排除其它的读写操作, 否则就会出现脏数据 ---- 即数据一致性的问题. + +这个问题并不仅仅出现在数据库操作中, 普通的并发以及并行操作都可能导致出现脏数据. 避免出现脏数据通常是从架构上避免或者采用事务的思想处理. + +### 矛盾 + +* 1)要想让数据有高可用性,就得写多份数据 +* 2)写多份的问题会导致数据一致性的问题 +* 3)数据一致性的问题又会引发性能问题 + +强一致性必然导致性能短板, 而弱一致性则有很好的性能但是存在数据安全(灾备数据丢失)/一致性(脏读/脏写等)的问题. + +目前 Node.js 业内流行的主要是与 Mongodb 配合, 在数据一致性方面属于短板. + +### 事务 + +事务并不仅仅是 sql 数据库中的一个功能, 也是分布式系统开发中的一个思想, 事务在分布式的问题中可以称为 "两阶段提交" (以下引用陈皓原文) + +第一阶段: + +* 协调者会问所有的参与者结点,是否可以执行提交操作。 +* 各个参与者开始事务执行的准备工作:如:为资源上锁,预留资源,写undo/redo log…… +* 参与者响应协调者,如果事务的准备工作成功,则回应“可以提交”,否则回应“拒绝提交”。 + +第二阶段: + +* 如果所有的参与者都回应“可以提交”,那么,协调者向所有的参与者发送“正式提交”的命令。参与者完成正式提交,并释放所有资源,然后回应“完成”,协调者收集各结点的“完成”回应后结束这个Global Transaction。 +* 如果有一个参与者回应“拒绝提交”,那么,协调者向所有的参与者发送“回滚操作”,并释放所有资源,然后回应“回滚完成”,协调者收集各结点的“回滚”回应后,取消这个Global Transaction。 + +异常: + +* 如果第一阶段中,参与者没有收到询问请求,或是参与者的回应没有到达协调者。那么,需要协调者做超时处理,一旦超时,可以当作失败,也可以重试。 +* 如果第二阶段中,正式提交发出后,如果有的参与者没有收到,或是参与者提交/回滚后的确认信息没有返回,一旦参与者的回应超时,要么重试,要么把那个参与者标记为问题结点剔除整个集群,这样可以保证服务结点都是数据一致性的。 +* 第二阶段中,如果参与者收不到协调者的commit/fallback指令,参与者将处于“状态未知”阶段,参与者完全不知道要怎么办。 + + +## 缓存 + +> redis 与 memcached 的区别? + +|attr|memcached|redis| +|----|----|----| +|struct|key/value|key/value + list, set, hash etc. | +|backup | × | ✓ | +|Persistence | × | ✓ | +|transcations | × | ✓ | +|consistency | strong (by cas) | weak | +|thread | multi | single | +|memory | physical | physical & swap | + + +## 其他 + +* zookeeper +* kafka +* storm +* hadoop +* spark + + diff --git a/sections/zh-cn/test.md b/sections/zh-cn/test.md new file mode 100644 index 0000000..7a6f343 --- /dev/null +++ b/sections/zh-cn/test.md @@ -0,0 +1,256 @@ +# 测试 + +* [`[Basic]` 测试方法](#测试方法) +* [`[Basic]` 单元测试](#单元测试) +* [`[Basic]` 基准测试](#集成测试) +* [`[Basic]` 集成测试](#基准测试) +* [`[Basic]` 压力测试](#压力测试) +* [`[Doc]` Assert (断言)](#assert) + +## 简述 + +> 为什么要写测试? 写测试是否会拖累开发进度? + +项目在多人合作的时候, 为了某个功能修改了某个模块的某部分代码, 实际的情况中修改一个地方可能会影响到别人开发的多个功能, 在自己不知情的情况下想要保证自己修改的代码不影响到其他功能, 最简单的办法是通过测试来保证. + +``` +A + \ + E + / \ +B H + \ / + F + / +C + \ + G + / +D +``` + +如上述情况, ABCD 是逻辑层, EFGH 等是更低一次层 (比如工具层等), 当你为了功能 A 的 BUG 修改了 H 的代码, 那么实际受影响的功能除了 A 之外还有 BC, 如果你有针对每一个逻辑的测试, 那么修改了 H 的代码之后, 跑一遍测试即可保证对 H 的修改不会影响到 BC (如果有影响, 那么相应的测试会报错). 利用这种特性, 你还可以基于测试去做重构, 在通过原有测试的情况下, 即表明新的重构版本可以替代原有的版本. + +而这样的效果, 只有当覆盖率达到了一定程度 (通常是 80% 以上, 90% 以上为最理想) 才能实现, 如果测试的覆盖率低, 无法覆盖到多种情况, 那么测试对你的项目可能是没有用甚至起到反作用的 (让你误以为你的修改没问题而发布等). + +写测试是否会拖累开发进度要视具体情况而定. 需要考虑到, 开发进度包含功能和品质两个方面, 单纯写代码的速度不能完全代表开发进度. 测试在适当的情况下可以保证项目的品质从而得到更好的开发进度. + +如上述的例子, 在修改功能 A 的 BUG 的时候, 如果你不知道 H 会影响到 BC 又没有测试的话, 那么开发 BC 的同学可能会出现十分经典的 **"昨天还好好的, 今天怎么就不能用了?"** 的情况. + +当然写测试拖累开发进度的情况也是客观存在的, 通常是有以下几种情况: + +* 不会写测试 +* 过度测试, 不必要的测试 +* 为了迎合测试, 而忽略了实际需求 + + +> 测试是如何保证业务逻辑中不会出现死循环的? + +你可以通过测试来避免坑爹的同事在某些逻辑中写出死循环, 在通常的测试中加上超时的时间, 在覆盖率足够的情况下, 就可以通过跑出超时的测试来排查出现死循环以及低性能的情况. + + +## 测试方法 + +### 黑盒测试 + +黑盒测试 (Black-box Testing), 测试应用程序的功能, 而不是其内部结构或运作. 测试者不需了解代码、内部结构等, 只需知道什么是应用应该做的事, 即当键入特定的输入, 可得到一定的输出. 测试者通过选择`有效输入`和`无效输入`来验证是否正确的输出. 此测试方法可适合大部分的软件测试, 例如集成测试 (Integration Testing) 以及系统测试 (System Testing). + +### 白盒测试 + +白盒测试 (White-box Testing) 测试应用程序的内部结构或运作, 而不是测试应用程序的功能 (即黑盒测试). 在白盒测试时, 以编程语言的角度来设计测试案例. 白盒测试可以应用于单元测试 (Unit Testing)、集成测试 (Integration Testing) 和系统的软件测试流程, 可测试在集成过程中每一单元之间的路径, 或者主系统跟子系统中的测试. + + +## 单元测试 + +单元测试 (Unit Testing) 是白盒测试的一种, 用于针对程序模块进行正确性检验的测试工作. 单元 (Unit) 是指**最小可测试的部件**. 在过程化编程中, 一个单元就是单个程序、函数、过程等; 对于面向对象编程, 最小单元就是方法, 包括基类、抽象类、或者子类中的方法. + +另外, 每次修改代码之后, 通过单元测试来验证比把整个应用启动/重启验证要更快/更简单. + +### 覆盖率 + +测试覆盖率 (Test Coverage) 是指代码中各项逻辑被测试覆盖到的比率, 比如 90% 的覆盖率, 是指代码中 90% 的情况都被测试覆盖到了. + +覆盖率通常由四个维度贡献: + +* 行覆盖率 (line coverage) 是否每一行都执行了? +* 函数覆盖率 (function coverage) 是否每个函数都调用了? +* 分支覆盖率 (branch coverage) 是否每个if代码块都执行了? +* 语句覆盖率 (statement coverage) 是否每个语句都执行了? + +常用的测试覆盖率框架 [istanbul](https://github.com/gotwarlost/istanbul). + +当然覆盖率并不完全是由单元测试贡献, 在单元测试之上还有集成测试等. 更多关于覆盖率的内容可以参见[测试覆盖(率)到底有什么用?](http://www.infoq.com/cn/articles/test-coverage-rate-role) + +### Mock + +Mock 主要用于单元测试中. 当一个测试的对象可能依赖其他 (也许复杂/多个) 的对象. 为了确保其行为不受其他对象的影响, 你可以通过模拟其他对象的行为来隔离你要测试的对象. + +当你要测试的单元依赖了一些很难纳入单元测试的情况时 (例如要测试的单元依赖数据库/文件操作/第三方服务 等情况的返回时), 使用 mock 是非常有用的. 简而言之, Mock 是模拟其他依赖的 behaviour. + +Mock 与 Stub 的区别参见: [Mocks Aren't Stubs](https://martinfowler.com/articles/mocksArentStubs.html) + + +### 常见测试工具 + +* [Mocha](https://github.com/mochajs/mocha) +* [ava](https://github.com/avajs/ava) +* [Jest](https://github.com/facebook/jest) + + +## 集成测试 + +集成测试也称综合测试、组装测试、联合测试, 将程序模块采用适当的集成策略组装起来, 对系统的接口及集成后的功能进行正确性检测的测试工作. 集成测试可以是黑盒的, 也可以是白盒的, 其主要目的是检查软件单位之间的接口是否正确, 而集成测试的对象是**已经经过单元测试的模块**. + +例如你可以在本地将项目中的 web app 启动, 并模拟接口调用: + +```javascript +describe('Path API', () => { + // ... + + describe('GET /v2/path/:_id', () => { + it('should return 200 GET /v2/path/:_id', () => { + return request + .get('/v2/path/' + pathId) + .set('Cookie', 'common_user=xxx') + .expect(200); + }); + }); + + describe('POST /v2/path', () => { + it('should return 412 POST /v2/path lost params path', () => { + return request + .post('/v2/path') + .set('Cookie', 'common_user=xxx') + .expect(412); + }); + + it('should return 409 POST /v2/path when path exist', () => { + return request + .post('/v2/path') + .send({path: '/'}) + .set('Cookie', 'common_user=xxx') + .expect(409); + }); + + it('should return 200 POST /v2/path successfully', () => { + return request + .post('/v2/path') + .send({path: '/comment'}) + .set('Cookie', 'common_user=xxx') + .expect(200); + }); + }); + + // ... +}); +``` + +## 基准测试 + +目前 Node.js 中流行的白盒级基准测试工具是 [benchmark](https://benchmarkjs.com/docs). + +```javascript +const Benchmark = require('benchmark'); +const suite = new Benchmark.Suite; + +suite.add('RegExp#test', function() { + /o/.test('Hello World!'); +}) +.add('String#indexOf', function() { + 'Hello World!'.indexOf('o') > -1; +}) +.on('cycle', function(event) { + console.log(String(event.target)); +}) +.on('complete', function() { + console.log('Fastest is ' + this.filter('fastest').map('name')); +}) +// run async +.run({ 'async': true }); +``` + +你可以将同一个功能的不同实现基于同一个标准来比较不同实现的速度, 从而得到最优解. + +黑盒级别的基准测试, 则推荐 [Apache ab](https://httpd.apache.org/docs/2.4/programs/ab.html) 以及 [wrk](https://github.com/wg/wrk) 等, 例如执行: + +``` +ab -n 100 -c 10 https://ele.me/ +``` + +可以得到如下的详细数据: + +``` +Server Software: Tengine/2.1.1 +Server Hostname: ele.me +Server Port: 443 +SSL/TLS Protocol: TLSv1.2,ECDHE-RSA-AES256-GCM-SHA384,2048,256 + +Document Path: / +Document Length: 284 bytes + +Concurrency Level: 10 +Time taken for tests: 1.775 seconds +Complete requests: 100 +Failed requests: 0 +Non-2xx responses: 100 +Total transferred: 62400 bytes +HTML transferred: 28400 bytes +Requests per second: 56.33 [#/sec] (mean) +Time per request: 177.511 [ms] (mean) +Time per request: 17.751 [ms] (mean, across all concurrent requests) +Transfer rate: 34.33 [Kbytes/sec] received + +Connection Times (ms) + min mean[+/-sd] median max +Connect: 88 116 26.0 104 234 +Processing: 33 55 39.6 47 394 +Waiting: 33 54 39.0 46 394 +Total: 124 171 48.1 152 491 + +Percentage of the requests served within a certain time (ms) + 50% 152 + 66% 184 + 75% 193 + 80% 199 + 90% 224 + 95% 242 + 98% 288 + 99% 491 + 100% 491 (longest request) +``` + +与前者相比, ab 等工具可以设置规模以及并发情况. 在比规模不大/需求不复杂的情况下, ab 以及 wrk 也可以用于做压力测试. + + +## 压力测试 + +压力测试 (Stress testing), 是保证系统稳定性的一种测试方法. 通过预估系统所需要承载的 QPS, TPS 等指标, 然后通过如 [Jmeter](http://jmeter.apache.org/) 等压测工具模拟相应的请求情况, 来验证当前应能能否达到目标. + +对于比较重要, 流量较高或者后期业务量会持续增长的系统, 进行压力测试是保证项目品质的重要环节. 常见的如负载是否均衡, 带宽是否合理, 以及磁盘 IO 网络 IO 等问题都可以通过比较极限的压力测试暴露出来. + + +## Assert + +断言 (Assert) 是快速判断并对不符合预期的情况进行报错的模块. 是将: + +```javascript +if (condition) { + throw new Error('Sth wrong'); +} +``` + +写成: + +```javascript +assert(!condition, 'Sth wrong'); +``` + +等等情况的一种简化. 并且提供了丰富了 `equal` 判断, 对于对象类型也有深度/严格判断等情况支持. + +Node.js 中内置的 `assert` 模块也是属于断言模块的一种, 但是官方在文档中有注明, 该内置模块主要是用于内置代码编写时的基本断言需求, 并不是一个通用的断言库 (**not intended to be used as a general purpose assertion library**) + +### 常见断言工具 + +* [Chai](https://github.com/chaijs/chai) +* [should.js](https://github.com/shouldjs/should.js) + diff --git a/sections/zh-cn/util.md b/sections/zh-cn/util.md new file mode 100644 index 0000000..c49f404 --- /dev/null +++ b/sections/zh-cn/util.md @@ -0,0 +1,230 @@ +# util + +* `[Doc]` URL +* `[Doc]` Query Strings (查询字符串) +* `[Doc]` Utilities (实用函数) +* `[Basic]` 正则表达式 + + +## URL + +```javascript +┌─────────────────────────────────────────────────────────────────────────────┐ +│ href │ +├──────────┬┬───────────┬─────────────────┬───────────────────────────┬───────┤ +│ protocol ││ auth │ host │ path │ hash │ +│ ││ ├──────────┬──────┼──────────┬────────────────┤ │ +│ ││ │ hostname │ port │ pathname │ search │ │ +│ ││ │ │ │ ├─┬──────────────┤ │ +│ ││ │ │ │ │ │ query │ │ +" http: // user:pass @ host.com : 8080 /p/a/t/h ? query=string #hash " +│ ││ │ │ │ │ │ │ │ +└──────────┴┴───────────┴──────────┴──────┴──────────┴─┴──────────────┴───────┘ +``` + +### 转义字符 + +常见的需要转移的字符列表: + +|字符|encodeURI| +|---|---| +|`' '`|`'%20'`| +|`<`|`'%3C'`| +|`>`|`'%3E'`| +|`"`|`'%22'`| +|```|`'%60'`| +|`\r`|`'%0D'`| +|`\n`|`'%0A'`| +|`\t`|`'%09'`| +|`{`|`'%7B'`| +|`}`|`'%7D'`| +|`|`|`'%7C'`| +|`\\`|`'%5C'`| +|`^`|`'%5E'`| +|`'`|'%27'| + +想了解更多? 你可以这样: + +```javascript +Array(range).fill(0) + .map((_, i) => String.fromCharCode(i)) + .map(encodeURI) +``` + +range 先来个 255 试试 (doge + + +## Query Strings + +query string 属于 URL 的一部分, 见上方 URL 的表. 在 Node.js 中有内置提供一个 `querystring` 的模块. + +|方法|描述| +|---|---| +|.parse(str[, sep[, eq[, options]]])|将一个 query string 解析为 json 对象| +|.unescape(str)|供 .parse 调用的内置解转义方法, 暴露出来以供用户自行替代| +|.stringify(obj[, sep[, eq[, options]]])|将一个 json 对象转换成 query string| +|.escape(str)|供 .stringify 调用的内置转义方法, 暴露出来以供用户自行替代| + +Node.js 内置的 querystring 目前对于有深度的结构尚不支持. 见如下: + +```javascript +const qs = require('qs'); // 第三方 +const querystring = require('querystring'); // Node.js 内置 + +let obj = { a: { b: { c: 1 } } }; + +console.log(qs.stringify(obj)); // 'a%5Bb%5D%5Bc%5D=1' +console.log(querystring.stringify(obj)); // 'a=' + +let str = 'a%5Bb%5D%5Bc%5D=1'; + +console.log(qs.parse(str)); // { a: { b: { c: '1' } } } +console.log(querystring.parse(str)); // { 'a[b][c]': '1' } +``` + +> HTTP 如何通过 GET 方法 (URL) 传递 let arr = [1,2,3,4] 给服务器? + +```javascript +const qs = require('qs'); + +let arr = [1,2,3,4]; +let str = qs.stringify({arr}); + +console.log(str); // arr%5B0%5D=1&arr%5B1%5D=2&arr%5B2%5D=3&arr%5B3%5D=4 +console.log(decodeURI(str)); // 'arr[0]=1&arr[1]=2&arr[2]=3&arr[3]=4' +console.log(qs.parse(str)); // { arr: [ '1', '2', '3', '4' ] } +``` + +通过 `https://your.host/api/?arr[0]=1&arr[1]=2&arr[2]=3&arr[3]=4` 即可传递把 arr 数组传递给服务器 + + +## util + +util.is*() 从 v4.0.0 开始被不建议使用即将废弃 (deprecated). 大概的废弃原因, 笔者个人认为是维护这些功能吃力不讨好, 而且现在流行的轮子那么多. 那么一下是具体列表: + +* util.debug(string) +* util.error([...strings]) +* util.isArray(object) +* util.isBoolean(object) +* util.isBuffer(object) +* util.isDate(object) +* util.isError(object) +* util.isFunction(object) +* util.isNull(object) +* util.isNullOrUndefined(object) +* util.isNumber(object) +* util.isObject(object) +* util.isPrimitive(object) +* util.isRegExp(object) +* util.isString(object) +* util.isSymbol(object) +* util.isUndefined(object) +* util.log(string) +* util.print([...strings]) +* util.puts([...strings]) +* util._extend(target, source) + +其中大部分都可以作为面试题来问如何实现. + +### util.inherits + +> Node.js 中继承 (util.inherits) 的实现? + +https://github.com/nodejs/node/blob/v7.6.0/lib/util.js#L960 + +```javascript +/** + * Inherit the prototype methods from one constructor into another. + * + * The Function.prototype.inherits from lang.js rewritten as a standalone + * function (not on Function.prototype). NOTE: If this file is to be loaded + * during bootstrapping this function needs to be rewritten using some native + * functions as prototype setup using normal JavaScript does not work as + * expected during bootstrapping (see mirror.js in r114903). + * + * @param {function} ctor Constructor function which needs to inherit the + * prototype. + * @param {function} superCtor Constructor function to inherit prototype from. + * @throws {TypeError} Will error if either constructor is null, or if + * the super constructor lacks a prototype. + */ +exports.inherits = function(ctor, superCtor) { + + if (ctor === undefined || ctor === null) + throw new TypeError('The constructor to "inherits" must not be ' + + 'null or undefined'); + + if (superCtor === undefined || superCtor === null) + throw new TypeError('The super constructor to "inherits" must not ' + + 'be null or undefined'); + + if (superCtor.prototype === undefined) + throw new TypeError('The super constructor to "inherits" must ' + + 'have a prototype'); + + ctor.super_ = superCtor; + Object.setPrototypeOf(ctor.prototype, superCtor.prototype); +}; +``` + +## 正则表达式 + +正则表达式最早生物学上用来描述大脑神经元的一种表达式, 被 GNU 的大胡子拿来做字符串匹配之后在原本的道路上渐行渐远. + +整理中.. + +## 常用模块 + +[Awesome Node.js](https://github.com/sindresorhus/awesome-nodejs) +[Most depended-upon packages](https://www.npmjs.com/browse/depended) + +> 如何获取某个文件夹下所有的文件名? + +一个简单的例子: + +```javascript +const fs = require('fs'); +const path = require('path'); + +function traversal(dir) { + let res = [] + for (let item of fs.readdirSync(dir)) { + let filepath = path.join(dir, item); + try { + let fd = fs.openSync(filepath, 'r'); + let flag = fs.fstatSync(fd).isDirectory(); + fs.close(fd); // TODO + if (flag) { + res.push(...traversal(filepath)); + } else { + res.push(filepath); + } + } catch(err) { + if (err.code === 'ENOENT' && // link 文件打不开 + !!fs.readlinkSync(filepath)) { // 判断是否 link 文件 + res.push(filepath); + } else { + console.error('err', err); + } + } + } + return res.map((file) => path.basename(file)); +} + +console.log(traversal('.')); + + +``` + +当然也可以 Oh my [glob](https://github.com/isaacs/node-glob): + +```javascript +const glob = require("glob"); + +glob("**/*.js", (err, files) { + if (err) { + throw new Error(err); + } + console.log('Here you are:', files.map(path.basename)); +}); +``` From f40bad630f82ae107f970973db7d52c8afc1382f Mon Sep 17 00:00:00 2001 From: Lellansin Date: Sun, 21 May 2017 03:33:25 +0800 Subject: [PATCH 38/98] [doc]: home readme, update detail --- README.md | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 54460fc..090f9a3 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,27 @@ ## What's this? -This is a interview Index about Node.js which you should know while you are intersting about the Job of ElemeFE. +We were looking for developer which had three years development experience with Node.js & Backend dev. + +This is the interview Index about it, and it shows you how to pass the Node.js interview of ElemeFE. ## What is ElemeFE? -It means Frontend team of Eleme (饿了么) which seems like the bigest takeaway company. +It means Frontend team of Eleme (饿了么) which seems like the bigest takeaway company of China. + +There are some public projects of Eleme: + +* [Element](http://element.eleme.io/#/en-US) A Vue.js 2.0 UI Toolkit for Web. +* [Element-react](https://github.com/eleme/element-react) React.js version for Element. +* [Mint UI](http://mint-ui.github.io/#!/en) Mobile UI elements for Vue.js. +* [restc](https://elemefe.github.io/restc/) A server-side middleware to visualize REST requests. +* [vue-infinite-scroll](https://github.com/ElemeFE/vue-infinite-scroll) An infinite scroll directive for vue.js. + +As you see, we are frontend team, but we'v been doing some backend job to support frontend to gain more. + +#### Language support -#### Support languages +This index is translated from chinese, We'd say sorry that the translation is just started, you may not able to read it completely. BTW, it is welcome to help us to improve the translations. ##### [English](sections/en-us/) ##### [简体中文](sections/zh-cn/) From 005adb22270be300fd2562f2e74f6def9f344d25 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Sun, 21 May 2017 03:39:09 +0800 Subject: [PATCH 39/98] doc: sections readme, fix picture URL --- sections/en-us/README.md | 2 +- sections/zh-cn/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sections/en-us/README.md b/sections/en-us/README.md index d796162..9c2131f 100644 --- a/sections/en-us/README.md +++ b/sections/en-us/README.md @@ -1,4 +1,4 @@ -![ElemeFE-background](assets/ElemeFE-background.png) +![ElemeFE-background](/assets/ElemeFE-background.png) # Node interview of ElemeFE diff --git a/sections/zh-cn/README.md b/sections/zh-cn/README.md index 2840334..088f8eb 100644 --- a/sections/zh-cn/README.md +++ b/sections/zh-cn/README.md @@ -1,4 +1,4 @@ -![ElemeFE-background](assets/ElemeFE-background.png) +![ElemeFE-background](/assets/ElemeFE-background.png) # 如何通过饿了么 Node.js 面试 From 4f6dafa36515b016223e1db1c637ca58837424d8 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Sun, 21 May 2017 04:11:30 +0800 Subject: [PATCH 40/98] section: en-us, clear Chinese content and fill the catalog --- sections/en-us/README.md | 71 +++++- sections/en-us/error.md | 373 +------------------------------- sections/en-us/event-async.md | 232 +------------------- sections/en-us/io.md | 391 +--------------------------------- sections/en-us/js-basic.md | 131 +----------- sections/en-us/module.md | 138 +----------- sections/en-us/network.md | 338 +---------------------------- sections/en-us/os.md | 374 +------------------------------- sections/en-us/process.md | 253 +--------------------- sections/en-us/security.md | 207 +----------------- sections/en-us/storage.md | 168 +-------------- sections/en-us/test.md | 264 +---------------------- sections/en-us/util.md | 230 +------------------- 13 files changed, 133 insertions(+), 3037 deletions(-) diff --git a/sections/en-us/README.md b/sections/en-us/README.md index 9c2131f..3efab96 100644 --- a/sections/en-us/README.md +++ b/sections/en-us/README.md @@ -1,23 +1,31 @@ ![ElemeFE-background](/assets/ElemeFE-background.png) -# Node interview of ElemeFE +## Movition -Hi, welcome to ElemeFE ## Guide + ## [Basic](sections/en-us/js-basic.md) > It's much more diff between frontend and backend. +* `[Basic]` Type judgment +* `[Basic]` Scope +* `[Basic]` Reference +* `[Basic]` Memory release +* `[Basic]` ES6+ featrues **Common Problem** - [View more](sections/en-us/js-basic.md) ## [Module](sections/en-us/module.md) +* `[Basic]` Module +* `[Basic]` Hotfix +* `[Basic]` Context +* `[Basic]` Package Manager **Common Problem** @@ -26,6 +34,11 @@ Hi, welcome to ElemeFE ## [Event & Async](sections/en-us/event-async.md) +* `[Basic]` Promise +* `[Doc]` Events +* `[Doc]` Timers +* `[Point]` Blocking & Non-blocking +* `[Point]` Parallel & Concurrent **Common Problem** @@ -34,6 +47,11 @@ Hi, welcome to ElemeFE ## [Process](sections/en-us/process.md) +* `[Doc]` Process +* `[Doc]` Child Processes +* `[Doc]` Cluster +* `[Basic]` IPC +* `[Basic]` Daemon **Common Problem** @@ -43,6 +61,13 @@ Hi, welcome to ElemeFE ## [IO](sections/en-us/io.md) +* `[Doc]` Buffer +* `[Doc]` String Decoder +* `[Doc]` Stream +* `[Doc]` Console +* `[Doc]` File System +* `[Doc]` Readline +* `[Doc]` REPL **Common Problem** @@ -51,6 +76,12 @@ Hi, welcome to ElemeFE ## [Network](sections/en-us/network.md) +* `[Doc]` Net +* `[Doc]` UDP/Datagram +* `[Doc]` HTTP +* `[Doc]` DNS +* `[Doc]` ZLIB +* `[Point]` RPC **Common Problem** @@ -59,6 +90,12 @@ Hi, welcome to ElemeFE ## [OS](sections/en-us/os.md) +* `[Doc]` TTY +* `[Doc]` OS +* `[Doc]` Command Line Options +* `[Basic]` Load +* `[Point]` CheckList +* `[Basic]` Indicators **Common Problem** @@ -67,6 +104,13 @@ Hi, welcome to ElemeFE ## [Error/Debug/Opt](sections/en-us/error.md) +* `[Doc]` Errors +* `[Doc]` Domain +* `[Doc]` Debugger +* `[Doc]` C/C++ Addon +* `[Doc]` V8 +* `[Point]` Memory snapshot +* `[Point]` CPU Profilling **Common Problem** @@ -75,6 +119,12 @@ Hi, welcome to ElemeFE ## [Test](sections/en-us/test.md) +* `[Basic]` Methods +* `[Basic]` Unit Test +* `[Basic]` Benchmarks +* `[Basic]` Integration Test +* `[Basic]` Pressure Test +* `[Doc]` Assert **Common Problem** @@ -83,6 +133,10 @@ Hi, welcome to ElemeFE ## [Util](sections/en-us/util.md) +* `[Doc]` URL +* `[Doc]` Query Strings +* `[Doc]` Utilities +* `[Basic]` Regex **Common Problem** @@ -91,6 +145,10 @@ Hi, welcome to ElemeFE ## [Storage](sections/en-us/storage.md) +* `[Point]` Sql +* `[Point]` NoSql +* `[Point]` Cache +* `[Point]` Consistency **Common Problem** @@ -99,6 +157,13 @@ Hi, welcome to ElemeFE ## [Security](sections/en-us/security.md) +* `[Doc]` Crypto +* `[Doc]` TLS/SSL +* `[Doc]` HTTPS +* `[Point]` XSS +* `[Point]` CSRF +* `[Point]` MITM +* `[Point]` Sql/Nosql Injection **Common Problem** diff --git a/sections/en-us/error.md b/sections/en-us/error.md index 96f23a9..f99aa2e 100644 --- a/sections/en-us/error.md +++ b/sections/en-us/error.md @@ -1,369 +1,10 @@ -# 错误处理/调试/优化 +# Error/Debug/Opt -* `[Doc]` Errors (异常) -* `[Doc]` Domain (域) -* `[Doc]` Debugger (调试器) -* `[Doc]` C/C++ 插件 +* `[Doc]` Errors +* `[Doc]` Domain +* `[Doc]` Debugger +* `[Doc]` C/C++ Addon * `[Doc]` V8 -* `[Point]` 内存快照 -* `[Point]` CPU剖析 - - -## Errors - -在 Node.js 中的错误主要有一下四种类型: - -|错误|名称|触发| -|---|---|---| -|Standard JavaScript errors|标准 JavaScript 错误|由错误代码触发| -|System errors|系统错误|由操作系统触发| -|User-specified errors|用户自定义错误|通过 throw 抛出| -|Assertion errors|断言错误|由 `assert` 模块触发| - -其中标准的 JavaScript 错误常见有: - -* [EvalError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/EvalError): 调用 eval() 出现错误时抛出该错误 -* [SyntaxError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError): 代码不符合 JavaScript 语法规范时抛出该错误 -* [RangeError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError): 数组越界时抛出该错误 -* [ReferenceError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ReferenceError): 引用未定义的变量时抛出该错误 -* [TypeError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError): 参数类型错误时抛出该错误 -* [URIError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/URIError): 误用全局的 URI 处理函数时抛出该错误 - -而常见的系统错误列表可以通过 Node.js 的 os 对象常看列表: - -```javascript -const os = require('os'); - -console.log(os.constants.errno); -``` - -目前搜索 Node.js 面试题, 发现很多题目已经跟不上 Node.js 的发展了.比较老的 [NodeJS 错误处理最佳实践](https://cnodejs.org/topic/55714dfac4e7fbea6e9a2e5d), 译自 Joyent 的官方博客, 其中有这样的描述: - -> 实际上, `try/catch` 唯一常用的是在 `JSON.parse` 和类似验证用户输入的地方 - -然而实际上现在在 Node.js 中你已经可以轻松的使用 try/catch 去捕获异步的异常了. 并且在 Node.js v7.6 之后使用了升级引擎的新版 v8, 旧版中 try/catch 代码不能优化的问题也解决了. 所以我们现在再来看 - -> 怎么处理未预料的出错? 用 try/catch , domains 还是其它什么? - -在 Node.js 中错误处理主要有一下几种方法: - -* callback(err, data) 回调约定 -* throw / try / catch -* EventEmitter 的 error 事件 - -callback(err, data) 这种形式的错误处理起来繁琐, 并不具备强制性, 目前已经处于仅需要了解, 不推荐使用的情况. 而 domain 模块则是半只脚踏进棺材了. - -1) 感谢 [co](https://github.com/visionmedia/co) 的先河, 现在的你已经简单的使用 try/catch 保护关键的位置, 以 koa 为例, 可以通过中间件的形式来进行错误处理, 详见 [Koa error handling](https://github.com/koajs/koa/wiki/Error-Handling). 之后的 async/await 均属于这种模式. - -2) 通过 EventEmitter 的错误监听形式为各大关键的对象加上错误监听的回调. 例如监听 http server, tcp server 等对象的 `error` 事件以及 process 对象提供的 `uncaughtException` 和 `unhandledRejection` 等等. - -3) 使用 Promise 来封装异步, 并通过 Promise 的错误处理来 handle 错误. - -4) 如果上述办法不能起到良好的作用, 那么你需要学习如何优雅的 [Let It Crash](http://wiki.c2.com/?LetItCrash) - -> 为什么要在 cb 的第一参数传 error? 为什么有的 cb 第一个参数不是 error, 例如 http.createServer? - -TODO - - -### 错误栈丢失 - -```javascript -function test() { - throw new Error('test error'); -} - -function main() { - test(); -} - -main(); -``` - -可以收获报错: - -```javascript -/data/node-interview/error.js:2 - throw new Error('test error'); - ^ - -Error: test error - at test (/data/node-interview/error.js:2:9) - at main (/data/node-interview/error.js:6:3) - at Object. (/data/node-interview/error.js:9:1) - at Module._compile (module.js:570:32) - at Object.Module._extensions..js (module.js:579:10) - at Module.load (module.js:487:32) - at tryModuleLoad (module.js:446:12) - at Function.Module._load (module.js:438:3) - at Module.runMain (module.js:604:10) - at run (bootstrap_node.js:394:7) -``` - -可以发现报错的行数, test 函数, main 函数的调用关系都在 stack 中清晰的体现. - -当你使用 setImmediate 等定时器来设置异步的时候: - -```javascript -function test() { - throw new Error('test error'); -} - -function main() { - setImmediate(() => test()); -} - -main(); - -``` - -我们发现 - -```javascript -/data/node-interview/error.js:2 - throw new Error('test error'); - ^ - -Error: test error - at test (/data/node-interview/error.js:2:9) - at Immediate.setImmediate (/data/node-interview/error.js:6:22) - at runCallback (timers.js:637:20) - at tryOnImmediate (timers.js:610:5) - at processImmediate [as _immediateCallback] (timers.js:582:5) -``` - -错误栈中仅输出到 test 函数内调用的地方位置, 再往上 main 的调用信息就丢失了. 也就是说如果你的函数调用深度比较深的情况下, 你使用异步调用某个函数出错了的情况下追溯这个异步的调用是一个很困难的事情, 因为其之上的栈都已经丢失了. 如果你用过 [async](https://github.com/caolan/async) 之类的模块, 你还可能发现, 报错的 stack 会非常的长而且曲折, 光看 stack 很难去定位问题. - -这在项目不大/作者清楚的情况下不是问题, 但是当项目大起来, 开发人员多起来之后, 这样追溯错误会变得异常痛苦. 关于这个问题, 在上文中提到 [错误处理的最佳实践](https://cnodejs.org/topic/55714dfac4e7fbea6e9a2e5d) 中, 关于 `编写新函数的具体建议` 那一带的内容有描述到. 通过使用 [verror](https://www.npmjs.com/package/verror) 这样的方式, 让 Error 一层层封装, 并在每一层将错误的信息一层层的包上, 最后拿到的 Error 直接可以从 message 中获取用于定位问题的关键信息. - -以昨天的数据为准(2017-3-13)各位只要对比一下看看 npm 上上个月 [verror](https://www.npmjs.com/package/verror) 的下载量 `1100w` 比 [express](https://www.npmjs.com/package/express) 的 `1070w` 还高. 应该就能感受到这种写法有多流行了. - -### 防御性编程 - -错误并不可怕, 可怕的是你不去准备应对错误————[防御性编程的介绍和技巧](http://blog.jobbole.com/101651/) - -### let it crash - -[Let It Crash](http://wiki.c2.com/?LetItCrash) - -### uncaughtException - -当异常没有被捕获一路冒泡到 Event Loop 时就会触发该事件 process 对象上的 `uncaughtException` 事件. 默认情况下, Node.js 对于此类异常会直接将其堆栈跟踪信息输出给 `stderr` 并结束进程, 而为 `uncaughtException` 事件添加监听可以覆盖该默认行为, 不会直接结束进程. - -```javascript -process.on('uncaughtException', (err) => { - console.log(`Caught exception: ${err}`); -}); - -setTimeout(() => { - console.log('This will still run.'); -}, 500); - -// Intentionally cause an exception, but don't catch it. -nonexistentFunc(); -console.log('This will not run.'); -``` - -#### 合理使用 uncaughtException - -`uncaughtException` 的初衷是可以让你拿到错误之后可以做一些回收处理之后再 process.exit. 官方的同志们还曾经讨论过要移除该事件 (详见 [issues](https://github.com/nodejs/node-v0.x-archive/issues/2582)) - -所以你需要明白 `uncaughtException` 其实已经是非常规手段了, 应尽量避免使用它来处理错误. 因为通过该事件捕获到错误后, 并不代表 `你可以愉快的继续运行 (On Error Resume Next)`. 程序内部存在未处理的异常, 这意味着应用程序处于一种未知的状态. 如果不能适当的恢复其状态, 那么很有可能会触发不可预见的问题. (使用 domain 会很夸张的加剧这个现象, 并产生新人不能理解的各类幽灵问题) - -如果在 `.on` 指定的监听回调中报错不会被捕获, Node.js 的进程会直接终端并返回一个非零的退出码, 最后输出相应的堆栈信息. 否则, 会出现无限递归. 除此之外, 内存崩溃/底层报错等情况也不会被捕获, **目前猜测**是 v8/C++ 那边撂担子不干了, Node.js 完全插不上话导致的 (TODO 整理到这里才想起来这个念头尚未验证, 如果有空的朋友帮忙验证下). - -所以官方建议的使用 `uncaughtException` 的正确姿势是在结束进程前使用同步的方式清理已使用的资源 (文件描述符、句柄等) 然后 process.exit. - -在 uncaughtException 事件之后执行普通的恢复操作并不安全. 官方建议是另外在专门准备一个 monitor 进程来做健康检查并通过 monitor 来管理恢复情况, 并在必要的时候重启 (所以官方是含蓄的提醒各位用 pm2 之类的工具). - - -### unhandledRejection - -当 Promise 被 reject 且没有绑定监听处理时, 就会触发该事件. 该事件对排查和追踪没有处理 reject 行为的 Promise 很有用. - -该事件的回调函数接收以下参数: - -* `reason` [``](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) | `` 该 Promise 被 reject 的对象 (通常为 Error 对象) -* `p` 被 reject 的 Promise 本身 - -例如 - -```javascript -process.on('unhandledRejection', (reason, p) => { - console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); - // application specific logging, throwing an error, or other logic here -}); - -somePromise.then((res) => { - return reportToUser(JSON.pasre(res)); // note the typo (`pasre`) -}); // no `.catch` or `.then` -``` - -以下代码也会触发 `unhandledRejection` 事件: - -```javascript -function SomeResource() { - // Initially set the loaded status to a rejected promise - this.loaded = Promise.reject(new Error('Resource not yet loaded!')); -} - -var resource = new SomeResource(); -// no .catch or .then on resource.loaded for at least a turn -``` - -> In this example case, it is possible to track the rejection as a developer error as would typically be the case for other 'unhandledRejection' events. To address such failures, a non-operational `.catch(() => { })` handler may be attached to resource.loaded, which would prevent the 'unhandledRejection' event from being emitted. Alternatively, the 'rejectionHandled' event may be used. - - -## Domain - -Node.js 早期, try/catch 无法捕获异步的错误, 而错误优先的 callback 仅仅是一种约定并没有强制性并且写起来十分繁琐. 所以为了能够很好的捕获异常, Node.js 从 v0.8 开始引入 domain 这个模块. - -domain 本身是一个 EventEmitter 对象, 其中文意思是 "域" 的意思, 捕获异步异常的基本思路是创建一个域, cb 函数会在定义时会继承上一层的域, 报错通过当前域的 `.emit('error', err)` 方法触发错误事件将错误传递上去, 从而使得异步错误可以被强制捕获. (更多内容详见 [Node.js 异步异常的处理与domain模块解析](https://cnodejs.org/topic/516b64596d38277306407936)) - -但是 domain 的引入也带来了更多新的问题. 比如依赖的模块无法继承你定义的 domain, 导致你写的 domain 无法 cover 依赖模块报错. 而且, 很多人 (特别是新人) 由于不了解 Node.js 的内存/异步流程等问题, 在使用 domain 处理报错的时候, 没有做到完善的处理并盲目的让代码继续走下去, 这很可能导致**项目完全无法维护** (可能出现的问题真是不胜枚举, 各种梦魇...) - -该模块目前的情况: [deprecate domains](https://github.com/nodejs/node/issues/66) - - -## Debugger - -![node-js-survey-debug](/assets/node-js-survey-debug.png) - -类似 gdb 的命令行下 debug 工具 (上图中的 build-in debugger), 同时也支持远程 debug (类似 [node-inspector](https://github.com/node-inspector/node-inspector), 目前处于试验状态). 当然, 目前有不少同学觉得 [vscode](https://code.visualstudio.com/) 对 debug 工具集成的比较好. - -关于这个 build-in debugger 使用推荐看[官方文档](https://nodejs.org/dist/latest-v6.x/docs/api/debugger.html). 如果要深入一点, 你可能对本文感兴趣: [动态修改 NodeJS 程序中的变量值](http://code.oneapm.com/nodejs/2015/06/27/intereference/) - - -## C/C++ Addon - -在 Node.js 中开发 addon 最痛苦的地方莫过于升级 V8 导致的 C/C++ 代码不能兼容的问题, 这个问题在很早就出现了. 为了解决这个问题前人开了一个叫 [nan](https://github.com/nodejs/nan) 的项目. - -要学习 addon 开发, 除了[官方文档](https://nodejs.org/docs/latest/api/addons.html)也推荐阅读这个: https://github.com/nodejs/node-addon-examples - - -## V8 - -这里并不是介绍 V8, 而是介绍 Node.js 中的 V8 这个模块. 该模块用于开放 Node.js 内建的 V8 引擎的事件和接口. 这些接口由 V8 底层决定, 所以无法保证绝对的稳定性. - -|接口|描述| -|---|---| -|v8.getHeapStatistics()|获取 heap 信息| -|v8.getHeapSpaceStatistics()|获取 heap space 信息| -|v8.setFlagsFromString(string)|动态设置 V8 options| - -### v8.setFlagsFromString(string) - -该方法用于添加额外的 V8 命令行标志. 该方法需谨慎使用, 在 VM 启动后修改配置可能会发生不可预测的行为、崩溃和数据丢失; 或者什么反应都没有. - -通过 `node --v8-options` 命令可以查询当前 Node.js 环境中有哪些可用的 V8 options. 此外, 还可以参考非官方维护的一个 [V8 options 列表](https://github.com/thlorenz/v8-flags/blob/master/flags-0.11.md). - -用法: - -```javascript -// Print GC events to stdout for one minute. -const v8 = require('v8'); -v8.setFlagsFromString('--trace_gc'); -setTimeout(function() { v8.setFlagsFromString('--notrace_gc'); }, 60e3); -``` - -## 内存快照 - -内存快照常用与解决内存泄漏的问题. 快照工具推荐使用 [heapdump](https://github.com/bnoordhuis/node-heapdump) 用来保存内存快照, 使用 [devtool](https://github.com/Jam3/devtool) 来查看内存快照. 使用 heapdump 保存内存快照时, 只会有 Node.js 环境中的对象, 不会受到干扰(如果使用 [node-inspector](https://github.com/node-inspector/node-inspector) 的话, 快照中会有前端的变量干扰). - -使用以及内存泄漏的常见原因详见: [如何分析 Node.js 中的内存泄漏](https://zhuanlan.zhihu.com/p/25736931?group_id=825001468703674368). - -## CPU profiling - -CPU profiling (剖析) 常用于性能优化. 有许多用于做 profiling 的第三方工具, 但是大部分情况下, 使用 Node.js 内置的是最简单的. 其内置调用的就是 [V8 本身的 profiler](https://github.com/v8/v8/wiki/Using%20V8%E2%80%99s%20internal%20profiler), 它可以在程序执行过程中中是对 stack 间隔性的抽样分析. - -使用 `--prof` 开启内置的 profilling - -```shell -node --prof app.js -``` - -程序运行之后会生成一个 `isolate-0xnnnnnnnnnnnn-v8.log` 在当前运行目录. - -你可以使用 `--prof-process` 来生成报告查看 - -``` -node --prof-process isolate-0xnnnnnnnnnnnn-v8.log -``` - -报告形如: - -``` -Statistical profiling result from isolate-0x103001200-v8.log, (12042 ticks, 2634 unaccounted, 0 excluded). - - [Shared libraries]: - ticks total nonlib name - 35 0.3% /usr/lib/system/libsystem_platform.dylib - 27 0.2% /usr/lib/system/libsystem_pthread.dylib - 7 0.1% /usr/lib/system/libsystem_c.dylib - 3 0.0% /usr/lib/system/libsystem_kernel.dylib - 1 0.0% /usr/lib/system/libsystem_malloc.dylib - - [JavaScript]: - ticks total nonlib name - 208 1.7% 1.7% Stub: LoadICStub - 187 1.6% 1.6% KeyedLoadIC: A keyed load IC from the snapshot - 104 0.9% 0.9% Stub: VectorStoreICStub - 69 0.6% 0.6% LazyCompile: *emit events.js:136:44 - 68 0.6% 0.6% Builtin: CallFunction_ReceiverIsNotNullOrUndefined - 65 0.5% 0.5% KeyedStoreIC: A keyed store IC from the snapshot {2} - 47 0.4% 0.4% Builtin: CallFunction_ReceiverIsAny - 43 0.4% 0.4% LazyCompile: *storeHeader _http_outgoing.js:312:21 - 34 0.3% 0.3% LazyCompile: *removeListener events.js:315:28 - 33 0.3% 0.3% Stub: RegExpExecStub - 33 0.3% 0.3% LazyCompile: *_addListener events.js:210:22 - 32 0.3% 0.3% Stub: CEntryStub - 32 0.3% 0.3% Builtin: ArgumentsAdaptorTrampoline - 31 0.3% 0.3% Stub: FastNewClosureStub - 30 0.2% 0.3% Stub: InstanceOfStub - ... - - [C++]: - ticks total nonlib name - 460 3.8% 3.8% _mach_port_extract_member - 329 2.7% 2.7% _openat$NOCANCEL - 199 1.7% 1.7% ___bsdthread_register - 136 1.1% 1.1% ___mkdir_extended - 116 1.0% 1.0% node::HandleWrap::Close(v8::FunctionCallbackInfo const&) - 112 0.9% 0.9% void v8::internal::BodyDescriptorBase::IterateBodyImpl(v8::internal::Heap*, v8::internal::HeapObject*, int, int) - 106 0.9% 0.9% _http_parser_execute - 103 0.9% 0.9% _szone_malloc_should_clear - 99 0.8% 0.8% int v8::internal::BinarySearch<(v8::internal::SearchMode)1, v8::internal::DescriptorArray>(v8::internal::DescriptorArray*, v8::internal::Name*, int, int*) - 89 0.7% 0.7% node::TCPWrap::Connect(v8::FunctionCallbackInfo const&) - 86 0.7% 0.7% v8::internal::LookupIterator::State v8::internal::LookupIterator::LookupInRegularHolder(v8::internal::Map*, v8::internal::JSReceiver*) - ... - - [Bottom up (heavy) profile]: - Note: percentage shows a share of a particular caller in the total - amount of its parent calls. - Callers occupying less than 2.0% are not shown. - - ticks parent name - 2634 21.9% UNKNOWN - 764 29.0% LazyCompile: *connect net.js:815:17 - 764 100.0% LazyCompile: ~ net.js:966:30 - 764 100.0% LazyCompile: *_tickCallback internal/process/next_tick.js:87:25 - 193 7.3% LazyCompile: *createWriteReq net.js:732:24 - 101 52.3% LazyCompile: *Socket._writeGeneric net.js:660:42 - 99 98.0% LazyCompile: ~ net.js:667:34 - 99 100.0% LazyCompile: ~g events.js:287:13 - 99 100.0% LazyCompile: *emit events.js:136:44 - 92 47.7% LazyCompile: ~Socket._writeGeneric net.js:660:42 - 91 98.9% LazyCompile: ~ net.js:667:34 - 91 100.0% LazyCompile: ~g events.js:287:13 - 91 100.0% LazyCompile: *emit events.js:136:44 - ... -``` - -|字段|描述| -|---|---| -|ticks|时间片| -|total|当前操作执行的时间占总时间的比率| -|nonlib|当前非 System library 执行时间比率| - -整理中 +* `[Point]` Memory snapshot +* `[Point]` CPU Profilling diff --git a/sections/en-us/event-async.md b/sections/en-us/event-async.md index dd882ef..ca3963c 100644 --- a/sections/en-us/event-async.md +++ b/sections/en-us/event-async.md @@ -1,227 +1,7 @@ -# 事件/异步 +# Event/Async -* [`[Basic]` Promise](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#promise) -* [`[Doc]` Events (事件)](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#events) -* [`[Doc]` Timers (定时器)](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#timers) -* [`[Point]` 阻塞/异步](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#阻塞异步) -* [`[Point]` 并行/并发](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#并行并发) - -## 简述 - -异步还是不异步? 这是一个问题. - -## Promise - -![callback-hell](/assets/callback-hell.jpg) - -相信很多同学在面试的时候都碰到过这样一个问题, `如何处理 Callback Hell`. 在早些年的时候, 大家会看到有很多的解决方案例如 [Q](https://www.npmjs.com/package/q), [async](https://www.npmjs.com/package/async), [EventProxy](https://www.npmjs.com/package/eventproxy) 等等. 最后从流行程度来看 `Promise` 当之无愧的独领风骚, 并且是在 ES6 的 Javascript 标准上赢得了支持. - -关于它的基础知识/概念推荐看阮一峰的 [Promise 对象](http://javascript.ruanyifeng.com/advanced/promise.html#toc9) 这里就不多不赘述. - -> Promise 中 .then 的第二参数与 .catch 有什么区别? - -参见 [We have a problem with promises](https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html) - -另外关于同步与异步, 有个问题希望大家看一下, 这是很简单的 Promise 的使用例子: - -```javascript -let doSth = new Promise((resolve, reject) => { - console.log('hello'); - resolve(); -}); - -doSth.then(() => { - console.log('over'); -}); -``` - -毫无疑问的可以得到一下输出结果: - -``` -hello -over -``` - -但是首先的问题是, 该 Promise 封装的代码肯定是同步的, 那么这个 then 的执行是异步的吗? - -其次的问题是, 如下代码, `setTimeout` 到 10s 之后再 `.then` 调用, 那么 `hello` 是会在 10s 之后在打印吗, 还是一开始就打印? - -```javascript -let doSth = new Promise((resolve, reject) => { - console.log('hello'); - resolve(); -}); - -setTimeout(() => { - doSth.then(() => { - console.log('over'); - }) -}, 10000); -``` - -以及理解如下代码的执行顺序 ([出处](https://zhuanlan.zhihu.com/p/25407758)): - -```javascript -setTimeout(function() { - console.log(1) -}, 0); -new Promise(function executor(resolve) { - console.log(2); - for( var i=0 ; i<10000 ; i++ ) { - i == 9999 && resolve(); - } - console.log(3); -}).then(function() { - console.log(4); -}); -console.log(5); -``` - -如果你不了解这些问题, 可以自己在本地尝试研究一下打印的结果. 这里希望你掌握的是 Promise 的状态转换, 包括异步与 Promise 的关系, 以及 Promise 如何帮助你处理异步, 如果你研究过 Promise 的实现那就更好了. - -## Events - -`Events` 是 Node.js 中一个非常重要的 core 模块, 在 node 中有许多重要的 core API 都是依赖其建立的. 比如 `Stream` 是基于 `Events` 实现的, 而 `fs`, `net`, `http` 等模块都依赖 `Stream`, 所以 `Events` 模块的重要性可见一斑. - -通过继承 EventEmitter 来使得一个类具有 node 提供的基本的 event 方法, 这样的对象可以称作 emitter, 而触发(emit)事件的 cb 则称作 listener. 与前端 DOM 树上的事件并不相同, emitter 的触发不存在冒泡, 逐层捕获等事件行为, 也没有处理事件传递的方法. - -> Eventemitter 的 emit 是同步还是异步? - -Node.js 中 Eventemitter 的 emit 是同步的. 在官方文档中有说明: - -> The EventListener calls all listeners synchronously in the order in which they were registered. This is important to ensure the proper sequencing of events and to avoid race conditions or logic errors. - -另外, 可以讨论如下的执行结果是输出 `hi 1` 还是 `hi 2`? - -```javascript -const EventEmitter = require('events'); - -let emitter = new EventEmitter(); - -emitter.on('myEvent', () => { - console.log('hi 1'); -}); - -emitter.on('myEvent', () => { - console.log('hi 2'); -}); - -emitter.emit('myEvent'); -``` - -或者如下情况是否会死循环? - -```javascript -const EventEmitter = require('events'); - -let emitter = new EventEmitter(); - -emitter.on('myEvent', () => { - console.log('hi'); - emitter.emit('myEvent'); -}); - -emitter.emit('myEvent'); -``` - -以及这样会不会死循环? - -```javascript -const EventEmitter = require('events'); - -let emitter = new EventEmitter(); - -emitter.on('myEvent', function sth () { - emitter.on('myEvent', sth); - console.log('hi'); -}); - -emitter.emit('myEvent'); -``` - -使用 emitter 处理问题可以处理比较复杂的状态场景, 比如 TCP 的复杂状态机, 做多项异步操作的时候每一步都可能报错, 这个时候 .emit 错误并且执行某些 .once 的操作可以将你从泥沼中拯救出来. - -另外可以注意一下的是, 有些同学喜欢用 emitter 来监控某些类的状态, 但是在这些类释放的时候可能会忘记释放 emitter, 而这些类的内部可能持有该 emitter 的 listener 的引用从而导致内存泄漏. - -## 阻塞/异步 - -> 如何判断接口是否异步? 是否只要有回调函数就是异步? - -开放性问题, 每个写 node 的人都有一套自己的判断方式. - -* 看文档 -* console.log 打印看看 -* 看是否有 IO 操作 - -单纯使用回调函数并不会异步, IO 操作才可能会异步, 除此之外还有使用 setTimeout 等方式实现异步. - -> 有这样一个场景, 你在线上使用 koa 搭建了一个网站, 这个网站项目中有一个你同事写的接口 A, 而 A 接口中在特殊情况下会变成死循环. 那么首先问题是, 如果触发了这个死循环, 会对网站造成什么影响? - -Node.js 中执行 js 代码的过程是单线程的. 只有当前代码都执行完, 才会切入事件循环, 然后从事件队列中 pop 出下一个回调函数开始执行代码. 所以 ① 实现一个 sleep 函数, 只要通过一个死循环就可以阻塞整个 js 的执行流程. (关于如何避免坑爹的同事写出死循环, 在后面的测试环节有写到.) - -> 如何实现一个 sleep 函数? ① - -```javascript -function sleep(ms) { - var start = Date.now(), expire = start + ms; - while (Date.now() < expire) ; - return; -} -``` - -而异步, 是使用 libuv 来实现的 (C/C++的同学可以参见 libev 和 libevent) 另一个线程里的事件队列. - -如果在线上的网站中出现了死循环的逻辑被触发, 整个进程就会一直卡在死循环中, 如果没有多进程部署的话, 之后的网站请求全部会超时, js 代码没有结束那么事件队列就会停下等待不会执行异步, 整个网站无法响应. - -> 如何实现一个异步的 reduce? (注:不是异步完了之后同步 reduce) - -需要了解 reduce 的情况, 是第 n 个与 n+1 的结果异步处理完之后, 在用新的结果与第 n+2 个元素继续依次异步下去. 不贴答案, 期待诸君的版本. - -## Timers - -在笔者这里将 Node.js 中的异步简单的划分为两种, 硬异步和软异步. - -硬异步是指由于 IO 操作或者外部调用走 libuv 而需要异步的情况. 当然, 也存在 readFileSync, execSync 等例外情况, 不过 node 由于是单线程的, 所以如果常规业务在普通时段执行可能比较耗时同步的 IO 操作会使得其执行过程中其他的所有操作都不能响应, 有点作死的感觉. 不过在启动/初始化以及一些工具脚本的应用场景下是完全没问题的. 而一般的场景下 IO 操作都是需要异步的. - -软异步是指, 通过 setTimeout 等方式来实现的异步. 关于 nextTick, setTimeout 以及 setImmediate 三者的区别参见[该帖](https://cnodejs.org/topic/5556efce7cabb7b45ee6bcac) - -**Event loop 示例** - -``` - ┌───────────────────────┐ -┌─>│ timers │ -│ └──────────┬────────────┘ -│ ┌──────────┴────────────┐ -│ │ I/O callbacks │ -│ └──────────┬────────────┘ -│ ┌──────────┴────────────┐ -│ │ idle, prepare │ -│ └──────────┬────────────┘ ┌───────────────┐ -│ ┌──────────┴────────────┐ │ incoming: │ -│ │ poll │<─────┤ connections, │ -│ └──────────┬────────────┘ │ data, etc. │ -│ ┌──────────┴────────────┐ └───────────────┘ -│ │ check │ -│ └──────────┬────────────┘ -│ ┌──────────┴────────────┐ -└──┤ close callbacks │ - └───────────────────────┘ -``` - -关于事件循环, Timers 以及 nextTick 的关系详见官方文档 [The Node.js Event Loop, Timers, and process.nextTick() (英文)](https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/) 以及阮一峰的 [JavaScript 运行机制详解:再谈Event Loop (中文)](http://www.ruanyifeng.com/blog/2014/10/event-loop.html) 等. - -## 并行/并发 - -并行 (Parallel) 与并发 (Concurrent) 是两个很常见的概念. - -可以看 Erlang 作者 Joe Armstrong 的博客 ([Concurrent and Parallel](http://joearms.github.io/2013/04/05/concurrent-and-parallel-programming.html)) - -![con_and_par](http://joearms.github.io/images/con_and_par.jpg) - -并发 (Concurrent) = 2 队列对应 1 咖啡机. - -并行 (Parallel) = 2 队列对应 2 咖啡机. - -Node.js 通过事件循环来挨个抽取实践队列中的一个个 Task 执行, 从而避免了传统的多线程情况下 `2个队列对应 1个咖啡机` 的时候上线文切换以及资源争抢/同步的问题, 所以获得了高并发的成就. - -至于在 node 中并行, 你可以通过 cluster 来再添加一个咖啡机. +* `[Basic]` Promise +* `[Doc]` Events +* `[Doc]` Timers +* `[Point]` Blocking & Non-blocking +* `[Point]` Parallel & Concurrent diff --git a/sections/en-us/io.md b/sections/en-us/io.md index 0c021b7..e9aaa72 100644 --- a/sections/en-us/io.md +++ b/sections/en-us/io.md @@ -1,386 +1,9 @@ # IO -* [`[Doc]` Buffer](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#buffer) -* [`[Doc]` String Decoder (字符串解码)](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#string-decoder) -* [`[Doc]` Stream (流)](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#stream) -* [`[Doc]` Console (控制台)](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#console) -* [`[Doc]` File System (文件系统)](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#file) -* [`[Doc]` Readline](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#readline) -* [`[Doc]` REPL](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#repl) - -# 简述 - -Node.js 是以 IO 密集型业务著称. 那么问题来了, 你真的了解什么叫 IO, 什么又叫 IO 密集型业务吗? - -## Buffer - -Buffer 是 Node.js 中用于处理二进制数据的类, 其中与 IO 相关的操作 (网络/文件等) 均基于 Buffer. Buffer 类的实例非常类似整数数组, ***但其大小是固定不变的***, 并且其内存在 V8 堆栈外分配原始内存空间. Buffer 类的实例创建之后, 其所占用的内存大小就不能再进行调整. - -在 Node.js v6.x 之后 `new Buffer()` 接口开始被废弃, 理由是参数类型不同会返回不同类型的 Buffer 对象, 所以当开发者没有正确校验参数或没有正确初始化 Buffer 对象的内容时, 以及不了解的情况下初始化 就会在不经意间向代码中引入安全性和可靠性问题. - -接口|用途 ----|--- -Buffer.from()|根据已有数据生成一个 Buffer 对象 -Buffer.alloc()|创建一个初始化后的 Buffer 对象 -Buffer.allocUnsafe()|创建一个未初始化的 Buffer 对象 - -### TypedArray - -Node.js 的 Buffer 在 ES6 增加了 TypedArray 类型之后, 修改了原来的 Buffer 的实现, 选择基于 TypedArray 中 Uint8Array 来实现, 从而提升了一波性能. - -使用上, 你需要了解如下情况: - -```javascript -const arr = new Uint16Array(2); -arr[0] = 5000; -arr[1] = 4000; - -const buf1 = Buffer.from(arr); // 拷贝了该 buffer -const buf2 = Buffer.from(arr.buffer); // 与该数组共享了内存 - -console.log(buf1); -// 输出: , 拷贝的 buffer 只有两个元素 -console.log(buf2); -// 输出: - -arr[1] = 6000; -console.log(buf1); -// 输出: -console.log(buf2); -// 输出: -``` - -## String Decoder - -字符串解码器 (String Decoder) 是一个用于将 Buffer 拿来 decode 到 string 的模块, 是作为 Buffer.toString 的一个补充, 它支持多字节 UTF-8 和 UTF-16 字符. 例如 - -```javascript -const StringDecoder = require('string_decoder').StringDecoder; -const decoder = new StringDecoder('utf8'); - -const cent = Buffer.from([0xC2, 0xA2]); -console.log(decoder.write(cent)); // ¢ - -const euro = Buffer.from([0xE2, 0x82, 0xAC]); -console.log(decoder.write(euro)); // € -``` - -当然也可以断断续续的处理. - -```javascript -const StringDecoder = require('string_decoder').StringDecoder; -const decoder = new StringDecoder('utf8'); - -decoder.write(Buffer.from([0xE2])); -decoder.write(Buffer.from([0x82])); -console.log(decoder.end(Buffer.from([0xAC]))); // € -``` - -## Stream - -Node.js 内置的 `stream` 模块是多个核心模块的基础. 但是流 (stream) 是一种很早之前流行的编程方式. 可以用大家比较熟悉的 C语言来看这种流式操作: - -```c - -int copy(const char *src, const char *dest) -{ - FILE *fpSrc, *fpDest; - char buf[BUF_SIZE] = {0}; - int lenSrc, lenDest; - - // 打开要 src 的文件 - if ((fpSrc = fopen(src, "r")) == NULL) - { - printf("文件 '%s' 无法打开\n", src); - return FAILURE; - } - - // 打开 dest 的文件 - if ((fpDest = fopen(dest, "w")) == NULL) - { - printf("文件 '%s' 无法打开\n", dest); - fclose(fpSrc); - return FAILURE; - } - - // 从 src 中读取 BUF_SIZE 长的数据到 buf 中 - while ((lenSrc = fread(buf, 1, BUF_SIZE, fpSrc)) > 0) - { - // 将 buf 中的数据写入 dest 中 - if ((lenDest = fwrite(buf, 1, lenSrc, fpDest)) != lenSrc) - { - printf("写入文件 '%s' 失败\n", dest); - fclose(fpSrc); - fclose(fpDest); - return FAILURE; - } - // 写入成功后清空 buf - memset(buf, 0, BUF_SIZE); - } - - // 关闭文件 - fclose(fpSrc); - fclose(fpDest); - return SUCCESS; -} -``` - -应用的场景很简单, 你要拷贝一个 20G 大的文件, 如果你一次性将 20G 的数据读入到内存, 你的内存条可能不够用, 或者严重影响性能. 但是你如果使用一个 1MB 大小的缓存 (buf) 每次读取 1Mb, 然后写入 1Mb, 那么不论这个文件多大都只会占用 1Mb 的内存. - -而在 Node.js 中, 原理与上述 C 代码类似, 不过在读写的实现上通过 libuv 与 EventEmitter 加上了异步的特性. 在 linux/unix 中你可以通过 `|` 来感受到流式操作. - -### Stream 的类型 - - -类|使用场景|重写方法 ----|---|--- -[Readable](https://github.com/substack/stream-handbook#readable-streams)|只读|_read -[Writable](https://github.com/substack/stream-handbook#writable-streams)|只写|_write -[Duplex](https://github.com/substack/stream-handbook#duplex)|读写|_read, _write -[Transform](https://github.com/substack/stream-handbook#transform)|操作被写入数据, 然后读出结果|_transform, _flush - - -### 对象模式 - -通过 Node API 创建的流, 只能够对字符串或者 buffer 对象进行操作. 但其实流的实现是可以基于其他的 Javascript 类型(除了 null, 它在流中有特殊的含义)的. 这样的流就处在 "对象模式(objectMode)" 中. -在创建流对象的时候, 可以通过提供 `objectMode` 参数来生成对象模式的流. 试图将现有的流转换为对象模式是不安全的. - -### 缓冲区 - -Node.js 中 stream 的缓冲区, 以开头的 C语言 拷贝文件的代码为模板讨论, (抛开异步的区别看) 则是从 `src` 中读出数据到 `buf` 中后, 并没有直接写入 `dest` 中, 而是先放在一个比较大的缓冲区中, 等待写入(消费) `dest` 中. 即, 在缓冲区的帮助下可以使读与写的过程分离. - -Readable 和 Writable 流都会将数据储存在内部的缓冲区中. 缓冲区可以分别通过 `writable._writableState.getBuffer()` 和 `readable._readableState.buffer` 来访问. 缓冲区的大小, 由构造 stream 时候的 `highWaterMark` 标志指定可容纳的 byte 大小, 对于 `objectMode` 的 stream, 该标志表示可以容纳的对象个数. - -#### 可读流 - -当一个可读实例调用 `stream.push()` 方法的时候, 数据将会被推入缓冲区. 如果数据没有被消费, 即调用 `stream.read()` 方法读取的话, 那么数据会一直留在缓冲队列中. 当缓冲区中的数据到达 `highWaterMark` 指定的阈值, 可读流将停止从底层汲取数据, 直到当前缓冲的报备成功消耗为止. - -#### 可写流 - -在一个在可写实例上不停地调用 writable.write(chunk) 的时候数据会被写入可写流的缓冲区. 如果当前缓冲区的缓冲的数据量低于 `highWaterMark` 设定的值, 调用 writable.write() 方法会返回 true (表示数据已经写入缓冲区), 否则当缓冲的数据量达到了阈值, 数据无法写入缓冲区 write 方法会返回 false, 直到 drain 事件触发之后才能继续调用 write 写入. - -```javascript -// Write the data to the supplied writable stream one million times. -// Be attentive to back-pressure. -function writeOneMillionTimes(writer, data, encoding, callback) { - let i = 1000000; - write(); - function write() { - var ok = true; - do { - i--; - if (i === 0) { - // last time! - writer.write(data, encoding, callback); - } else { - // see if we should continue, or wait - // don't pass the callback, because we're not done yet. - ok = writer.write(data, encoding); - } - } while (i > 0 && ok); - if (i > 0) { - // had to stop early! - // write some more once it drains - writer.once('drain', write); - } - } -} -``` - -#### Duplex 与 Transform - -Duplex 流和 Transform 流都是同时可读写的, 他们会在内部维持两个缓冲区, 分别对应读取和写入, 这样就可以允许两边同时独立操作, 维持高效的数据流. 比如说 net.Socket 是一个 Duplex 流, Readable 端允许从 socket 获取、消耗数据, Writable 端允许向 socket 写入数据. 数据写入的速度很有可能与消耗的速度有差距, 所以两端可以独立操作和缓冲是很重要的. - -### pipe - -stream 的 `.pipe()`, 将一个可写流附到可读流上, 同时将可写流切换到流模式, 并把所有数据推给可写流. 在 pipe 传递数据的过程中, `objectMode` 是传递引用, 非 `objectMode` 则是拷贝一份数据传递下去. - -pipe 方法最主要的目的就是将数据的流动缓冲到一个可接受的水平, 不让不同速度的数据源之间的差异导致内存被占满. 关于 pipe 的实现参见 David Cai 的 [通过源码解析 Node.js 中导流(pipe)的实现](https://cnodejs.org/topic/56ba030271204e03637a3870) - -## Console - -[console.log 正常情况下是异步的, 除非你使用 `new Console(stdout[, stderr])` 指定了一个文件为目的地](https://nodejs.org/dist/latest-v6.x/docs/api/console.html#console_asynchronous_vs_synchronous_consoles). 不过一般情况下的实现都是如下 ([6.x 源代码](https://github.com/nodejs/node/blob/v6.x/lib/console.js#L42)): - -```javascript -// As of v8 5.0.71.32, the combination of rest param, template string -// and .apply(null, args) benchmarks consistently faster than using -// the spread operator when calling util.format. -Console.prototype.log = function(...args) { - this._stdout.write(`${util.format.apply(null, args)}\n`); -}; -``` - -自己实现一个 console.log 可以参考如下代码: - -```javascript -let print = (str) => process.stdout.write(str + '\n'); - -print('hello world'); -``` - -注意: 该代码并没有处理多参数, 也没有处理占位符 (即 util.format 的功能). - -### console.log.bind(console) 问题 - -```javascript -// 源码出处 https://github.com/nodejs/node/blob/v6.x/lib/console.js -function Console(stdout, stderr) { - // ... init ... - - // bind the prototype functions to this Console instance - var keys = Object.keys(Console.prototype); - for (var v = 0; v < keys.length; v++) { - var k = keys[v]; - this[k] = this[k].bind(this); - } -} -``` - -## File - -“一切皆是文件”是 Unix/Linux 的基本哲学之一, 不仅普通的文件、目录、字符设备、块设备、套接字等在 Unix/Linux 中都是以文件被对待, 也就是说这些资源的操作对象均为 fd (文件描述符), 都可以通过同一套 system call 来读写. 在 linux 中你可以通过 ulimit 来对 fd 资源进行一定程度的管理限制. - -Node.js 封装了标准 POSIX 文件 I/O 操作的集合. 通过 require('fs') 可以加载该模块. 该模块中的所有方法都有异步执行和同步执行两个版本. 你可以通过 fs.open 获得一个文件的文件描述符. - -### 编码 - -// TODO - -UTF8, GBK, es6 中对编码的支持, 如何计算一个汉字的长度 - -BOM - -### stdio - -stdio (standard input output) 标准的输入输出流, 即输入流 (stdin), 输出流 (stdout), 错误流 (stderr) 三者. 在 Node.js 中分别对应 `process.stdin` (Readable), `process.stdout` (Writable) 以及 `process.stderr` (Writable) 三个 stream. - -输出函数是每个人在学习任何一门编程语言时所需要学到的第一个函数. 例如 C语言的 `printf("hello, world!");` python/ruby 的 `print 'hello, world!'` 以及 Javascript 中的 `console.log('hello, world!');` - -以 C语言的伪代码来看的话, 这类输出函数的实现思路如下: - -```c -int printf(FILE *stream, 要打印的内容) -{ - // ... - - // 1. 申请一个临时内存空间 - char *s = malloc(4096); - - // 2. 处理好要打印的的内容, 其值存储在 s 中 - // ... - - // 3. 将 s 上的内容写入到 stream 中 - fwrite(s, stream); - - // 4. 释放临时空间 - free(s); - - // ... -} -``` - -我们需要了解的是第 3 步, 其中的 stream 则是指 stdout (输出流). 实际上在 shell 上运行一个应用程序的时候, shell 做的第一个操作是 fork 当前 shell 的进程 (所以, 如果你通过 ps 去查看你从 shell 上启动的进程, 其父进程 pid 就是当前 shell 的 pid), 在这个过程中也把 shell 的 stdio 继承给了你当前的应用进程, 所以你在当前进程里面将数据写入到 stdout, 也就是写入到了 shell 的 stdout, 即在当前 shell 上显示了. - -输入也是同理, 当前进程继承了 shell 的 stdin, 所以当你从 stdin 中读取数据时, 其实就获取到你在 shell 上输入的数据. (PS: shell 可以是 windows 下的 cmd, powershell, 也可以是 linux 下 bash 或者 zsh 等) - -当你使用 ssh 在远程服务器上运行一个命令的时候, 在服务器上的命令输出虽然也是写入到服务器上 shell 的 stdout, 但是这个远程的 shell 是从 sshd 服务上 fork 出来的, 其 stdout 是继承自 sshd 的一个 fd, 这个 fd 其实是个 socket, 所以最终其实是写入到了一个 socket 中, 通过这个 socket 传输你本地的计算机上的 shell 的 stdout. - -如果你理解了上述情况, 那么你也就能理解为什么守护进程需要关闭 stdio, 如果切到后台的守护进程没有关闭 stdio 的话, 那么你在用 shell 操作的过程中, 屏幕上会莫名其妙的多出来一些输出. 此处对应[守护进程](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#守护进程)的 C 实现中的这一段: - -```c -for (; i < getdtablesize(); ++i) { - close(i); // 关闭打开的 fd -} -``` - -Linux/unix 的 fd 都被设计为整型数字, 从 0 开始. 你可以尝试运行如下代码查看. - -``` -console.log(process.stdin.fd); // 0 -console.log(process.stdout.fd); // 1 -console.log(process.stderr.fd); // 2 -``` - -在上一节中的 [在 IPC 通道建立之前, 父进程与子进程是怎么通信的? 如果没有通信, 那 IPC 是怎么建立的?](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#q-child) 中使用环境变量传递 fd 的方法, 这么看起来就很直白了, 因为传递 fd 其实是直接传递了一个整型数字. - -### 如何同步的获取用户的输入? - -如果你理解了上述的内容, 那么放到 Node.js 中来看, 获取用户的输入其实就是读取 Node.js 进程中的输入流 (即 process.stdin 这个 stream) 的数据. - -而要同步读取, 则是不用异步的 read 接口, 而是用同步的 readSync 接口去读取 stdin 的数据即可实现. 以下来自万能的 stackoverflow: - -```javascript -/* - * http://stackoverflow.com/questions/3430939/node-js-readsync-from-stdin - * @mklement0 - */ -var fs = require('fs'); - -var BUFSIZE = 256; -var buf = new Buffer(BUFSIZE); -var bytesRead; - -module.exports = function() { - var fd = ('win32' === process.platform) ? process.stdin.fd : fs.openSync('/dev/stdin', 'rs'); - bytesRead = 0; - - try { - bytesRead = fs.readSync(fd, buf, 0, BUFSIZE); - } catch (e) { - if (e.code === 'EAGAIN') { // 'resource temporarily unavailable' - // Happens on OS X 10.8.3 (not Windows 7!), if there's no - // stdin input - typically when invoking a script without any - // input (for interactive stdin input). - // If you were to just continue, you'd create a tight loop. - console.error('ERROR: interactive stdin input not supported.'); - process.exit(1); - } else if (e.code === 'EOF') { - // Happens on Windows 7, but not OS X 10.8.3: - // simply signals the end of *piped* stdin input. - return ''; - } - throw e; // unexpected exception - } - - if (bytesRead === 0) { - // No more stdin input available. - // OS X 10.8.3: regardless of input method, this is how the end - // of input is signaled. - // Windows 7: this is how the end of input is signaled for - // *interactive* stdin input. - return ''; - } - // Process the chunk read. - - var content = buf.toString(null, 0, bytesRead - 1); - - return content; -}; -``` - -## Readline - -`readline` 模块提供了一个用于从 Readble 的 stream (例如 process.stdin) 中一次读取一行的接口. 当然你也可以用来读取文件或者 net, http 的 stream, 比如: - -```javascript -const readline = require('readline'); -const fs = require('fs'); - -const rl = readline.createInterface({ - input: fs.createReadStream('sample.txt') -}); - -rl.on('line', (line) => { - console.log(`Line from file: ${line}`); -}); -``` - -实现上, realine 在读取 TTY 的数据时, 是通过 `input.on('keypress', onkeypress)` 时发现用户按下了回车键来判断是新的 line 的, 而读取一般的 stream 时, 则是通过缓存数据然后用正则 .test 来判断是否为 new line 的. - -PS: 打个广告, 如果在编写脚本时, 不习惯这样异步获取输入, 想要同步获取同步的用户输入可以看一看这个 Node.js 版本类 C语言使用的 [scanf](https://github.com/Lellansin/node-scanf/) 模块 (支持 ts). - -## REPL - -Read-Eval-Print-Loop (REPL) - -整理中 +* `[Doc]` Buffer +* `[Doc]` String Decoder +* `[Doc]` Stream +* `[Doc]` Console +* `[Doc]` File System +* `[Doc]` Readline +* `[Doc]` REPL diff --git a/sections/en-us/js-basic.md b/sections/en-us/js-basic.md index b778d42..35f5abc 100644 --- a/sections/en-us/js-basic.md +++ b/sections/en-us/js-basic.md @@ -1,126 +1,7 @@ -# Javascript 基础问题 +# Basic -* [`[Basic]` 类型判断](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#类型判断) -* [`[Basic]` 作用域](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#作用域) -* [`[Basic]` 引用传递](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#引用传递) -* [`[Basic]` 内存释放](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#内存释放) -* [`[Basic]` ES6 新特性](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#es6-新特性) - - -## 简述 - -与前端 Js 不同, 后端方面除了SSR/爬虫之外很少会接触 DOM, 所以关于 DOM 方面的各种知识基本不会讨论. 前端很少碰到内存问题, 但是后端几乎是直面服务器内存的, 更加偏向内存方面, 对于一些更基础的问题也会更加关注. - -不过由于 Js 方面的知识点是在太多, 《Javascript 权威指南》的厚度完全可以说明问题, 所以本教程并不会完整的带大家过一遍 Js 的基础问题, 只是简单列举一些饿了么在面试 Node.js 程序的时候通常会问的一些 Js 基础问题, 有的详细的地方会直接留下书名或者博文链接, 以供大家深入了解, 这里就不赘述了. - -> 希望大家更多的是带着本文抛出的问题去学习, 而不是期待本文把所有答案列出来. - -## 类型判断 - -Javascript 的类型判断其实是个挺折磨人的话题, 不然也不会有 Typescript 出现了. 在类型判断的问题上, 基础上 推荐阅读 [lodash](https://github.com/lodash/lodash) 的源代码. - -这类问题一般只是简单的开场, 不会因为说你不知道 `undefined == null` 的结果是 `true` 就一票否决一个人. 只是根据个人经验看来,这个问题答不清楚的有不小的概率属于基础较差. 如果你对这种问题没有任何概念, 也许要反思一下是不是该找本书过一下 Js 的基础了. - -另外在这个问题上, 对使用 TypeScript 以及 flow 同学会有一定的加分. - -## 作用域 - -在面试时, 作用域并不是一个很好问的知识点, 一般会问的是 `es6 中 let 与 var 的区别`, 或者列举代码, 然后通过对代码的解读来看你对作用域的掌握比较方便. - -印象中那本 [《你不知道的 Javascript》](https://book.douban.com/subject/26351021/) 讲的很好了, 有兴趣可以去看那本书, 以下是该书的部分目录: - -* 第1章 作用域是什么 -* 第2章 词法作用域 -* 第3章 函数作用域和块作用域 -* 第4章 提升 -* 第5章 作用域闭包 -* ... - -## 引用传递 - -> js 中什么类型是引用传递, 什么类型是值传递? 如何将值类型的变量以引用的方式传递? - -简单点说, 对象是引用传递, 基础类型是值传递, 通过将基础类型包装 (boxing) 可以以引用的方式传递.(复杂见注①) - -引用传递和值传递是一个非常简单的问题, 也是理解 Javascript 中的内存方面问题的一个基础. 如果不了解引用可能很难去看很多问题. - -面试写代码的话, 可以通过 `如何编写一个 json 对象的拷贝函数` 等类似的问题来考察对引用的了解. -不过笔者偶尔会有恶趣味, 喜欢先问应聘者对于 `==` 的 `===` 的区别的了解. 然后再问 `[1] == [1]` 是 `true` 还是 `false`. 如果基础不好的同学可能会被自己对于 `==` 和 `===` 的结论影响然后得出错误的结论. - -注①: 对于技术好的, 希望能直接反驳这个问题本身是有问题的, 比如讲清楚 Javascript 中没有引用传递只是传递引用. 参见 [Is JavaScript a pass-by-reference or pass-by-value language?](http://stackoverflow.com/questions/518000/is-javascript-a-pass-by-reference-or-pass-by-value-language). 虽然说是复杂版, 但是这些知识对于 3年经验的同学真的应该是很简单的问题了. - -另外如果简历中有写 C++, 则必问 `指针与引用的区别`. - -## 内存释放 - -> Javascript 中不同类型以及不同环境下变量的内存都是何时释放? - -引用类型是在没有引用之后, 通过 v8 的 GC 自动回收, 值类型如果是处于闭包的情况下, 要等闭包没有引用才会被 GC 回收, 非闭包的情况下等待 v8 的新生代 (new space) 切换的时候回收. - -与前端 Js 不同, 2年以上经验的 Node.js 一定要开始注意内存了, 不说对 v8 的 GC 有多了解, 基础的内存释放一定有概念了, 并且要开始注意内存泄漏的问题了. - -你需要了解哪些操作一定会导致内存泄漏, 或者可以崩掉内存. 比如如下代码能否爆掉 V8 的内存? - -```javascript -let arr = []; -while(true) - arr.push(1); -``` - -然后上述代码与下方的情况有什么区别? - -```javascript -let arr = []; -while(true) - arr.push(); -``` - -如果 push 的是 `Buffer` 情况又会有什么区别? - -```javascript -let arr = []; -while(true) - arr.push(new Buffer(1000)); -``` - -思考完之后可以尝试找找别的情况如何爆掉 V8 的内存. 以及来聊聊内存泄漏? - -```javascript -var theThing = null -var replaceThing = function () { - var originalThing = theThing - var unused = function () { - if (originalThing) - console.log("hi") - } - theThing = { - longStr: new Array(1000000).join('*'), - someMethod: function () { - console.log(someMessage) - } - }; -}; -setInterval(replaceThing, 1000) -``` - -比如上述情况中 `unused` 的函数中持有了 `originalThing` 的引用, 使得每次旧的对象不会释放从而导致内存泄漏 (例子出自[《Node.js 垃圾回收》](https://eggggger.xyz/2016/10/22/node-gc/)) - -当然对于一些高水平的同学, 要求能清楚的了解 v8 内存 GC 的机制, 懂得内存快照等 (之后会在`调试/优化`的小结中讨论) 了. 比如 V8 中不同类型的数据存储的位置, 在内存释放的时候不同区域的不同策略等等. - -## ES6 新特性 - -推荐阅读阮一峰的 [《ECMAScript 6 入门》](http://es6.ruanyifeng.com/) - -比较简单的会问 `let` 与 `var` 的区别, 以及 `箭头函数` 与 `function` 的区别等等. - -深入的话, es6 有太多细节可以深入了. 比如结合 `引用` 的知识点来询问 `const` 方面的知识. 结合 `{}` 的使用与缺点来谈 `Set, Map` 等. 比如私有化的问题与 `symbol` 等等. - -其他像是 `闭包是什么?` 这种问烂了问题已经感觉没必要问了, 取而代之的是询问闭包应用的场景更加合理. 比如说, 如果回答者通常使用闭包实现数据的私有, 那么可以接着问 es6 的一些新特性 (例如 `class`, `symbol`) 能否实现私有, 如果能的话那为什么要用闭包? 亦或者是什么闭包中的数据/私有化的数据的内存什么时候释放? 等等. - -`...` 的使用上, 如何实现一个数组的去重 (使用 Set 可以加分). - -> const 定义的 Array 中间元素能否被修改? 如果可以, 那 const 修饰对象有什么意义? - -其中的值可以被修改. 意义上, 主要保护引用不被修改 (如用 [Map](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map) 等接口对引用的变化很敏感, 使用 const 保护引用始终如一是有意义的), 也适合用在 immutable 的场景. - -暂时写上这些, 之后会慢慢整理, 如果内容比较多可能单独归一类来讨论. +* `[Basic]` Type judgment +* `[Basic]` Scope +* `[Basic]` Reference +* `[Basic]` Memory release +* `[Basic]` ES6+ featrues diff --git a/sections/en-us/module.md b/sections/en-us/module.md index d7d69b8..ccfabc9 100644 --- a/sections/en-us/module.md +++ b/sections/en-us/module.md @@ -1,134 +1,6 @@ -# 模块 +# Module -* [`[Basic]` 模块机制](#模块机制) -* [`[Basic]` 热更新](#热更新) -* [`[Basic]` 上下文](#上下文) -* [`[Basic]` 包管理](#包管理) - -## 常见问题 - - -> 如何在不重启 node 进程的情况下热更新一个 js/json 文件? 这个问题本身是否有问题? - -可以清除掉 `require.cache` 的缓存重新 `require(xxx)`, 视具体情况还可以用 VM 模块重新执行. - -当然这个问题可能是典型的 [`X-Y Problem`](http://coolshell.cn/articles/10804.html), 使用 js 实现热更新很容易碰到 v8 优化之后各地拿到缓存的引用导致热更新 js 没意义. 当然热更新 json 还是可以简单一点比如用读取文件的方式来热更新, 但是这样也不如从 redis 之类的数据库中读取比较合理. - -## 简述 - -其他还有很多内容也是属于很 '基础' 的 Node.js 问题 (例如异步/线程等等), 但是由于归类的问题并没有放在这个分类中. 所以这里只简单讲几个之后没归类的基础问题. - - -## 模块机制 - -node 的基础中毫无疑问的应该是有关于模块机制的方面的, 也即 `require` 这个内置功能的一些原理的问题. - -关于模块互相引用之类的, 不了解的推荐先好好读读[官方文档](https://nodejs.org/dist/latest-v6.x/docs/api/modules.html). - -其实官方文档已经说得很清楚了, 每个 node 进程只有一个 VM 的上下文, 不会跟浏览器相差多少, 模块机制在文档中也描述的非常清楚了: - -```javascript -function require(...) { - var module = { exports: {} }; - ((module, exports) => { - // Your module code here. In this example, define a function. - function some_func() {}; - exports = some_func; - // At this point, exports is no longer a shortcut to module.exports, and - // this module will still export an empty default object. - module.exports = some_func; - // At this point, the module will now export some_func, instead of the - // default object. - })(module, module.exports); - return module.exports; -} -``` - -> 如果 a.js require 了 b.js, 那么在 b 中定义全局变量 `t = 111` 能否在 a 中直接打印出来? - -① 每个 `.js` 能独立一个环境只是因为 node 帮你在外层包了一圈自执行, 所以你使用 `t = 111` 定义全局变量在其他地方当然能拿到. 情况如下: - -```javascript - -// b.js -(function (exports, require, module, __filename, __dirname) { - t = 111; -})(); - -// a.js -(function (exports, require, module, __filename, __dirname) { - // ... - console.log(t); // 111 -})(); -``` - -> a.js 和 b.js 两个文件互相 require 是否会死循环? 双方是否能导出变量? 如何从设计上避免这种问题? - -② 不会, 先执行的导出空对象, 通过导出工厂函数让对方从函数去拿比较好避免. 模块在导出的只是 `var module = { exports: {} };` 中的 exports, 以从 a.js 启动为例, a.js 还没执行完 exports 就是 `{}` 在 b.js 的开头拿到的就是 `{}` 而已. - -另外还有非常基础和常见的问题, 比如 module.exports 和 exports 的区别这里也能一并解决了 exports 只是 module.exports 的一个引用. 没看懂可以在细看我以前发的[帖子](https://cnodejs.org/topic/5734017ac3e4ef7657ab1215). - -再晋级一点, 众所周知, node 的模块机制是基于 [`CommonJS`](http://javascript.ruanyifeng.com/nodejs/module.html) 规范的. 对于从前端转 node 的同学, 如果面试官想问的难一点会考验关于 [`CommonJS`](http://javascript.ruanyifeng.com/nodejs/module.html) 的一些问题. 比如比较 `AMD`, `CMD`, [`CommonJS`](http://javascript.ruanyifeng.com/nodejs/module.html) 三者的区别, 包括询问关于 node 中 `require` 的实现原理等. - -## 热更新 - -从面试官的角度看, `热更新` 是很多程序常见的问题. 对客户端而言, 热更新意味着不用换包, 当然也包含着 md5 校验/差异更新等复杂问题; 对服务端而言, 热更新意味着服务不用重启, 这样可用性较高同时也优雅和有逼格. 问的过程中可以一定程度的暴露应聘程序员的水平. - -从 PHP 转 node 的同学可能会有些想法, 比如 PHP 的代码直接刷上去就好了, 并没有所谓的重启. 而 node 重启看起来动作还挺大. 当然这里面的区别, 主要是与同时有 PHP 与 node 开发经验的同学可以讨论, 也是很好的切入点. - -在 Node.js 中做热更新代码, 牵扯到的知识点可能主要是 `require` 会有一个 `cache`, 有这个 `cache` 在, 即使你更新了 `.js` 文件, 在代码中再次 `require` 还是会拿到之前的编译好缓存在 v8 内存 (code space) 中的的旧代码. 但是如果只是单纯的清除掉 `require` 中的 `cache`, 再次 `require` 确实能拿到新的代码, 但是这时候很容易碰到各地维持旧的引用依旧跑的旧的代码的问题. 如果还要继续推行这种热更新代码的话, 可能要推翻当前的架构, 从头开始从新设计一下目前的框架. - -不过热更新 json 之类的配置文件的话, 还是可以简单的实现的, 更新 `require` 的 `cache` 可以实现, 不会有持有旧引用的问题, 可以参见我 2 年前写着玩的[例子](https://www.npmjs.com/package/auto-reload), 但是如果旧的引用一直被持有很容易出现内存泄漏, 而要热更新配置的话, 为什么不存数据库? 或者用 `zookeeper` 之类的服务? 通过更新文件还要再发布一次, 但是存数据库直接写个接口配个界面多爽你说是不是? - -所以这个问题其实本身其实是值得商榷的, 可能是典型的 [`X-Y Problem`](http://coolshell.cn/articles/10804.html), 不过聊起来确实是可以暴露水平. - -## 上下文 - -如果你已经了解 ①② 那么你也应该了解, 对于 Node.js 而言, 正常情况下只有一个上下文, 甚至于内置的很多方面例如 `require` 的实现只是在启动的时候运行了[内置的函数](https://github.com/nodejs/node/tree/master/lib). - -每个单独的 `.js` 文件并不意味着单独的上下文, 在某个 `.js` 文件中污染了全局的作用域一样能影响到其他的地方. - -而目前的 Node.js 将 VM 的接口暴露了出来, 可以让你自己创建一个新的 js 上下文, 这一点上跟前端 js 还是区别挺大的. 在执行外部代码的时候, 通过创建新的上下文沙盒 (sandbox) 可以避免上下文被污染: - -```javascript -'use strict'; -const vm = require('vm'); - -let code = -`(function(require) { - - const http = require('http'); - - http.createServer( (request, response) => { - response.writeHead(200, {'Content-Type': 'text/plain'}); - response.end('Hello World\\n'); - }).listen(8124); - - console.log('Server running at http://127.0.0.1:8124/'); -})`; - -vm.runInThisContext(code)(require); -``` - -这种执行方式与 eval 和 Function 有明显的区别. 关于 VM 更多的一些接口可以先阅读[官方文档 VM (虚拟机)](https://nodejs.org/dist/latest-v6.x/docs/api/vm.html) - -讲完这个知识点, 这里留下一个简单的问题, 既然可以通过新的上下文来避免污染, 那么`为什么 Node.js 不给每一个 `.js` 文件以独立的上下文来避免作用域被污染?` (反应不过来的同学还是别投简历了, 微笑脸) - - -## 包管理 - - -整理中... - -为什么我装了全局, 但是提示我 not found - -npm -yarn - -锁版本 - -lerna:一个用户管理多个包模块的工具。 - -left-pad事件 - -greenkeeper 等 +* `[Basic]` Module +* `[Basic]` Hotfix +* `[Basic]` Context +* `[Basic]` Package Manager diff --git a/sections/en-us/network.md b/sections/en-us/network.md index e8f1605..d54e571 100644 --- a/sections/en-us/network.md +++ b/sections/en-us/network.md @@ -1,334 +1,8 @@ # Network -* [`[Doc]` Net (网络)](#net) -* [`[Doc]` UDP/Datagram](#udp) -* [`[Doc]` HTTP](#http) -* [`[Doc]` DNS (域名服务器)](#dns) -* [`[Doc]` ZLIB (压缩)](#zlib) -* [`[Point]` RPC](#rpc) - - -## Net - -目前互联化的核心是建立在 TCP/IP 协议的基础上的, 这些协议将数据分割成小的数据包进行传输, 并且解决传输过程中各种各样复杂的问题. 关于协议的具体细节推荐阅读 W.Richard Stevens 的[《TCP/IP 详解 卷1:协议》](https://www.amazon.cn/TCP-IP%E8%AF%A6%E8%A7%A3%E5%8D%B71-%E5%8D%8F%E8%AE%AE-W-Richard-Stevens/dp/B00116OTVS/), 本文不做赘述, 只是列举一些常见的知识点, 新人推荐看[《图解TCP/IP》](https://www.amazon.cn/%E5%9B%BE%E4%B9%A6/dp/B00DMS9990/), 抓包工具推荐看[《Wireshark网络分析就这么简单》](https://www.amazon.cn/%E5%9B%BE%E4%B9%A6/dp/B00PB5QQ84/). - -### 粘包 - -默认情况下, TCP 连接会启用延迟传送算法 (Nagle 算法), 在数据发送之前缓存他们. 如果短时间有多个数据发送, 会缓冲到一起作一次发送 (缓冲大小见 `socket.bufferSize`), 这样可以减少 IO 消耗提高性能. - -如果是传输文件的话, 那么根本不用处理粘包的问题, 来一个包拼一个包就好了. 但是如果是多条消息, 或者是别的用途的数据那么久需要处理粘包. - -可以参见网上流传比较广的一个例子, 连续调用两次 send 分别发送两段数据 data1 和 data2, 在接收端有以下几种常见的情况: - -* A. 先接收到 data1, 然后接收到 data2 . -* B. 先接收到 data1 的部分数据, 然后接收到 data1 余下的部分以及 data2 的全部. -* C. 先接收到了 data1 的全部数据和 data2 的部分数据, 然后接收到了 data2 的余下的数据. -* D. 一次性接收到了 data1 和 data2 的全部数据. - -其中的 BCD 就是我们常见的粘包的情况. 而对于处理粘包的问题, 常见的解决方案有: - -* 1. 多次发送之前间隔一个等待时间 -* 2. 关闭 Nagle 算法 -* 3. 进行封包/拆包 - -***方案1*** - -只需要等上一段时间再进行下一次 send 就好, 适用于交互频率特别低的场景. 缺点也很明显, 对于比较频繁的场景而言传输效率实在太低. 不过几乎用做什么处理. - -***方案2*** - -关闭 Nagle 算法, 在 Node.js 中你可以通过 [`socket.setNoDelay()`](https://nodejs.org/dist/latest-v6.x/docs/api/net.html#net_socket_setnodelay_nodelay) 方法来关闭 Nagle 算法, 让每一次 send 都不缓冲直接发送. - -该方法比较适用于每次发送的数据都比较大 (但不是文件那么大), 并且频率不是特别高的场景. 如果是每次发送的数据量比较小, 并且频率特别高的, 关闭 Nagle 纯属自废武功. - -另外, 该方法不适用于网络较差的情况, 因为 Nagle 算法是在服务端进行的包合并情况, 但是如果短时间内客户端的网络情况不好, 或者应用层由于某些原因不能及时将 TCP 的数据 recv, 就会造成多个包在客户端缓冲从而粘包的情况. (如果是在稳定的机房内部通信那么这个概率是比较小可以选择忽略的) - -***方案3*** - -封包/拆包是目前业内常见的解决方案了. 即给每个数据包在发送之前, 于其前/后放一些有特征的数据, 然后收到数据的时候根据特征数据分割出来各个数据包. - -### 可靠传输 - -为每一个发送的数据包分配一个序列号(SYN, Synchronise packet), 每一个包在对方收到后要返回一个对应的应答数据包(ACK, Acknowledgedgement),. 发送方如果发现某个包没有被对方 ACK, 则会选择重发. 接收方通过 SYN 序号来保证数据的不会乱序(reordering), 发送方通过 ACK 来保证数据不缺漏, 以此参考决定是否重传. 关于具体的序号计算, 丢包时的重传机制等可以参见阅读陈皓的 [《TCP的那些事儿(上)》](http://coolshell.cn/articles/11564.html) 此处不做赘述. - -### window - -TCP 头里有一个 Window 字段, 是接收端告诉发送端自己还有多少缓冲区可以接收数据的. 发送端就可以根据接收端的处理能力来发送数据, 从而避免接收端处理不过来. 详细参见陈皓的 [《TCP的那些事儿(下)》](http://coolshell.cn/articles/11609.html) - -> window 是否设置的越大越好? - -类似木桶理论, 一个木桶能装多少水, 是由最短的那块木板决定的. 一个 TCP 连接的 window 是由该连接中间一连串设备中 window 最小的那一个设备决定的. - -### backlog - -![图片出处 http://www.cnxct.com/something-about-phpfpm-s-backlog/](/assets/socket-backlog.png) - -关于该 backlog 的定义参见 [man](https://linux.die.net/man/2/listen) 手册: - -> The behavior of the backlog argument on TCP sockets changed with Linux 2.2. Now it specifies the queue length for completely established sockets waiting to be accepted, instead of the number of incomplete connection requests. - -backlog 用于设置客户端与服务端 `ESTABLISHED` 之后等待 accept 的队列长图 (如上图中的 accept queue). 如果 backlog 过小, 在并发连接大的情况下容易导致 accept queue 装满之后断开连接. 但是如果将这个队列设置的特别大, 那么假定连接数并发量是 65525, 以 php-fpm 的 qps 5000 为例, 处理完约耗时 13s, 而这段时间中连接可能早已被 nginx 或者客户端断开, 那么我们去 accept 这个 socket 时只会拿到一个 broken pipe (该例子出处见 [PHP 源码 Set FPM_BACKLOG_DEFAULT to 511](https://github.com/php/php-src/commit/ebf4ffc9354f316f19c839a114b26a564033708a)). 经过我也不懂的计算 backlog 的长度默认是 511. - -另外提一句, 这个 backlog 是通过系统指定时是通过 `somaxconn` 参数来指定 accept queue 的. 而 `tcp_max_syn_backlog` 参数指定的是 SYN queue 的长度. - -### 状态机 - -![tcpfsm.png](/assets/tcpfsm.png) - -关于网络连接的建立以及断开, 存在着一个复杂的状态转换机制, 完整的状态表参见 [《The TCP/IP Guide》](http://www.tcpipguide.com/free/t_TCPOperationalOverviewandtheTCPFiniteStateMachineF-2.htm) - -state|简述 ------|--- -CLOSED|连接关闭, 所有连接的初始状态 -LISTEN|监听状态, 等待客户端发送 SYN -SYN-SENT|客户端发送了 SYN, 等待服务端回复 -SYN-RECEIVED|双方都收到了 SYN, 等待 ACK -ESTABLISHED| SYN-RECEIVED 收到 ACK 之后, 状态切换为连接已建立. -CLOSE-WAIT|被动方收到了关闭请求(FIN)后, 发送 ACK, 如果有数据要发送, 则发送数据, 无数据发送则回复 FIN. 状态切换到 LAST-ACK -LAST-ACK|等待对方 ACK 当前设备的 CLOSE-WAIT 时发送的 FIN, 等到则切换 CLOSED -FIN-WAIT-1|主动方发送 FIN, 等待 ACK -FIN-WAIT-2|主动方收到被动方的 ACK, 等待 FIN -CLOSING|主动方收到了FIN, 却没收到 FIN-WAIT-1 时发的 ACK, 此时等待那个 ACK -TIME-WAIT|主动方收到 FIN, 返回收到对方 FIN 的 ACK, 等待对方是否真的收到了 ACK, 如果过一会又来一个 FIN, 表示对方没收到, 这时要再 ACK 一次 - -> `TIME_WAIT` 是什么情况? 出现过多的 `TIME_WAIT` 可能是什么原因? - -`TIME_WAIT` 是连接的某一方 (可能是服务端也可能是客户端) 主动断开连接时, 四次挥手等待被断开的一方是否收到最后一次挥手 (ACK) 的状态. 如果在等待时间中, 再次收到第三次挥手 (FIN) 表示对方没收到最后一次挥手, 这时要再 ACK 一次. 这个等待的作用是避免出现连接混用的情况 (`prevent potential overlap with new connections` see [TCP Connection Termination](http://www.tcpipguide.com/free/t_TCPConnectionTermination.htm) for more). - -出现大量的 `TIME_WAIT` 比较常见的情况是, 并发量大, 服务器在短时间断开了大量连接. 对应 HTTP server 的情况可能是没开启 `keepAlive`. 如果有开 `keepAlive`, 一般是等待客户端自己主动断开, 那么`TIME_WAIT` 就只存在客户端, 而服务端则是 `CLOSE_WAIT` 的状态, 如果服务端出现大量 `CLOSE_WAIT`, 意味着当前服务端建立的连接大面积的被断开, 可能是目标服务集群重启之类. - - -## UDP - -> TCP/UDP 的区别? UDP 有粘包吗? - -协议|连接性|双工性|可靠性|有序性|有界性|拥塞控制|传输速度|量级|头部大小 ----|---|---|---|---|---|---|---|---|--- -TCP|面向连接
(Connection oriented)|全双工(1:1)|可靠
(重传机制)|有序
(通过SYN排序)|无, 有[粘包情况](#粘包)|有|慢|低|20~60字节 -UDP|无连接
(Connection less)|n:m|不可靠
(丢包后数据丢失)|无序|有消息边界, **无粘包**|无|快|高|8字节 - -UDP socket 支持 n 对 m 的连接状态, 在[官方文档](https://nodejs.org/dist/latest-v6.x/docs/api/dgram.html)中有写到在 `dgram.createSocket(options[, callback])` 中的 option 可以指定 `reuseAddr` 即 `SO_REUSEADDR`标志. 通过 `SO_REUSEADDR` 可以简单的实现 n 对 m 的多播特性 (不过仅在支持多播的系统上才有). - - -### 常见的应用场景 - - - - - - - - - - - - - - - -
传输层协议应用应用层协议
TCP电子邮件SMTP
终端连接TELNET
终端连接SSH
万维网HTTP
文件传输FTP
UDP域名解析DNS
简单文件传输TFTP
网络时间校对NTP
网络文件系统NFS
路由选择RIP
IP电话-
流式多媒体通信-
- -简单的说, UDP 速度快, 开销低, 不用封包/拆包允许丢一部分数据, 监控统计/日志数据上报/流媒体通信等场景都可以用 UDP. 目前 Node.js 的项目中使用 UDP 比较流行的是 [StatsD](https://github.com/etsy/statsd) 监控服务. - - -## HTTP - -目前世界上运行最良好的分布式集群, 莫过于当前的万维网了 (http servers) 了. 目前前端工程师也都是靠 HTTP 协议吃饭的, 所以 2-3 年的前端同学都应该对 HTTP 有比较深的理解了, 所以这里不做太多的赘述. 推荐书籍[《图解HTTP》](https://www.amazon.cn/%E5%9B%BE%E4%B9%A6/dp/B00JTQK1L4/), 博客[HTTP 协议入门](http://www.ruanyifeng.com/blog/2016/08/http.html). - -另外最近几年开始大家对 HTTP 的面试的考察也渐渐偏向[理解 RESTful 架构](http://www.ruanyifeng.com/blog/2011/09/restful.html). 简单的说, RESTful 是把每个 URI 当做资源 (Resources), 通过 method 作为动词来对资源做不同的动作, 然后服务器返回 status 来得知资源状态的变化 (State Transfer); - -### method/status - -因为 HTTP 的方法 (method) 与状态码 (status) 讲解太常见, 你可以使用如下代码打印出来自己看 Node.js 官方定义的, 完整的就不列举了. - -```javascript -const http = require('http'); - -console.log(http.METHODS); -console.log(http.STATUS_CODES); -``` - -一个常见的 method 列表, 关于这些 method 在 RESTful 中的一些应用的详细可以参见[Using HTTP Methods for RESTful Services](http://www.restapitutorial.com/lessons/httpmethods.html) - -methods|CRUD|幂等|缓存 ----|---|---|--- -GET|Read|✓|✓ -POST|Create|| -PUT|Update/Replace|✓ -PATCH|Update/Modify|✓ -DELETE|Delete|✓ - -> GET 和 POST 有什么区别? - -网上有很多讲这个的, 比如从书签, url 等前端的角度去看他们的区别这里不赘述. 而从后端的角度看, 前两年出来一个 《GET 和 POST 没有区别》(出处不好考究, 就没贴了) 的文章比较有名, 早在我刚学 PHP 的时候也有过这种疑惑, 刚学 Node 的时候发现不能像 PHP 那样同时处理 GET 和 POST 的时候还很不适应. 后来接触 RESTful 才意识到, 这两个东西最根本的差别是语义, 引申了看, 协议 (protocol) 这种东西就是人与人之间协商的约定, 什么行为是什么作用都是"约定"好的, 而不是强制使用的, 非要把 GET 当 POST 这样不遵守约定的做法我们也爱莫能助. - -跑题了, 简而言之, 讨论这二者的区别最好从 RESTful 提倡的语义角度来讲比较符合当代程序员的逼格比较合理. - -> POST 和 PUT 有什么区别? - -POST 是新建 (create) 资源, 非幂等, 同一个请求如果重复 POST 会新建多个资源. PUT 是 Update/Replace, 幂等, 同一个 PUT 请求重复操作会得到同样的结果. - - -### headers - -HTTP headers 是在进行 HTTP 请求的交互过程中互相支会对方一些信息的主要字段. 比如请求 (Request) 的时候告诉服务端自己能接受的各项参数, 以及之前就存在本地的一些数据等. 详细各位可以参见 wikipedia: - -* [Request fields](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields) -* [Response fields](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Response_fields) - -> cookie 与 session 的区别? 服务端如何清除 cookie? - -主要区别在于, session 存在服务端, cookie 存在客户端. session 比 cookie 更安全. 而且 cookie 不一定一直能用 (可能被浏览器关掉). 服务端可以通过设置 cookie 的值为空并设置一个及时的 expires 来清除存在客户端上的 cookie. - -> 什么是跨域请求? 如何允许跨域? - -出于安全考虑, 默认情况下使用 XMLHttpRequest 和 Fetch 发起 HTTP 请求必须遵守同源策略, 即只能向相同域名请求. 向不同域名的请求被称作跨域请求 (cross-origin HTTP request). 可以通过设置 [CORS headers](https://developer.mozilla.org/en-US/docs/Glossary/CORS) 即 `Access-Control-Allow-` 系列来允许跨域. 例如: - -``` -location ~* ^/(?:v1|_) { - if ($request_method = OPTIONS) { return 200 ''; } - header_filter_by_lua ' - ngx.header["Access-Control-Allow-Origin"] = ngx.var.http_origin; # 这样相当于允许所有来源了 - ngx.header["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, PATCH, OPTIONS"; - ngx.header["Access-Control-Allow-Credentials"] = "true"; - ngx.header["Access-Control-Allow-Headers"] = "Content-Type"; - '; - proxy_pass http://localhost:3001; -} -``` - -> `Script error.` 是什么错误? 如何拿到更详细的信息? - -接上题, 由于同源性策略 (CORS), 如果你引用的 js 脚本所在的域与当前域不同, 那么浏览器会把 onError 中的 msg 替换为 `Script error.` 要拿到详细错误的方法, 处理配好 `Access-Control-Allow-Origin` 还有在引用脚本的时候指定 `crossorigin` 例如: - -```html - -``` - -详见 [Javascript Script Error.](https://sentry.io/answers/javascript-script-error/) - - -### Agent - -Node.js 中的 `http.Agent` 用于池化 HTTP 客户端请求的 socket (pooling sockets used in HTTP client requests). 也就是复用 HTTP 请求时候的 socket. 如果你没有指定 Agent 的话, 默认用的是 `http.globalAgent`. - -另外, 目前在 Node.js 的 6.8.1(包括)到 6.10(不包括)版本中发现一个问题: - -* 1. 你将 keepAlive 设置为 `true` 时, socket 有复用 -* 2. 即使 keepAlive 没有设置成 `true` 但是长时间内有大量请求时, 同样有复用 socket (复用情况参见[@zcs19871221](https://github.com/zcs19871221)的[解析](https://github.com/zcs19871221/mydoc/blob/master/nodejsAgent.md)) - -1 和 2 这两种情况下, 一旦设置了 request timeout, 由于 socket 一直未销毁, 如果你在请求完成以后没有注意清除该事件, 会导致事件重复监听, 且该事件闭包引用了 req, 会导致内存泄漏. - -如果有疑虑的话可以参见 Node 官方讨论的 [issue](https://github.com/nodejs/node/issues/9268) 以及引入此 bug 的 [commit](https://github.com/nodejs/node/blob/ee7af01b93cc46f1848f6962ad2d6c93f319341a/lib/_http_client.js#L565), 如果此处描述有疑问可以在本 repo 的 [issue](https://github.com/ElemeFE/node-interview/issues/19) 中指出. - - -### socket hang up - -hang up 有挂断的意思, socket hang up 也可以理解为 socket 被挂断. 在 Node.js 中当你要 response 一个请求的时候, 发现该这个 socket 已经被 "挂断", 就会就会报 socket hang up 错误. - -[Node.js 中源码的情况:](https://github.com/nodejs/node/blob/v6.x/lib/_http_client.js#L286) - -```javascript -function socketCloseListener() { - var socket = this; - var req = socket._httpMessage; - - // Pull through final chunk, if anything is buffered. - // the ondata function will handle it properly, and this - // is a no-op if no final chunk remains. - socket.read(); - - // NOTE: It's important to get parser here, because it could be freed by - // the `socketOnData`. - var parser = socket.parser; - req.emit('close'); - if (req.res && req.res.readable) { - // Socket closed before we emitted 'end' below. - req.res.emit('aborted'); - var res = req.res; - res.on('end', function() { - res.emit('close'); - }); - res.push(null); - } else if (!req.res && !req.socket._hadError) { - // This socket error fired before we started to - // receive a response. The error needs to - // fire on the request. - req.emit('error', createHangUpError()); // <------------------- socket hang up - req.socket._hadError = true; - } - - // Too bad. That output wasn't getting written. - // This is pretty terrible that it doesn't raise an error. - // Fixed better in v0.10 - if (req.output) - req.output.length = 0; - if (req.outputEncodings) - req.outputEncodings.length = 0; - - if (parser) { - parser.finish(); - freeParser(parser, req, socket); - } -} -``` - -典型的情况是用户使用浏览器, 请求的时间有点长, 然后用户简单的按了一下 F5 刷新页面. 这个操作会让浏览器取消之前的请求, 然后导致服务端 throw 了一个 socket hang up. - -详见万能的 stackoverflow: [NodeJS - What does “socket hang up” actually mean?](http://stackoverflow.com/questions/16995184/nodejs-what-does-socket-hang-up-actually-mean) - - -## DNS - -早期可以用 TCP/IP 通信之后, 有一个比较蛋疼的问题, 就是 ip 都是一串比较长的数字, 比较难记, 于是大家想了个办法, 给每个 ip 取个好记一点的名字比如 `Alan -> 192.168.0.11` 这样只需要记住好记的名字即可, 随着这个名字的规范化最终变成了今天的域名 (Domain name), 而帮助别人记录这个名字的服务就叫域名解析服务 (Domain Name Service). - -DNS 服务主要基于 UDP, 这里简单介绍 Node.js 实现的接口中的两个方法: - -方法|功能|同步|网络请求|速度 ----|---|---|---|--- -.lookup(hostname[, options], cb)|通过系统自带的 DNS 缓存 (如 `/etc/hosts`)|同步|无|快 -.resolve(hostname[, rrtype], cb)|通过系统配置的 DNS 服务器指定的记录 (rrtype指定)|异步|有|慢 - -> DNS 模块中 .lookup 与 .resolve 的区别? - -当你要解析一个域名的 ip 时, 通过 .lookup 查询直接调用 `getaddrinfo` 来拿取地址, 速度很快, 但是如果本地的 hosts 文件被修改了, .lookup 就会拿 hosts 文件中的地方, 而 .resolve 依旧是外部正常的地址. - -由于 .lookup 是同步的, 所以如果由于什么不可控的原因导致 `getaddrinfo` 缓慢或者阻塞是会影响整个 Node 进程的, 参见[文档](https://nodejs.org/dist/latest-v6.x/docs/api/dns.html#dns_dns_lookup). - -> hosts 文件是什么? 什么叫 DNS 本地解析? - -hosts 文件是个没有扩展名的系统文件,其作用就是将网址域名与其对应的 IP 地址建立一个关联“数据库”,当用户在浏览器中输入一个需要登录的网址时,系统会首先自动从 hosts 文件中寻找对应的IP地址。 - -当我们访问一个域名时,实际上需要的是访问对应的 IP 地址。这时候,获取 IP 地址的方式,先是读取浏览器缓存,如果未命中 => 接着读取本地 hosts 文件,如果还是未命中 => 则向 DNS 服务器发送请求获取。在向 DNS 服务器获取 IP 地址之前的行为,叫做 DNS 本地解析。 - -## ZLIB - -在网络传输过程中, 如果网速稳定的情况下, 对数据进行压缩, 压缩比率越大, 那么传输的效率就越高等同于速度越快了. zlib 模块提供了 Gzip/Gunzip, Deflate/Inflate 和 DeflateRaw/InflateRaw 等压缩方法的类, 这些类接收相同的参数, 都属于可读写的 Stream 实例. - -TODO - -## RPC - -RPC (Remote Procedure Call Protocol) 基于 TCP/IP 来实现调用远程服务器的方法, 与 http 同属应用层. 常用于构建集群, 以及微服务 (推荐一本[《Node.js 微服务》](https://www.amazon.cn/%E5%9B%BE%E4%B9%A6/dp/B01MXY8ARP)虽然我还没看完) - -常见的 RPC 方式: - -* [Thrift](http://thrift.apache.org/) -* HTTP -* MQ - -### Thrift - -> **Thrift**是一种[接口描述语言](https://zh.wikipedia.org/wiki/%E6%8E%A5%E5%8F%A3%E6%8F%8F%E8%BF%B0%E8%AF%AD%E8%A8%80 "接口描述语言")和二进制通讯协议,它被用来定义和创建跨语言的服务。它被当作一个[远程过程调用](https://zh.wikipedia.org/wiki/%E8%BF%9C%E7%A8%8B%E8%BF%87%E7%A8%8B%E8%B0%83%E7%94%A8 "远程过程调用")(RPC)框架来使用,是由[Facebook](https://zh.wikipedia.org/wiki/Facebook "Facebook")为“大规模跨语言服务开发”而开发的。它通过一个代码生成引擎联合了一个软件栈,来创建不同程度的、无缝的[跨平台](https://zh.wikipedia.org/wiki/%E8%B7%A8%E5%B9%B3%E5%8F%B0 "跨平台")高效服务,可以使用[C#](https://zh.wikipedia.org/wiki/C%E2%99%AF "C♯")、[C++](https://zh.wikipedia.org/wiki/C%2B%2B "C++")(基于[POSIX](https://zh.wikipedia.org/wiki/POSIX "POSIX")兼容系统)、Cappuccino、[Cocoa](https://zh.wikipedia.org/wiki/Cocoa "Cocoa")、[Delphi](https://zh.wikipedia.org/wiki/Delphi "Delphi")、[Erlang](https://zh.wikipedia.org/wiki/Erlang "Erlang")、[Go](https://zh.wikipedia.org/wiki/Go "Go")、[Haskell](https://zh.wikipedia.org/wiki/Haskell "Haskell")、[Java](https://zh.wikipedia.org/wiki/Java "Java")、[Node.js](https://zh.wikipedia.org/wiki/Node.js "Node.js")、[OCaml](https://zh.wikipedia.org/wiki/OCaml "OCaml")、[Perl](https://zh.wikipedia.org/wiki/Perl "Perl")、[PHP](https://zh.wikipedia.org/wiki/PHP "PHP")、[Python](https://zh.wikipedia.org/wiki/Python "Python")、[Ruby](https://zh.wikipedia.org/wiki/Ruby "Ruby")和[Smalltalk](https://zh.wikipedia.org/wiki/Smalltalk "Smalltalk")。虽然它以前是由Facebook开发的,但它现在是[Apache软件基金会](https://zh.wikipedia.org/wiki/Apache%E8%BD%AF%E4%BB%B6%E5%9F%BA%E9%87%91%E4%BC%9A "Apache软件基金会")的[开源](https://zh.wikipedia.org/wiki/%E5%BC%80%E6%BA%90 "开源")项目了。该实现被描述在2007年4月的一篇由Facebook发表的技术论文中,该论文现由Apache掌管。 - -### HTTP - -使用 HTTP 协议来进行 RPC 调用也是很常见的, 相比 TCP 连接, 通过通过 HTTP 的方式性能会差一些, 但是在使用以及调试上会简单一些. 近期比较有名的框架参见 [gRPC](http://www.grpc.io/): - -> gRPC is an open source remote procedure call (RPC) system initially developed at Google. It uses HTTP/2 for transport, Protocol Buffers as the interface description language, and provides features such as authentication, bidirectional streaming and flow control, blocking or nonblocking bindings, and cancellation and timeouts. It generates cross-platform client and server bindings for many languages. - -### MQ - -使用消息队列 (Message Queue) 来进行 RPC 调用 (RPC over mq) 在业内有不少例子, 比较适合业务解耦/广播/限流等场景. - -TODO +* `[Doc]` Net +* `[Doc]` UDP/Datagram +* `[Doc]` HTTP +* `[Doc]` DNS +* `[Doc]` ZLIB +* `[Point]` RPC diff --git a/sections/en-us/os.md b/sections/en-us/os.md index a8d4c31..30ac190 100644 --- a/sections/en-us/os.md +++ b/sections/en-us/os.md @@ -1,374 +1,8 @@ # OS * `[Doc]` TTY -* `[Doc]` OS (操作系统) -* `[Doc]` 命令行参数 -* `[Basic]` 负载 +* `[Doc]` OS +* `[Doc]` Command Line Options +* `[Basic]` Load * `[Point]` CheckList -* `[Basic]` 指标 - -## TTY - -"tty" 原意是指 "teletype" 即打字机, "pty" 则是 "pseudo-teletype" 即伪打字机. 在 Unix 中, `/dev/tty*` 是指任何表现的像打字机的设备, 例如终端 (terminal). - -你可以通过 `w` 命令查看当前登录的用户情况, 你会发现每登录了一个窗口就会有一个新的 tty. - -```shell -$ w - 11:49:43 up 482 days, 19:38, 3 users, load average: 0.03, 0.08, 0.07 -USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT -dev pts/0 10.0.128.252 10:44 1:01m 0.09s 0.07s -bash -dev pts/2 10.0.128.252 11:08 2:07 0.17s 0.14s top -root pts/3 10.0.240.2 11:43 7.00s 0.04s 0.00s w -``` - -使用 ps 命令查看进程信息中也有 tty 的信息: - -```shell -$ ps -x - PID TTY STAT TIME COMMAND - 5530 ? S 0:00 sshd: dev@pts/3 - 5531 pts/3 Ss+ 0:00 -bash -11296 ? S 0:00 sshd: dev@pts/4 -11297 pts/4 Ss 0:00 -bash -13318 pts/4 R+ 0:00 ps -x -23733 ? Ssl 2:53 PM2 v1.1.2: God Daemon -``` - -其中为 `?` 的是没有依赖 TTY 的进程, 即[守护进程](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#%E5%AE%88%E6%8A%A4%E8%BF%9B%E7%A8%8B). - -在 Node.js 中你可以通过 stdio 的 isTTY 来判断当前进程是否处于 TTY (如终端) 的环境. - -```shell -$ node -p -e "Boolean(process.stdout.isTTY)" -true -$ node -p -e "Boolean(process.stdout.isTTY)" | cat -false -``` - -## OS - -通过 OS 模块可以获取到当前系统一些基础信息的辅助函数. - -|属性|描述| -|---|---| -|os.EOL|根据当前系统, 返回当前系统的 `End Of Line`| -|os.arch()|返回当前系统的 CPU 架构, 如 `'x86'` 和 `'x64'`| -|os.constants|返回系统常量| -|os.cpus()|返回 CPU 每个核的信息| -|os.endianness()|返回 CPU 字节序, 如果是大端字节序返回 `BE`, 小端字节序则 `LE`| -|os.freemem()|返回系统空闲内存的大小, 单位是字节| -|os.homedir()|返回当前用户的根目录| -|os.hostname()|返回当前系统的主机名| -|os.loadavg()|返回负载信息| -|os.networkInterfaces()|返回网卡信息 (类似 `ifconfig`)| -|os.platform()|返回编译时指定的平台信息, 如 `win32`, `linux`, 同 `process.platform()`| -|os.release()|返回操作系统的分发版本号| -|os.tmpdir()|返回系统默认的临时文件夹| -|os.totalmem()|返回总内存大小(同内存条大小)| -|os.type()|根据 `[uname](https://en.wikipedia.org/wiki/Uname#Examples)` 返回系统的名称| -|os.uptime()|返回系统的运行时间,单位是秒| -|os.userInfo([options])|返回当前用户信息| - -> 不同操作系统的换行符 (EOL) 有什么区别? - -end of line (EOL) 同 newline, line ending, 以及 line break. - -通常由 line feed (LF, `\n`) 和 carriage return (CR, `\r`) 组成. 常见的情况: - -|符号|系统| -|---|---| -|LF|在 Unix 或 Unix 相容系统 (GNU/Linux, AIX, Xenix, Mac OS X, ...)、BeOS、Amiga、RISC OS| -|CR+LF|MS-DOS、微软视窗操作系统 (Microsoft Windows)、大部分非 Unix 的系统| -|CR|Apple II 家族, Mac OS 至版本9| - -如果不了解 EOL 跨系统的兼容情况, 那么在处理文件的行分割/行统计等情况时可能会被坑. - -### OS 常量 - -* 信号常量 (Signal Constants), 如 `SIGHUP`, `SIGKILL` 等. -* POSIX 错误常量 (POSIX Error Constants), 如 `EACCES`, `EADDRINUSE` 等. -* Windows 错误常量 (Windows Specific Error Constants), 如 `WSAEACCES`, `WSAEBADF` 等. -* libuv 常量 (libuv Constants), 仅 `UV_UDP_REUSEADDR`. - - -## Path - -Node.js 内置的 path 是用于处理路径问题的模块. 不过众所周知, 路径在不同操作系统下又不可调和的差异. - -### Windows vs. POSIX - -|POSIX|值|Windows|值| -|---|---|---|---| -|path.posix.sep|`'/'`|path.win32.sep|`'\\'`| -|path.posix.normalize('/foo/bar//baz/asdf/quux/..')|`'/foo/bar/baz/asdf'`|path.win32.normalize('C:\\temp\\\\foo\\bar\\..\\')|`'C:\\temp\\foo\\'`| -|path.posix.basename('/tmp/myfile.html')|`'myfile.html'`|path.win32.basename('C:\\temp\\myfile.html')|`'myfile.html'`| -|path.posix.join('/asdf', '/test.html')|`'/asdf/test.html'`|path.win32.join('/asdf', '/test.html')|`'\\asdf\\test.html'`| -|path.posix.relative('/root/a', '/root/b')|`'../b'`|path.win32.relative('C:\\a', 'c:\\b')|`'..\\b'` -|path.posix.isAbsolute('/baz/..')|`true`|path.win32.isAbsolute('C:\\foo\\..')|`true`| -|path.posix.delimiter|`':'`|path.win32.delimiter|`','`| -|process.env.PATH|`'/usr/bin:/bin'`|process.env.PATH|`C:\Windows\system32;C:\Program Files\node\'`| -|PATH.split(path.posix.delimiter)|`['/usr/bin', '/bin']`|PATH.split(path.win32.delimiter)|`['C:\\Windows\\system32', 'C:\\Program Files\\node\\']`| - - -看了上表之后, 你应该了解到当你处于某个平台之下的时候, 所使用的 `path` 模块的方法其实就是对应的平台的方法, 例如笔者这里用的是 mac, 所以: - -```javascript -const path = require('path'); -console.log(path.basename === path.posix.basename); // true -``` - -如果你处于其中某一个平台, 但是要处理另外一个平台的路径, 需要注意这个跨平台的问题. - -### path 对象 - -on POSIX: - -```javascript -path.parse('/home/user/dir/file.txt') -// Returns: -// { -// root : "/", -// dir : "/home/user/dir", -// base : "file.txt", -// ext : ".txt", -// name : "file" -// } -``` - -```javascript -┌─────────────────────┬────────────┐ -│ dir │ base │ -├──────┬ ├──────┬─────┤ -│ root │ │ name │ ext │ -" / home/user/dir / file .txt " -└──────┴──────────────┴──────┴─────┘ -``` - -on Windows: - -```javascript -path.parse('C:\\path\\dir\\file.txt') -// Returns: -// { -// root : "C:\\", -// dir : "C:\\path\\dir", -// base : "file.txt", -// ext : ".txt", -// name : "file" -// } -``` - -```javascript -┌─────────────────────┬────────────┐ -│ dir │ base │ -├──────┬ ├──────┬─────┤ -│ root │ │ name │ ext │ -" C:\ path\dir \ file .txt " -└──────┴──────────────┴──────┴─────┘ -``` - -### path.extname(path) - -|case|return| -|---|---| -|path.extname('index.html')|`'.html'`| -|path.extname('index.coffee.md')|`'.md'`| -|path.extname('index.')|`'.'`| -|path.extname('index')|`''`| -|path.extname('.index')|`''`| - - -## 命令行参数 - -命令行参数 (Command Line Options), 即对 CLI 使用上的一些文档. 关于 CLI 主要有 4 种使用方式: - -* node [options] [v8 options] [script.js | -e "script"] [arguments] -* node debug [script.js | -e "script" | :] … -* node --v8-options -* 无参数直接启动 REPL 环境 - -### Options - -|参数|简介| -|---|---| -|-v, --version|查看当前 node 版本| -|-h, --help|查看帮助文档| -|-e, --eval "script"|将参数字符串当做代码执行 -|-p, --print "script"|打印 `-e` 的返回值 -|-c, --check|检查语法并不执行 -|-i, --interactive|即使 stdin 不是终端也打开 REPL 模式 -|-r, --require module|在启动前预先 `require` 指定模块 -|--no-deprecation|关闭废弃模块警告 -|--trace-deprecation|打印废弃模块的堆栈跟踪信息 -|--throw-deprecation|执行废弃模块时抛出错误 -|--no-warnings|无视报警(包括废弃警告) -|--trace-warnings|打印警告的 stack (包括废弃模块) -|--trace-sync-io|只要检测到异步 I/O 出于 Event loop 的开头就打印 stack trace -|--zero-fill-buffers|自动初始化(zero-fill) **Buffer** 和 **SlowBuffer** -|--preserve-symlinks|在解析和缓存模块时指示模块加载程序保存符号链接 -|--track-heap-objects|为堆快照跟踪堆对象的分配情况 -|--prof-process|使用 v8 选项 `--prof` 生成 Profilling 报告 -|--v8-options|显示 v8 命令行选项 -|--tls-cipher-list=list|指明替代的默认 TLS 加密器列表 -|--enable-fips|在启动时开启 FIPS-compliant crypto -|--force-fips|在启动时强制实施 FIPS-compliant -|--openssl-config=file|启动时加载 OpenSSL 配置文件 -|--icu-data-dir=file|指定ICU数据加载路径 - -### 环境变量 - -|环境变量|简介| -|----|----| -|`NODE_DEBUG=module[,…]`|指定要打印调试信息的核心模块列表 -|`NODE_PATH=path[:…]`|指定搜索目录模块路径的前缀列表 -|`NODE_DISABLE_COLORS=1`|关闭 REPL 的颜色显示 -|`NODE_ICU_DATA=file`|ICU (Intl object) 数据路径 -|`NODE_REPL_HISTORY=file`|持久化存储REPL历史文件的路径 -|`NODE_TTY_UNSAFE_ASYNC=1`|设置为1时, 将同步操作 stdio (如 console.log 变成同步) -|`NODE_EXTRA_CA_CERTS=file`|指定 CA (如 VeriSign) 的额外证书路径 - -## 负载 - -负载是衡量服务器运行状态的一个重要概念. 通过负载情况, 我们可以知道服务器目前状态是清闲, 良好, 繁忙还是即将 crash. - -通常我们要查看的负载是 CPU 负载, 详细一点的情况你可以通过阅读这篇博客: [Understanding Linux CPU Load](http://blog.scoutapp.com/articles/2009/07/31/understanding-load-averages) 来了解. - -命令行上可以通过 `uptime`, `top` 命令, Node.js 中可以通过 `os.loadavg()` 来获取当前系统的负载情况: - -``` -load average: 0.09, 0.05, 0.01 -``` - -其中分别是最近 1 分钟, 5 分钟, 15 分钟内系统 CPU 的平均负载. 当 CPU 的一个核工作饱和的时候负载为 1, 有几核 CPU 那么饱和负载就是几. - -在 Node.js 中单个进程的 CPU 负载查看可以使用 [pidusage](https://github.com/soyuka/pidusage) 模块. - -除了 CPU 负载, 对于服务端 (偏维护) 还需要了解网络负载, 磁盘负载等. - -## CheckList - -> 有一个醉汉半夜在路灯下徘徊,路过的人奇怪地问他:“你在路灯下找什么?”醉汉回答:“我在找我的KEY”,路人更奇怪了:“找钥匙为什么在路灯下?”,醉汉说:“因为这里最亮!”。 - -很多服务端的同学在说到检查服务器状态时只知道使用 `top` 命令, 其实情况就和上面的笑话一样, 因为对于他们而言 `top` 是最亮的那盏路灯. - -对于服务端程序员而言, 完整的服务器 checklist 首推 [《性能之巅》](https://www.amazon.cn/%E5%9B%BE%E4%B9%A6/dp/B0140I5WPK) 第二章中讲述的 [USE 方法](http://www.brendangregg.com/USEmethod/use-linux.html). - -The USE Method provides a strategy for performing a complete check of system health, identifying common bottlenecks and errors. For each system resource, metrics for utilization, saturation and errors are identified and checked. Any issues discovered are then investigated using further strategies. - -This is an example USE-based metric list for Linux operating systems (eg, Ubuntu, CentOS, Fedora). This is primarily intended for system administrators of the physical systems, who are using command line tools. Some of these metrics can be found in remote monitoring tools. - -### Physical Resources - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
componenttypemetric
CPUutilizationsystem-wide: vmstat 1, "us" + "sy" + "st"; sar -u, sum fields except "%idle" and "%iowait"; dstat -c, sum fields except "idl" and "wai"; per-cpu: mpstat -P ALL 1, sum fields except "%idle" and "%iowait"; sar -P ALL, same as mpstat; per-process: top, "%CPU"; htop, "CPU%"; ps -o pcpu; pidstat 1, "%CPU"; per-kernel-thread: top/htop ("K" to toggle), where VIRT == 0 (heuristic). [1]
CPUsaturationsystem-wide: vmstat 1, "r" > CPU count [2]; sar -q, "runq-sz" > CPU count; dstat -p, "run" > CPU count; per-process: /proc/PID/schedstat 2nd field (sched_info.run_delay); perf sched latency (shows "Average" and "Maximum" delay per-schedule); dynamic tracing, eg, SystemTap schedtimes.stp "queued(us)" [3]
CPUerrorsperf (LPE) if processor specific error events (CPC) are available; eg, AMD64's "04Ah Single-bit ECC Errors Recorded by Scrubber" [4]
Memory capacityutilizationsystem-wide: free -m, "Mem:" (main memory), "Swap:" (virtual memory); vmstat 1, "free" (main memory), "swap" (virtual memory); sar -r, "%memused"; dstat -m, "free"; slabtop -s c for kmem slab usage; per-process: top/htop, "RES" (resident main memory), "VIRT" (virtual memory), "Mem" for system-wide summary
Memory capacitysaturationsystem-wide: vmstat 1, "si"/"so" (swapping); sar -B, "pgscank" + "pgscand" (scanning); sar -W; per-process: 10th field (min_flt) from /proc/PID/stat for minor-fault rate, or dynamic tracing [5]; OOM killer: dmesg | grep killed
Memory capacityerrorsdmesg for physical failures; dynamic tracing, eg, SystemTap uprobes for failed malloc()s
Network Interfacesutilizationsar -n DEV 1, "rxKB/s"/max "txKB/s"/max; ip -s link, RX/TX tput / max bandwidth; /proc/net/dev, "bytes" RX/TX tput/max; nicstat "%Util" [6]
Network Interfacessaturationifconfig, "overruns", "dropped"; netstat -s, "segments retransmited"; sar -n EDEV, *drop and *fifo metrics; /proc/net/dev, RX/TX "drop"; nicstat "Sat" [6]; dynamic tracing for other TCP/IP stack queueing [7]
Network Interfaceserrorsifconfig, "errors", "dropped"; netstat -i, "RX-ERR"/"TX-ERR"; ip -s link, "errors"; sar -n EDEV, "rxerr/s" "txerr/s"; /proc/net/dev, "errs", "drop"; extra counters may be under /sys/class/net/...; dynamic tracing of driver function returns 76]
Storage device I/Outilizationsystem-wide: iostat -xz 1, "%util"; sar -d, "%util"; per-process: iotop; pidstat -d; /proc/PID/sched "se.statistics.iowait_sum"
Storage device I/Osaturationiostat -xnz 1, "avgqu-sz" > 1, or high "await"; sar -d same; LPE block probes for queue length/latency; dynamic/static tracing of I/O subsystem (incl. LPE block probes)
Storage device I/Oerrors/sys/devices/.../ioerr_cnt; smartctl; dynamic/static tracing of I/O subsystem response codes [8]
Storage capacityutilizationswap: swapon -s; free; /proc/meminfo "SwapFree"/"SwapTotal"; file systems: "df -h"
Storage capacitysaturationnot sure this one makes sense - once it's full, ENOSPC
Storage capacityerrorsstrace for ENOSPC; dynamic tracing for ENOSPC; /var/log/messages errs, depending on FS
Storage controllerutilizationiostat -xz 1, sum devices and compare to known IOPS/tput limits per-card
Storage controllersaturationsee storage device saturation, ...
Storage controllererrorssee storage device errors, ...
Network controllerutilizationinfer from ip -s link (or /proc/net/dev) and known controller max tput for its interfaces
Network controllersaturationsee network interface saturation, ...
Network controllererrorssee network interface errors, ...
CPU interconnectutilizationLPE (CPC) for CPU interconnect ports, tput / max
CPU interconnectsaturationLPE (CPC) for stall cycles
CPU interconnecterrorsLPE (CPC) for whatever is available
Memory interconnectutilizationLPE (CPC) for memory busses, tput / max; or CPI greater than, say, 5; CPC may also have local vs remote counters
Memory interconnectsaturationLPE (CPC) for stall cycles
Memory interconnecterrorsLPE (CPC) for whatever is available
I/O interconnectutilizationLPE (CPC) for tput / max if available; inference via known tput from iostat/ip/...
I/O interconnectsaturationLPE (CPC) for stall cycles
I/O interconnecterrorsLPE (CPC) for whatever is available
- - -### Software Resources - - - - - - - - - - - - - - - - - - - - -
componenttypemetric
Kernel mutexutilizationWith CONFIG_LOCK_STATS=y, /proc/lock_stat "holdtime-totat" / "acquisitions" (also see "holdtime-min", "holdtime-max") [8]; dynamic tracing of lock functions or instructions (maybe)
Kernel mutexsaturationWith CONFIG_LOCK_STATS=y, /proc/lock_stat "waittime-total" / "contentions" (also see "waittime-min", "waittime-max"); dynamic tracing of lock functions or instructions (maybe); spinning shows up with profiling (perf record -a -g -F 997 ..., oprofile, dynamic tracing)
Kernel mutexerrorsdynamic tracing (eg, recusive mutex enter); other errors can cause kernel lockup/panic, debug with kdump/crash
User mutexutilizationvalgrind --tool=drd --exclusive-threshold=... (held time); dynamic tracing of lock to unlock function time
User mutexsaturationvalgrind --tool=drd to infer contention from held time; dynamic tracing of synchronization functions for wait time; profiling (oprofile, PEL, ...) user stacks for spins
User mutexerrorsvalgrind --tool=drd various errors; dynamic tracing of pthread_mutex_lock() for EAGAIN, EINVAL, EPERM, EDEADLK, ENOMEM, EOWNERDEAD, ...
Task capacityutilizationtop/htop, "Tasks" (current); sysctl kernel.threads-max, /proc/sys/kernel/threads-max (max)
Task capacitysaturationthreads blocking on memory allocation; at this point the page scanner should be running (sar -B "pgscan*"), else examine using dynamic tracing
Task capacityerrors"can't fork()" errors; user-level threads: pthread_create() failures with EAGAIN, EINVAL, ...; kernel: dynamic tracing of kernel_thread() ENOMEM
File descriptorsutilizationsystem-wide: sar -v, "file-nr" vs /proc/sys/fs/file-max; dstat --fs, "files"; or just /proc/sys/fs/file-nr; per-process: ls /proc/PID/fd | wc -l vs ulimit -n
File descriptorssaturationdoes this make sense? I don't think there is any queueing or blocking, other than on memory allocation.
File descriptorserrorsstrace errno == EMFILE on syscalls returning fds (eg, open(), accept(), ...).
- -#### ulimit - -ulimit 用于管理用户对系统资源的访问. - -``` --a 显示目前全部限制情况 --c 设定 core 文件的最大值, 单位为区块 --d <数据节区大小> 程序数据节区的最大值, 单位为KB --f <文件大小> shell 所能建立的最大文件, 单位为区块 --H 设定资源的硬性限制, 也就是管理员所设下的限制 --m <内存大小> 指定可使用内存的上限, 单位为 KB --n <文件描述符数目> 指定同一时间最多可开启的 fd 数 --p <缓冲区大小> 指定管道缓冲区的大小, 单位512字节 --s <堆叠大小> 指定堆叠的上限, 单位为 KB --S 设定资源的弹性限制 --t 指定CPU使用时间的上限, 单位为秒 --u <进程数目> 用户最多可开启的进程数目 --v <虚拟内存大小> 指定可使用的虚拟内存上限, 单位为 KB -``` - -例如: - -``` -$ ulimit -a -core file size (blocks, -c) 0 -data seg size (kbytes, -d) unlimited -scheduling priority (-e) 0 -file size (blocks, -f) unlimited -pending signals (-i) 127988 -max locked memory (kbytes, -l) 64 -max memory size (kbytes, -m) unlimited -open files (-n) 655360 -pipe size (512 bytes, -p) 8 -POSIX message queues (bytes, -q) 819200 -real-time priority (-r) 0 -stack size (kbytes, -s) 8192 -cpu time (seconds, -t) unlimited -max user processes (-u) 4096 -virtual memory (kbytes, -v) unlimited -file locks (-x) unlimited -``` - -注意, open socket 等资源拿到的也是 fd, 所以 `ulimit -n` 比较小除了文件打不开, 还可能建立不了 socket 链接. - +* `[Basic]` Indicators diff --git a/sections/en-us/process.md b/sections/en-us/process.md index 062f446..7741a7f 100644 --- a/sections/en-us/process.md +++ b/sections/en-us/process.md @@ -1,248 +1,7 @@ -# 进程 - -* [`[Doc]` Process (进程)](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#process) -* [`[Doc]` Child Processes (子进程)](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#child-process) -* [`[Doc]` Cluster (集群)](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#cluster) -* [`[Basic]` 进程间通信](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#进程间通信) -* [`[Basic]` 守护进程](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#守护进程) - -## 简述 - -关于 Process, 我们需要讨论的是两个概念, ①操作系统的进程, ② Node.js 中的 Process 对象. 操作进程对于服务端而言, 好比 html 之于前端一样基础. 想做服务端编程是不可能绕过 Unix/Linux 的. 在 Linux/Unix/Mac 系统中运行 `ps -ef` 命令可以看到当前系统中运行的进程. 各个参数如下: - -|列名称|意义| -|-----|---| -|UID|执行该进程的用户ID| -|PID|进程编号| -|PPID|该进程的父进程编号| -|C|该进程所在的CPU利用率| -|STIME|进程执行时间| -|TTY|进程相关的终端类型| -|TIME|进程所占用的CPU时间| -|CMD|创建该进程的指令| - -关于进程以及操作系统一些更深入的细节推荐阅读 APUE, 即《Unix 高级编程》等书籍来了解. - -## Process - -这里来讨论 Node.js 中的 `process` 对象. 直接在代码中通过 `console.log(process)` 即可打印出来. 可以看到 process 对象暴露了非常多有用的属性以及方法, 具体的细节见[官方文档](https://nodejs.org/dist/latest-v6.x/docs/api/process.html), 已经说的挺详细了. 其中包括但不限于: - -* 进程基础信息 -* 进程 Usage -* 进程级事件 -* 依赖模块/版本信息 -* OS 基础信息 -* 账户信息 -* 信号收发 -* 三个标准流 - -### process.nextTick - -上一节已经提到过 `process.nextTick` 了, 这是一个你需要了解的, 重要的, 基础方法. - - -``` - ┌───────────────────────┐ -┌─>│ timers │ -│ └──────────┬────────────┘ -│ ┌──────────┴────────────┐ -│ │ I/O callbacks │ -│ └──────────┬────────────┘ -│ ┌──────────┴────────────┐ -│ │ idle, prepare │ -│ └──────────┬────────────┘ ┌───────────────┐ -│ ┌──────────┴────────────┐ │ incoming: │ -│ │ poll │<─────┤ connections, │ -│ └──────────┬────────────┘ │ data, etc. │ -│ ┌──────────┴────────────┐ └───────────────┘ -│ │ check │ -│ └──────────┬────────────┘ -│ ┌──────────┴────────────┐ -└──┤ close callbacks │ - └───────────────────────┘ -``` - -`process.nextTick` 并不属于 Event loop 中的某一个阶段, 而是在 Event loop 的每一个阶段结束后, 直接执行 `nextTickQueue` 中插入的 "Tick", 并且直到整个 Queue 处理完. 所以面试时又有可以问的问题了, 递归调用 process.nextTick 会怎么样? (doge - -```javascript -function test() { - process.nextTick(() => test()); -} -``` - -这种情况与以下情况, 有什么区别? 为什么? - -```javascript -function test() { - setTimeout(() => test(), 0); -} -``` - -### 配置 - -配置是开发部署中一个很常见的问题. 普通的配置有两种方式, 一是定义配置文件, 二是使用环境变量. - -![node-configuration](https://blog-assets.risingstack.com/2016/Sep/node-js-survey/node-js-survey-envvar-config-new.png) - -你可以通过[设置环境变量](http://cn.bing.com/search?q=linux+%E8%AE%BE%E7%BD%AE%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F)来指定配置, 然后通过 `process.env` 来获取配置项. 另外也可以通过读取定义好的配置文件来获取, 在这方面有很多不错的库例如 `dotenv`, `node-config` 等, 而在使用这些库来加载配置文件的时候, 通常都会碰到一个当前工作目录的问题. - -> 进程的当前工作目录是什么? 有什么作用? - -当前进程启动的目录, 通过 process.cwd() 获取当前工作目录 (current working directory), 通常是命令行启动的时候所在的目录 (也可以在启动时指定), 文件操作等使用相对路径的时候会相对当前工作目录来获取文件. - -一些获取配置的第三方模块就是通过你的当前目录来找配置文件的. 所以如果你错误的目录启动脚本, 可能没法得到正确的结果. 在程序中可以通过 `process.chdir()` 来改变当前的工作目录. - -### 标准流 - -在 process 对象上还暴露了 `process.stderr`, `process.stdout` 以及 `process.stdin` 三个标准流, 熟悉 C/C++/Java 的同学应该对此比较熟悉. 关于这几个流, 常见的面试问题是问 **console.log 是同步还是异步? 如何实现一个 console.log?** - -如果简历中有出现 C/C++ 关键字, 一般都会问到如何实现一个同步的输入 (类似实现C语言的 `scanf`, C++ 的 `cin`, Python 的 `raw_input` 等). - -### 维护方面 - -熟悉与进程有关的基础命令, 如 top, ps, pstree 等命令. - -## Child Process - -子进程 (Child Process) 是进程中一个重要的概念. 你可以通过 Node.js 的 `child_process` 模块来执行可执行文件, 调用命令行命令, 比如其他语言的程序等. 也可以通过该模块来将 .js 代码以子进程的方式启动. 比较有名的网易的分布式架构 [pomelo](https://github.com/NetEase/pomelo) 就是基于该模块 (而不是 `cluster`) 来实现多进程分布式架构的. - -> child_process.fork 与 POSIX 的 fork 有什么区别? - -Node.js 的 `child_process.fork()` 不像 POSIX [fork(2)](http://man7.org/linux/man-pages/man2/fork.2.html) 系统调用, 不会拷贝当前父进程. 这里对于其他语言转过的同学可能比较误导, 可以作为一个比较偏的面试题. - -* spawn() 启动一个子进程来执行命令 - * options.detached 父进程死后是否允许子进程存活 - * options.stdio 指定子进程的三个标准流 -* spawnSync() 同步版的 spawn, 可指定超时, 返回的对象可获得子进程的情况 -* exec() 启动一个子进程来执行命令, 带回调参数获知子进程的情况, 可指定进程运行的超时时间 -* execSync() 同步版的 exec(), 可指定超时, 返回子进程的输出 (stdout) -* execFile() 启动一个子进程来执行一个可执行文件, 可指定进程运行的超时时间 -* execFileSync() 同步版的 execFile(), 返回子进程的输出, 如何超时或者 exit code 不为 0, 会直接 throw Error -* fork() 加强版的 spawn(), 返回值是 ChildProcess 对象可以与子进程交互 - -其中 exec/execSync 方法会直接调用 bash 来解释命令, 所以如果有命令有外部参数, 则需要注意被注入的情况. - -### child.kill 与 child.send - -常见会问的面试题, 如 `child.kill` 与 `child.send` 的区别. 二者一个是基于信号系统, 一个是基于 IPC. - -> 父进程或子进程的死亡是否会影响对方? 什么是孤儿进程? - -子进程死亡不会影响父进程, 不过子进程死亡时(线程组的最后一个线程,通常是“领头”线程死亡时),会向它的父进程发送死亡信号. 反之父进程死亡, 一般情况下子进程也会随之死亡, 但如果此时子进程处于可运行态、僵死状态等等的话, 子进程将被`进程1`(init 进程)收养,从而成为孤儿进程. 另外, 子进程死亡的时候(处于“终止状态”),父进程没有及时调用 `wait()` 或 `waitpid()` 来返回死亡进程的相关信息,此时子进程还有一个 `PCB` 残留在进程表中,被称作僵尸进程. - -## Cluster - -Cluster 是常见的 Node.js 利用多核的办法. 它是基于 `child_process.fork()` 实现的, 所以 cluster 产生的进程之间是通过 IPC 来通信的, 并且它也没有拷贝父进程的空间, 而是通过加入 cluster.isMaster 这个标识, 来区分父进程以及子进程, 达到类似 POSIX 的 [fork](http://man7.org/linux/man-pages/man2/fork.2.html) 的效果. - -```javascript -const cluster = require('cluster'); // | | -const http = require('http'); // | | -const numCPUs = require('os').cpus().length; // | | 都执行了 - // | | -if (cluster.isMaster) { // |-|----------------- - // Fork workers. // | - for (var i = 0; i < numCPUs; i++) { // | - cluster.fork(); // | - } // | 仅父进程执行 (a.js) - cluster.on('exit', (worker) => { // | - console.log(`${worker.process.pid} died`); // | - }); // | -} else { // |------------------- - // Workers can share any TCP connection // | - // In this case it is an HTTP server // | - http.createServer((req, res) => { // | - res.writeHead(200); // | 仅子进程执行 (b.js) - res.end('hello world\n'); // | - }).listen(8000); // | -} // |------------------- - // | | -console.log('hello'); // | | 都执行了 -``` - -在上述代码中 numCPUs 虽然是全局变量但是, 在父进程中修改它, 子进程中并不会改变, 因为父进程与子进程是完全独立的两个空间. 他们所谓的共有仅仅只是都执行了, 并不是同一份. - -你可以把父进程执行的部分当做 `a.js`, 子进程执行的部分当做 `b.js`, 你可以把他们想象成是先执行了 `node a.js` 然后 cluster.fork 了几次, 就执行执行了几次 `node b.js`. 而 cluster 模块则是二者之间的一个桥梁, 你可以通过 cluster 提供的方法, 让其二者之间进行沟通交流. - -### How It Works - -worker 进程是由 child_process.fork() 方法创建的, 所以可以通过 IPC 在主进程和子进程之间相互传递服务器句柄. - -cluster 模块提供了两种分发连接的方式. - -第一种方式 (默认方式, 不适用于 windows), 通过时间片轮转法(round-robin)分发连接. 主进程监听端口, 接收到新连接之后, 通过时间片轮转法来决定将接收到的客户端的 socket 句柄传递给指定的 worker 处理. 至于每个连接由哪个 worker 来处理, 完全由内置的循环算法决定. - -第二种方式是由主进程创建 socket 监听端口后, 将 socket 句柄直接分发给相应的 worker, 然后当连接进来时, 就直接由相应的 worker 来接收连接并处理. - -使用第二种方式时, 多个 worker 之间会存在竞争关系, 产生一个老生常谈的 "[惊群效应](https://www.google.com.hk/search?q=%E6%83%8A%E7%BE%A4%E6%95%88%E5%BA%94)" 从而导致效率变低的问题. 该问题常见于 Apache. 并且各自竞争的情况下无法控制一个新的连接由哪个进程来处理, 从而导致各 worker 进程之间的负载不均衡, 比如通常 70% 的连接仅被 8 个进程中的 2 个处理, 而其他进程比较清闲. - -## 进程间通信 - -IPC (Inter-process communication) 进程间通信技术. 常见的进程间通信技术列表如下: - -类型|无连接|可靠|流控制|优先级 ----|-----|----|-----|----- -普通PIPE|N|Y|Y|N -命名PIPE|N|Y|Y|N -消息队列|N|Y|Y|N -信号量|N|Y|Y|Y -共享存储|N|Y|Y|Y -UNIX流SOCKET|N|Y|Y|N -UNIX数据包SOCKET|Y|Y|N|N - -Node.js 中的 IPC 通信是由 libuv 通过管道技术实现的, 在 windows 下由命名管道(named pipe)实现也就是上表中的最后第二个, *nix 系统则采用 UDS (Unix Domain Socket) 实现. - -普通的 socket 是为网络通讯设计的, 而网络本身是不可靠的, 而为 IPC 设计的 socket 则不然, 因为默认本地的网络环境是可靠的, 所以可以简化大量不必要的 encode/decode 以及计算校验等, 得到效率更高的 UDS 通信. - -如果了解 Node.js 的 IPC 的话, 可以问个比较有意思的问题 - -> 在 IPC 通道建立之前, 父进程与子进程是怎么通信的? 如果没有通信, 那 IPC 是怎么建立的? - -这个问题也挺简单, 只是个思路的问题. 在通过 child_process 建立子进程的时候, 是可以指定子进程的 env (环境变量) 的. 所以 Node.js 在启动子进程的时候, 主进程先建立 IPC 频道, 然后将 IPC 频道的 fd (文件描述符) 通过环境变量 (`NODE_CHANNEL_FD`) 的方式传递给子进程, 然后子进程通过 fd 连上 IPC 与父进程建立连接. - -最后于进程间通信 (IPC) 的问题, 一般不会直接问 IPC 的实现, 而是会问什么情况下需要 IPC, 以及使用 IPC 处理过什么业务场景等. - - -## 守护进程 - -最后的守护进程, 是服务端方面一个很基础的概念了. 很多人可能只知道通过 pm2 之类的工具可以将进程以守护进程的方式启动, 却不了解什么是守护进程, 为什么要用守护进程. 对于水平好的同学, 我们是希望能了解守护进程的实现的. - -普通的进程, 在用户退出终端之后就会直接关闭. 通过 `&` 启动到后台的进程, 之后会由于会话(session组)被回收而终止进程. 守护进程是不依赖终端(tty)的进程, 不会因为用户退出终端而停止运行的进程. - -```c -// 守护进程实现 (C语言版本) -void init_daemon() -{ - pid_t pid; - int i = 0; - - if ((pid = fork()) == -1) { - printf("Fork error !\n"); - exit(1); - } - - if (pid != 0) { - exit(0); // 父进程退出 - } - - setsid(); // 子进程开启新会话, 并成为会话首进程和组长进程 - if ((pid = fork()) == -1) { - printf("Fork error !\n"); - exit(-1); - } - if (pid != 0) { - exit(0); // 结束第一子进程, 第二子进程不再是会话首进程 - // 避免当前会话组重新与tty连接 - } - chdir("/tmp"); // 改变工作目录 - umask(0); // 重设文件掩码 - for (; i < getdtablesize(); ++i) { - close(i); // 关闭打开的文件描述符 - } - - return; -} -``` - -[Node.js 编写守护进程](https://cnodejs.org/topic/57adfadf476898b472247eac) - +# Process +* `[Doc]` Process +* `[Doc]` Child Processes +* `[Doc]` Cluster +* `[Basic]` IPC +* `[Basic]` Daemon diff --git a/sections/en-us/security.md b/sections/en-us/security.md index fac89e3..cdebbff 100644 --- a/sections/en-us/security.md +++ b/sections/en-us/security.md @@ -1,208 +1,9 @@ -# 安全 +# Security -* `[Doc]` Crypto (加密) +* `[Doc]` Crypto * `[Doc]` TLS/SSL * `[Doc]` HTTPS * `[Point]` XSS * `[Point]` CSRF -* `[Point]` 中间人攻击 -* `[Point]` Sql/Nosql 注入攻击 - - -## Crypto - -Node.js 的 `crypto` 模块封装了诸多的加密功能, 包括 OpenSSL 的哈希、HMAC、加密、解密、签名和验证函数等. - -Node.js 的加密貌似有点问题, 某些算法算出来跟别的语言 (比如 Python) 不一样. 具体情况还在整理中 (时间不定), 欢迎补充. - -> 加密是如何保证用户密码的安全性? - -在客户端加密, 是增加传输的过程中被第三方嗅探到密码后破解的成本. 对于游戏, 在客户端加密是防止外挂/破解等. 在服务端加密 (如 md5) 是避免管理数据库的 DBA 或者攻击者攻击数据库之后直接拿到明文密码, 从而提高安全性. - - -## TLS/SSL - -早期的网络传输协议由于只在大学内使用, 所以是默认互相信任的. 所以传统的网络通信可以说是没有考虑网络安全的. 早年的浏览器大厂网景公司为了应对这个情况设计了 SSL (Secure Socket Layer), SSL 的主要用途是: - -1. 认证用户和服务器, 确保数据发送到正确的客户机和服务器; -2. 加密数据以防止数据中途被窃取; -3. 维护数据的完整性, 确保数据在传输过程中不被改变. - -存在三个特性: - -* 机密性:SSL协议使用密钥加密通信数据 -* 可靠性:服务器和客户都会被认证, 客户的认证是可选的 -* 完整性:SSL协议会对传送的数据进行完整性检查 - -1999年, SSL 因为应用广泛, 已经成为互联网上的事实标准. IETF 就在那年把 SSL 标准化/强化. 标准化之后的名称改为传输层安全协议 (Transport Layer Security, TLS). 很多相关的文章都把这两者并列称呼 (TLS/SSL), 因为这两者可以视作同一个东西的不同阶段. - - -## HTTPS - -在网络上, 每个网站都在各自的服务器上, 想要确保你访问的是一个正确的网站, 并且访问到这个网站正确的数据 (没有被劫持/篡改), 除了需要传输安全之外, 还需要安全的认证, 认证不能由目标网站进行, 否则恶意/钓鱼网站也可以自己说自己是对的, 所以为了能在网络上维护网络之间的基本信任, 早期的大厂们合力推动了一项名为 PKI 的基础设施, 通过第三方来认证网站. - -公钥基础设施 (Public Key Infrastructure, PKI) 是一种遵循标准的, 利用公钥加密技术为电子商务的开展提供一套安全基础平台的技术和规范. 其基础建置包含认证中心 (Certification Authority, CA) 、注册中心 (Register Authority, RA) 、目录服务 (Directory Service, DS) 服务器. - -由 RA 统筹、审核用户的证书申请, 将证书申请送至 CA 处理后发出证书, 并将证书公告至 DS 中. 在使用证书的过程中, 除了对证书的信任关系与证书本身的正确性做检查外, 并透过产生和发布证书废止列表 (Certificate Revocation List, CRL) 对证书的状态做确认检查, 了解证书是否因某种原因而遭废弃. 证书就像是个人的身分证, 其内容包括证书序号、用户名称、公开金钥 (Public Key) 、证书有效期限等. - -在 TLS/SLL 中你可以使用 OpenSSL 来生成 TLS/SSL 传输时用来认证的 public/private key. 不过这个 public/private key 是自己生成的, 而通过 PKI 基础设施可以获得权威的第三方证书 (key) 从而加密 HTTP 传输安全. 目前博客圈子里比较流行的是 [Let's Encrypt 签发免费的 HTTPS 证书](https://imququ.com/post/letsencrypt-certificate.html). - -需要注意的是, 如果 PKI 受到攻击, 那么 HTTPS 也一样不安全. 可以参见 [HTTPS 劫持 - 知乎讨论](https://www.zhihu.com/question/22795329) 中的情况, 证书由 CA 机构签发, 一般浏览器遇到非权威的 CA 机构是会告警的 (参见 [12306](https://kyfw.12306.cn/otn/)), 但是如果你在某些特殊的情况下信任了某个未知机构/证书, 那么也可能被劫持. - -此外有的 CA 机构以邮件方式认证, 那么当某个网站的邮件服务收到攻击/渗透, 那么攻击者也可能以此从 CA 机构获取权威的正确的证书. - - -## XSS - -跨站脚本 (Cross-Site Scripting, XSS) 是一种代码注入方式, 为了与 CSS 区分所以被称作 XSS. 早期常见于网络论坛, 起因是网站没有对用户的输入进行严格的限制, 使得攻击者可以将脚本上传到帖子让其他人浏览到有恶意脚本的页面, 其注入方式很简单包括但不限于 JavaScript / VBScript / CSS / Flash 等. - -当其他用户浏览到这些网页时, 就会执行这些恶意脚本, 对用户进行 Cookie 窃取/会话劫持/钓鱼欺骗等各种攻击. 其原理, 如使用 js 脚本收集当前用户环境的信息 (Cookie 等), 然后通过 img.src, Ajax, onclick/onload/onerror 事件等方式将用户数据传递到攻击者的服务器上. 钓鱼欺骗则常见于使用脚本进行视觉欺骗, 构建假的恶意的 Button 覆盖/替换真实的场景等情况 (该情况在用户上传 CSS 的时候也可能出现, 如早起淘宝网店装修, 使用 CSS 拼接假的评分数据等覆盖在真的评分数据上误导用户). - -> 过滤 Html 标签能否防止 XSS? 请列举不能的情况? - -用户除了上传 - -```html - -``` - -还可以使用图片 url 等方式来上传脚本进行攻击 - -```html -
- -``` - -还可以使用各种方式来回避检查, 例如空格, 回车, Tab - -```html - -``` - -还可以通过各种编码转换 (URL 编码, Unicode 编码, HTML 编码, ESCAPE 等) 来绕过检查 - -``` - - -``` - -### CPS 策略 - -在百般无奈, 没有统一解决方案的情况下, 厂商们推出了 CPS 策略. - -以 Node.js 为例, 计算脚本的 hashes 值: -``` -const crypto = require('crypto'); - -function getHashByCode(code, algorithm = 'sha256') { - return algorithm + '-' + crypto.createHash(algorithm).update(code, 'utf8').digest("base64"); -} - -getHashByCode('console.log("hello world");'); // 'sha256-wxWy1+9LmiuOeDwtQyZNmWpT0jqCUikqaqVlJdtdh/0=' -``` - -设置 CSP 头: - -``` -content-security-policy: script-src 'sha256-wxWy1+9LmiuOeDwtQyZNmWpT0jqCUikqaqVlJdtdh/0=' -``` - -```html - - -``` - -策略指令可以参见 [CSP Policy Directives](https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives)以及[阮一峰的博文](http://www.ruanyifeng.com/blog/2016/09/csp.html), [屈大神的博文](https://imququ.com/post/content-security-policy-reference.html) - - -## CSRF - -跨站请求伪造 (Cross-Site Request Forgery, CSRF, https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet) 是一种伪造跨站请求的攻击方式. 例如利用你在 A 站 (攻击目标) 的 cookie / 权限等, 在 B 站 (恶意/钓鱼网站) 拼装 A 站的请求. - -比如 Q 君是某论坛管理员. 已知这个论坛 A 删除的接口是 post 到某个地址, 并指定一个帖子的 id. 那么我可以在自己的博客 B 上组织一个 CSRF 请求. 然后诱使 Q 君来访问我的博客. 就可以在 Q 君不知情的情况下删除掉我想删的某个帖子. - -钓鱼方式包括但不限于公开网站 (xss), 攻击者的恶意网站, email 邮件, 微博, 微信, 短信等及时消息. - -同源策略是最早用于防止 CSRF 的一种方式, 即关于跨站请求 (Cross-Site Request) 只有在同源/信任的情况下才可以请求. 但是如果一个网站群, 在互相信任的情况下, 某个网站出现了问题: - -``` -a.public.com -b.public.com -c.public.com -... -``` - -以上情况下, 如果 c.public.com 上没有预防 xss 等情况, 使得攻击者可以基于此站对其他信任的网站发起 CSRF 攻击. - -另外同源策略主要是浏览器来进行验证的, 并且不同浏览器的实现又各自不同, 所以在某些浏览器上可以直接绕过, 而且也可以直接通过短信等方式直接绕过浏览器. - -预防: - -1. A 站 (预防站) 检查 http 请求的 header 确认其 origin -2. 检查 CSRF token - -### 1.同源检查 - -通过检查来过滤简单的 CSRF 攻击, 主要检查一下两个 header: - -* Origin Header -* Referer Header - -### 2.CSRF token - -简单来说, 对需要预防的请求, 通过特别的算法生成 token 存在 session 中, 然后将 token 隐藏在正确的界面表单中, 正式请求时带上该 token 在服务端验证, 避免跨站请求. - - -## 中间人攻击 - -中间人 (Man-in-the-middle attack, MITM) 是指攻击者与通讯的两端分别创建独立的联系, 并交换其所收到的数据, 使通讯的两端认为他们正在通过一个私密的连接与对方直接对话, 但事实上整个会话都被攻击者完全控制. 在中间人攻击中, 攻击者可以拦截通讯双方的通话并插入新的内容. - -目前比较常见的是在公共场所放置精心准备的免费 wifi, 劫持/监控通过该 wifi 的流量. 或者攻击路由器, 连上你家 wifi 攻破你家 wifi 之后在上面劫持流量等. - -对于通信过程中的 MITM, 常见的方案是通过 PKI / TLS 预防, 及时是通过存在第三方中间人的 wifi 你通过 HTTPS 访问的页面依旧是安全的. 而 HTTP 协议是明文传输, 则没有任何防护可言. - -不常见的还有强力的互相认证, 你确认他之后, 他也确认你一下; 延迟测试, 统计传输时间, 如果通讯延迟过高则认为可能存在第三方中间人; 等等. - -## SQL/NoSQL 注入 - -注入攻击是指当所执行的一些操作中有部分由用户传入时, 用户可以将其恶意逻辑注入到操作中. 当你使用 eval, new Function 等方式执行的字符串中有用户输入的部分时, 就可能被注入攻击. 上文中的 XSS 就属于一种注入攻击. 前面的章节中也提到过 Node.js 的 child_process.exec 由于调用 bash 解析, 如果执行的命令中有部分属于用户输入, 也可能被注入攻击. - -### SQL - -Sql 注入是网站常见的一种注入攻击方式. 其原因主要是由于登录时需要验证用户名/密码, 其执行 sql 类似: - -```sql -SELECT * FROM users WHERE usernae = 'myName' AND password = 'mySecret'; -``` - -其中的用户名和密码属于用户输入的部分, 那么在未做检查的情况下, 用户可能拼接恶意的字符串来达到其某种目的, 例如上传密码为 `'; DROP TABLE users; --` 使得最终执行的内容为: - -```sql -SELECT * FROM users WHERE usernae = 'myName' AND password = ''; DROP TABLE users; --'; -``` - -其能实现的功能, 包括但不限于删除数据 (经济损失), 篡改数据 (密码等), 窃取数据 (网站管理权限, 用户数据) 等. 防治手段常见于: - -* 给表名/字段名加前缀 (避免被猜到) -* 报错隐藏表信息 (避免被看到, 12306 早起就出现过的问题) -* 过滤可以拼接 SQL 的关键字符 -* 对用户输入进行转义 -* 验证用户输入的类型 (避免 limit, order by 等注入) -* 等... - -### NoSQL - -看个简单的情况: - -```javascript -let {user, pass, age} = ctx.query; - -db.collection.find({ - user, pass, - $where: `this.age >= ${age}` -}) -``` - -那么这里的 age 就可以注入了. 另外 GET/POST 还可以传递深层结构 (比如 ?name[0]=alan 传递上来), 通过 qs 之类的模块解析后导致注入, 如 [cnodejs 遭遇 mongodb 注入](https://github.com/cnodejs/nodeclub/commit/0f6cc14f6bcbbe6b4de3199c6896efaec637693e). - +* `[Point]` MITM +* `[Point]` Sql/Nosql Injection diff --git a/sections/en-us/storage.md b/sections/en-us/storage.md index 5e6a6e0..9db4166 100644 --- a/sections/en-us/storage.md +++ b/sections/en-us/storage.md @@ -1,168 +1,6 @@ -# 存储 +# Storage * `[Point]` Sql * `[Point]` NoSql -* `[Point]` 缓存 -* `[Point]` 数据一致性 - -## 简介 - -科班的同学可以了解一下[数据库范式](http://www.cnblogs.com/CareySon/archive/2010/02/16/1668803.html), 在 ElemeFe 面试不会问, 但是其他地方可能会问 (比如阿里). - - -## Mysql - -SQL (Structured Query Language) 是[关系式数据库管理系统](https://en.wikipedia.org/wiki/Relational_database)的标准语言, 关于关系型数据库这里主要带大家看一下 Mysql 的几个问题 - -### 存储引擎 - -|attr|MyISAM|InnoDB| -|----|----|----| -|Locking|Table-level|Row-level| -|designed for|need of speed|high volume of data| -|foreign keys | × (DBMS) | ✓ (RDBMS)| -|transaction | × | ✓ | -|fulltext search | ✓ | × | -|scene| lots of select | lots of insert/update | -|count rows| fast | slow | -|auto_increment | fast | slow | - -* 你的数据库有外键吗? -* 你需要事务支持吗? -* 你需要全文索引吗? -* 你经常使用什么样的查询模式? -* 你的数据有多大? - -参见 [MYSQL: INNODB 还是 MYISAM?](http://coolshell.cn/articles/652.html) - -### 索引 - -索引是用空间换时间的一种优化策略. 推荐阅读: [mysql索引类型](http://www.cnblogs.com/cq-home/p/3482101.html) 以及 [主键与唯一索引的区别](http://blog.mimvp.com/2015/03/the-difference-between-primary-key-and-unique-index/) - - -## Mongodb - -> Monogdb 连接问题(超时/断开等)有可能是什么问题导致的? - -* 网络问题 -* 任务跑不完, 超过了 driver 的默认链接超时时间 (如 30s) -* Monogdb 宕机了 -* 超过了连接空闲时间 (connection idle time) 被断开 -* fd 不够用 (ulimit 设置) -* mongodb 最大连接数不够用 (可能是连接未复用导致) -* etc... - -### other - -populate - -aggregate - -pipeline - -Cursor - -整理中 - -## Replication - -> 备份数据库与 M/S, M/M 等部署方式的区别? - -关于数据库基于各种模式的特点全部可以通过以下图片分清: - -![storage](/assets/storage.jpeg) - -图片出处:Google App Engine 的 co-founder Ryan Barrett 在 2009 年的 google i/o 上的演讲 [《Transaction Across DataCenter》](http://snarfed.org/transactions_across_datacenters_io.html)(视频: http://www.youtube.com/watch?v=srOgpXECblk) - -根据上图, 我们可以知道 Master/Slave 与 Master/Master 的关系. - - - - - - - - - -
attrMaster/SlaveMaster/Master
一致性Eventually:当你写入一个新值后,有可能读不出来,但在某个时间窗口之后保证最终能读出来。比如:DNS,电子邮件、Amazon S3,Google搜索引擎这样的系统。
事务完整本地
延迟低延迟
吞吐高吞吐
数据丢失部分丢失
熔断只读读/写
- -### 读写分离 - -读写分离是在 query 量大的情况下减轻单个 DB 节点压力, 优化数据库读/写速度的一种策略. 不论是 MySQL 还是 MongoDB 都可以进行读写分离. - -读写分离的配置方式直接搜索一下 `数据库名 + 读写分离` 即可找到. 通常是 M/S 的情况, 使用 Master 专门写, 用 Slave 节点专门读. 使用读写分离时, 请确认读的请求对一致性要求不高, 因为从写库同步读库是有延迟的. - - -## 数据一致性 - -关于数据一致性推荐看陈皓的[分布式系统的事务处理](http://www.infoq.com/cn/articles/distributed-system-transaction-processing) - -> 什么情况下数据会出现脏数据? 如何避免? - -* 从 A 帐号中把余额读出来 -* 对 A 帐号做减法操作 -* 把结果写回 A 帐号中 -* 从 B 帐号中把余额读出来 -* 对 B 帐号做加法操作 -* 把结果写回 B 帐号中 - -为了数据的一致性, 这6件事, 要么都成功做完, 要么都不成功, 而且这个操作的过程中, 对A、B帐号的其它访问必需锁死, 所谓锁死就是要排除其它的读写操作, 否则就会出现脏数据 ---- 即数据一致性的问题. - -这个问题并不仅仅出现在数据库操作中, 普通的并发以及并行操作都可能导致出现脏数据. 避免出现脏数据通常是从架构上避免或者采用事务的思想处理. - -### 矛盾 - -* 1)要想让数据有高可用性,就得写多份数据 -* 2)写多份的问题会导致数据一致性的问题 -* 3)数据一致性的问题又会引发性能问题 - -强一致性必然导致性能短板, 而弱一致性则有很好的性能但是存在数据安全(灾备数据丢失)/一致性(脏读/脏写等)的问题. - -目前 Node.js 业内流行的主要是与 Mongodb 配合, 在数据一致性方面属于短板. - -### 事务 - -事务并不仅仅是 sql 数据库中的一个功能, 也是分布式系统开发中的一个思想, 事务在分布式的问题中可以称为 "两阶段提交" (以下引用陈皓原文) - -第一阶段: - -* 协调者会问所有的参与者结点,是否可以执行提交操作。 -* 各个参与者开始事务执行的准备工作:如:为资源上锁,预留资源,写undo/redo log…… -* 参与者响应协调者,如果事务的准备工作成功,则回应“可以提交”,否则回应“拒绝提交”。 - -第二阶段: - -* 如果所有的参与者都回应“可以提交”,那么,协调者向所有的参与者发送“正式提交”的命令。参与者完成正式提交,并释放所有资源,然后回应“完成”,协调者收集各结点的“完成”回应后结束这个Global Transaction。 -* 如果有一个参与者回应“拒绝提交”,那么,协调者向所有的参与者发送“回滚操作”,并释放所有资源,然后回应“回滚完成”,协调者收集各结点的“回滚”回应后,取消这个Global Transaction。 - -异常: - -* 如果第一阶段中,参与者没有收到询问请求,或是参与者的回应没有到达协调者。那么,需要协调者做超时处理,一旦超时,可以当作失败,也可以重试。 -* 如果第二阶段中,正式提交发出后,如果有的参与者没有收到,或是参与者提交/回滚后的确认信息没有返回,一旦参与者的回应超时,要么重试,要么把那个参与者标记为问题结点剔除整个集群,这样可以保证服务结点都是数据一致性的。 -* 第二阶段中,如果参与者收不到协调者的commit/fallback指令,参与者将处于“状态未知”阶段,参与者完全不知道要怎么办。 - - -## 缓存 - -> redis 与 memcached 的区别? - -|attr|memcached|redis| -|----|----|----| -|struct|key/value|key/value + list, set, hash etc. | -|backup | × | ✓ | -|Persistence | × | ✓ | -|transcations | × | ✓ | -|consistency | strong (by cas) | weak | -|thread | multi | single | -|memory | physical | physical & swap | - - -## 其他 - -* zookeeper -* kafka -* storm -* hadoop -* spark - - +* `[Point]` Cache +* `[Point]` Consistency diff --git a/sections/en-us/test.md b/sections/en-us/test.md index 7a6f343..26e786f 100644 --- a/sections/en-us/test.md +++ b/sections/en-us/test.md @@ -1,256 +1,8 @@ -# 测试 - -* [`[Basic]` 测试方法](#测试方法) -* [`[Basic]` 单元测试](#单元测试) -* [`[Basic]` 基准测试](#集成测试) -* [`[Basic]` 集成测试](#基准测试) -* [`[Basic]` 压力测试](#压力测试) -* [`[Doc]` Assert (断言)](#assert) - -## 简述 - -> 为什么要写测试? 写测试是否会拖累开发进度? - -项目在多人合作的时候, 为了某个功能修改了某个模块的某部分代码, 实际的情况中修改一个地方可能会影响到别人开发的多个功能, 在自己不知情的情况下想要保证自己修改的代码不影响到其他功能, 最简单的办法是通过测试来保证. - -``` -A - \ - E - / \ -B H - \ / - F - / -C - \ - G - / -D -``` - -如上述情况, ABCD 是逻辑层, EFGH 等是更低一次层 (比如工具层等), 当你为了功能 A 的 BUG 修改了 H 的代码, 那么实际受影响的功能除了 A 之外还有 BC, 如果你有针对每一个逻辑的测试, 那么修改了 H 的代码之后, 跑一遍测试即可保证对 H 的修改不会影响到 BC (如果有影响, 那么相应的测试会报错). 利用这种特性, 你还可以基于测试去做重构, 在通过原有测试的情况下, 即表明新的重构版本可以替代原有的版本. - -而这样的效果, 只有当覆盖率达到了一定程度 (通常是 80% 以上, 90% 以上为最理想) 才能实现, 如果测试的覆盖率低, 无法覆盖到多种情况, 那么测试对你的项目可能是没有用甚至起到反作用的 (让你误以为你的修改没问题而发布等). - -写测试是否会拖累开发进度要视具体情况而定. 需要考虑到, 开发进度包含功能和品质两个方面, 单纯写代码的速度不能完全代表开发进度. 测试在适当的情况下可以保证项目的品质从而得到更好的开发进度. - -如上述的例子, 在修改功能 A 的 BUG 的时候, 如果你不知道 H 会影响到 BC 又没有测试的话, 那么开发 BC 的同学可能会出现十分经典的 **"昨天还好好的, 今天怎么就不能用了?"** 的情况. - -当然写测试拖累开发进度的情况也是客观存在的, 通常是有以下几种情况: - -* 不会写测试 -* 过度测试, 不必要的测试 -* 为了迎合测试, 而忽略了实际需求 - - -> 测试是如何保证业务逻辑中不会出现死循环的? - -你可以通过测试来避免坑爹的同事在某些逻辑中写出死循环, 在通常的测试中加上超时的时间, 在覆盖率足够的情况下, 就可以通过跑出超时的测试来排查出现死循环以及低性能的情况. - - -## 测试方法 - -### 黑盒测试 - -黑盒测试 (Black-box Testing), 测试应用程序的功能, 而不是其内部结构或运作. 测试者不需了解代码、内部结构等, 只需知道什么是应用应该做的事, 即当键入特定的输入, 可得到一定的输出. 测试者通过选择`有效输入`和`无效输入`来验证是否正确的输出. 此测试方法可适合大部分的软件测试, 例如集成测试 (Integration Testing) 以及系统测试 (System Testing). - -### 白盒测试 - -白盒测试 (White-box Testing) 测试应用程序的内部结构或运作, 而不是测试应用程序的功能 (即黑盒测试). 在白盒测试时, 以编程语言的角度来设计测试案例. 白盒测试可以应用于单元测试 (Unit Testing)、集成测试 (Integration Testing) 和系统的软件测试流程, 可测试在集成过程中每一单元之间的路径, 或者主系统跟子系统中的测试. - - -## 单元测试 - -单元测试 (Unit Testing) 是白盒测试的一种, 用于针对程序模块进行正确性检验的测试工作. 单元 (Unit) 是指**最小可测试的部件**. 在过程化编程中, 一个单元就是单个程序、函数、过程等; 对于面向对象编程, 最小单元就是方法, 包括基类、抽象类、或者子类中的方法. - -另外, 每次修改代码之后, 通过单元测试来验证比把整个应用启动/重启验证要更快/更简单. - -### 覆盖率 - -测试覆盖率 (Test Coverage) 是指代码中各项逻辑被测试覆盖到的比率, 比如 90% 的覆盖率, 是指代码中 90% 的情况都被测试覆盖到了. - -覆盖率通常由四个维度贡献: - -* 行覆盖率 (line coverage) 是否每一行都执行了? -* 函数覆盖率 (function coverage) 是否每个函数都调用了? -* 分支覆盖率 (branch coverage) 是否每个if代码块都执行了? -* 语句覆盖率 (statement coverage) 是否每个语句都执行了? - -常用的测试覆盖率框架 [istanbul](https://github.com/gotwarlost/istanbul). - -当然覆盖率并不完全是由单元测试贡献, 在单元测试之上还有集成测试等. 更多关于覆盖率的内容可以参见[测试覆盖(率)到底有什么用?](http://www.infoq.com/cn/articles/test-coverage-rate-role) - -### Mock - -Mock 主要用于单元测试中. 当一个测试的对象可能依赖其他 (也许复杂/多个) 的对象. 为了确保其行为不受其他对象的影响, 你可以通过模拟其他对象的行为来隔离你要测试的对象. - -当你要测试的单元依赖了一些很难纳入单元测试的情况时 (例如要测试的单元依赖数据库/文件操作/第三方服务 等情况的返回时), 使用 mock 是非常有用的. 简而言之, Mock 是模拟其他依赖的 behaviour. - -Mock 与 Stub 的区别参见: [Mocks Aren't Stubs](https://martinfowler.com/articles/mocksArentStubs.html) - - -### 常见测试工具 - -* [Mocha](https://github.com/mochajs/mocha) -* [ava](https://github.com/avajs/ava) -* [Jest](https://github.com/facebook/jest) - - -## 集成测试 - -集成测试也称综合测试、组装测试、联合测试, 将程序模块采用适当的集成策略组装起来, 对系统的接口及集成后的功能进行正确性检测的测试工作. 集成测试可以是黑盒的, 也可以是白盒的, 其主要目的是检查软件单位之间的接口是否正确, 而集成测试的对象是**已经经过单元测试的模块**. - -例如你可以在本地将项目中的 web app 启动, 并模拟接口调用: - -```javascript -describe('Path API', () => { - // ... - - describe('GET /v2/path/:_id', () => { - it('should return 200 GET /v2/path/:_id', () => { - return request - .get('/v2/path/' + pathId) - .set('Cookie', 'common_user=xxx') - .expect(200); - }); - }); - - describe('POST /v2/path', () => { - it('should return 412 POST /v2/path lost params path', () => { - return request - .post('/v2/path') - .set('Cookie', 'common_user=xxx') - .expect(412); - }); - - it('should return 409 POST /v2/path when path exist', () => { - return request - .post('/v2/path') - .send({path: '/'}) - .set('Cookie', 'common_user=xxx') - .expect(409); - }); - - it('should return 200 POST /v2/path successfully', () => { - return request - .post('/v2/path') - .send({path: '/comment'}) - .set('Cookie', 'common_user=xxx') - .expect(200); - }); - }); - - // ... -}); -``` - -## 基准测试 - -目前 Node.js 中流行的白盒级基准测试工具是 [benchmark](https://benchmarkjs.com/docs). - -```javascript -const Benchmark = require('benchmark'); -const suite = new Benchmark.Suite; - -suite.add('RegExp#test', function() { - /o/.test('Hello World!'); -}) -.add('String#indexOf', function() { - 'Hello World!'.indexOf('o') > -1; -}) -.on('cycle', function(event) { - console.log(String(event.target)); -}) -.on('complete', function() { - console.log('Fastest is ' + this.filter('fastest').map('name')); -}) -// run async -.run({ 'async': true }); -``` - -你可以将同一个功能的不同实现基于同一个标准来比较不同实现的速度, 从而得到最优解. - -黑盒级别的基准测试, 则推荐 [Apache ab](https://httpd.apache.org/docs/2.4/programs/ab.html) 以及 [wrk](https://github.com/wg/wrk) 等, 例如执行: - -``` -ab -n 100 -c 10 https://ele.me/ -``` - -可以得到如下的详细数据: - -``` -Server Software: Tengine/2.1.1 -Server Hostname: ele.me -Server Port: 443 -SSL/TLS Protocol: TLSv1.2,ECDHE-RSA-AES256-GCM-SHA384,2048,256 - -Document Path: / -Document Length: 284 bytes - -Concurrency Level: 10 -Time taken for tests: 1.775 seconds -Complete requests: 100 -Failed requests: 0 -Non-2xx responses: 100 -Total transferred: 62400 bytes -HTML transferred: 28400 bytes -Requests per second: 56.33 [#/sec] (mean) -Time per request: 177.511 [ms] (mean) -Time per request: 17.751 [ms] (mean, across all concurrent requests) -Transfer rate: 34.33 [Kbytes/sec] received - -Connection Times (ms) - min mean[+/-sd] median max -Connect: 88 116 26.0 104 234 -Processing: 33 55 39.6 47 394 -Waiting: 33 54 39.0 46 394 -Total: 124 171 48.1 152 491 - -Percentage of the requests served within a certain time (ms) - 50% 152 - 66% 184 - 75% 193 - 80% 199 - 90% 224 - 95% 242 - 98% 288 - 99% 491 - 100% 491 (longest request) -``` - -与前者相比, ab 等工具可以设置规模以及并发情况. 在比规模不大/需求不复杂的情况下, ab 以及 wrk 也可以用于做压力测试. - - -## 压力测试 - -压力测试 (Stress testing), 是保证系统稳定性的一种测试方法. 通过预估系统所需要承载的 QPS, TPS 等指标, 然后通过如 [Jmeter](http://jmeter.apache.org/) 等压测工具模拟相应的请求情况, 来验证当前应能能否达到目标. - -对于比较重要, 流量较高或者后期业务量会持续增长的系统, 进行压力测试是保证项目品质的重要环节. 常见的如负载是否均衡, 带宽是否合理, 以及磁盘 IO 网络 IO 等问题都可以通过比较极限的压力测试暴露出来. - - -## Assert - -断言 (Assert) 是快速判断并对不符合预期的情况进行报错的模块. 是将: - -```javascript -if (condition) { - throw new Error('Sth wrong'); -} -``` - -写成: - -```javascript -assert(!condition, 'Sth wrong'); -``` - -等等情况的一种简化. 并且提供了丰富了 `equal` 判断, 对于对象类型也有深度/严格判断等情况支持. - -Node.js 中内置的 `assert` 模块也是属于断言模块的一种, 但是官方在文档中有注明, 该内置模块主要是用于内置代码编写时的基本断言需求, 并不是一个通用的断言库 (**not intended to be used as a general purpose assertion library**) - -### 常见断言工具 - -* [Chai](https://github.com/chaijs/chai) -* [should.js](https://github.com/shouldjs/should.js) - +# Test + +* `[Basic]` Methods +* `[Basic]` Unit Test +* `[Basic]` Benchmarks +* `[Basic]` Integration Test +* `[Basic]` Pressure Test +* `[Doc]` Assert diff --git a/sections/en-us/util.md b/sections/en-us/util.md index c49f404..29975d9 100644 --- a/sections/en-us/util.md +++ b/sections/en-us/util.md @@ -1,230 +1,6 @@ # util * `[Doc]` URL -* `[Doc]` Query Strings (查询字符串) -* `[Doc]` Utilities (实用函数) -* `[Basic]` 正则表达式 - - -## URL - -```javascript -┌─────────────────────────────────────────────────────────────────────────────┐ -│ href │ -├──────────┬┬───────────┬─────────────────┬───────────────────────────┬───────┤ -│ protocol ││ auth │ host │ path │ hash │ -│ ││ ├──────────┬──────┼──────────┬────────────────┤ │ -│ ││ │ hostname │ port │ pathname │ search │ │ -│ ││ │ │ │ ├─┬──────────────┤ │ -│ ││ │ │ │ │ │ query │ │ -" http: // user:pass @ host.com : 8080 /p/a/t/h ? query=string #hash " -│ ││ │ │ │ │ │ │ │ -└──────────┴┴───────────┴──────────┴──────┴──────────┴─┴──────────────┴───────┘ -``` - -### 转义字符 - -常见的需要转移的字符列表: - -|字符|encodeURI| -|---|---| -|`' '`|`'%20'`| -|`<`|`'%3C'`| -|`>`|`'%3E'`| -|`"`|`'%22'`| -|```|`'%60'`| -|`\r`|`'%0D'`| -|`\n`|`'%0A'`| -|`\t`|`'%09'`| -|`{`|`'%7B'`| -|`}`|`'%7D'`| -|`|`|`'%7C'`| -|`\\`|`'%5C'`| -|`^`|`'%5E'`| -|`'`|'%27'| - -想了解更多? 你可以这样: - -```javascript -Array(range).fill(0) - .map((_, i) => String.fromCharCode(i)) - .map(encodeURI) -``` - -range 先来个 255 试试 (doge - - -## Query Strings - -query string 属于 URL 的一部分, 见上方 URL 的表. 在 Node.js 中有内置提供一个 `querystring` 的模块. - -|方法|描述| -|---|---| -|.parse(str[, sep[, eq[, options]]])|将一个 query string 解析为 json 对象| -|.unescape(str)|供 .parse 调用的内置解转义方法, 暴露出来以供用户自行替代| -|.stringify(obj[, sep[, eq[, options]]])|将一个 json 对象转换成 query string| -|.escape(str)|供 .stringify 调用的内置转义方法, 暴露出来以供用户自行替代| - -Node.js 内置的 querystring 目前对于有深度的结构尚不支持. 见如下: - -```javascript -const qs = require('qs'); // 第三方 -const querystring = require('querystring'); // Node.js 内置 - -let obj = { a: { b: { c: 1 } } }; - -console.log(qs.stringify(obj)); // 'a%5Bb%5D%5Bc%5D=1' -console.log(querystring.stringify(obj)); // 'a=' - -let str = 'a%5Bb%5D%5Bc%5D=1'; - -console.log(qs.parse(str)); // { a: { b: { c: '1' } } } -console.log(querystring.parse(str)); // { 'a[b][c]': '1' } -``` - -> HTTP 如何通过 GET 方法 (URL) 传递 let arr = [1,2,3,4] 给服务器? - -```javascript -const qs = require('qs'); - -let arr = [1,2,3,4]; -let str = qs.stringify({arr}); - -console.log(str); // arr%5B0%5D=1&arr%5B1%5D=2&arr%5B2%5D=3&arr%5B3%5D=4 -console.log(decodeURI(str)); // 'arr[0]=1&arr[1]=2&arr[2]=3&arr[3]=4' -console.log(qs.parse(str)); // { arr: [ '1', '2', '3', '4' ] } -``` - -通过 `https://your.host/api/?arr[0]=1&arr[1]=2&arr[2]=3&arr[3]=4` 即可传递把 arr 数组传递给服务器 - - -## util - -util.is*() 从 v4.0.0 开始被不建议使用即将废弃 (deprecated). 大概的废弃原因, 笔者个人认为是维护这些功能吃力不讨好, 而且现在流行的轮子那么多. 那么一下是具体列表: - -* util.debug(string) -* util.error([...strings]) -* util.isArray(object) -* util.isBoolean(object) -* util.isBuffer(object) -* util.isDate(object) -* util.isError(object) -* util.isFunction(object) -* util.isNull(object) -* util.isNullOrUndefined(object) -* util.isNumber(object) -* util.isObject(object) -* util.isPrimitive(object) -* util.isRegExp(object) -* util.isString(object) -* util.isSymbol(object) -* util.isUndefined(object) -* util.log(string) -* util.print([...strings]) -* util.puts([...strings]) -* util._extend(target, source) - -其中大部分都可以作为面试题来问如何实现. - -### util.inherits - -> Node.js 中继承 (util.inherits) 的实现? - -https://github.com/nodejs/node/blob/v7.6.0/lib/util.js#L960 - -```javascript -/** - * Inherit the prototype methods from one constructor into another. - * - * The Function.prototype.inherits from lang.js rewritten as a standalone - * function (not on Function.prototype). NOTE: If this file is to be loaded - * during bootstrapping this function needs to be rewritten using some native - * functions as prototype setup using normal JavaScript does not work as - * expected during bootstrapping (see mirror.js in r114903). - * - * @param {function} ctor Constructor function which needs to inherit the - * prototype. - * @param {function} superCtor Constructor function to inherit prototype from. - * @throws {TypeError} Will error if either constructor is null, or if - * the super constructor lacks a prototype. - */ -exports.inherits = function(ctor, superCtor) { - - if (ctor === undefined || ctor === null) - throw new TypeError('The constructor to "inherits" must not be ' + - 'null or undefined'); - - if (superCtor === undefined || superCtor === null) - throw new TypeError('The super constructor to "inherits" must not ' + - 'be null or undefined'); - - if (superCtor.prototype === undefined) - throw new TypeError('The super constructor to "inherits" must ' + - 'have a prototype'); - - ctor.super_ = superCtor; - Object.setPrototypeOf(ctor.prototype, superCtor.prototype); -}; -``` - -## 正则表达式 - -正则表达式最早生物学上用来描述大脑神经元的一种表达式, 被 GNU 的大胡子拿来做字符串匹配之后在原本的道路上渐行渐远. - -整理中.. - -## 常用模块 - -[Awesome Node.js](https://github.com/sindresorhus/awesome-nodejs) -[Most depended-upon packages](https://www.npmjs.com/browse/depended) - -> 如何获取某个文件夹下所有的文件名? - -一个简单的例子: - -```javascript -const fs = require('fs'); -const path = require('path'); - -function traversal(dir) { - let res = [] - for (let item of fs.readdirSync(dir)) { - let filepath = path.join(dir, item); - try { - let fd = fs.openSync(filepath, 'r'); - let flag = fs.fstatSync(fd).isDirectory(); - fs.close(fd); // TODO - if (flag) { - res.push(...traversal(filepath)); - } else { - res.push(filepath); - } - } catch(err) { - if (err.code === 'ENOENT' && // link 文件打不开 - !!fs.readlinkSync(filepath)) { // 判断是否 link 文件 - res.push(filepath); - } else { - console.error('err', err); - } - } - } - return res.map((file) => path.basename(file)); -} - -console.log(traversal('.')); - - -``` - -当然也可以 Oh my [glob](https://github.com/isaacs/node-glob): - -```javascript -const glob = require("glob"); - -glob("**/*.js", (err, files) { - if (err) { - throw new Error(err); - } - console.log('Here you are:', files.map(path.basename)); -}); -``` +* `[Doc]` Query Strings +* `[Doc]` Utilities +* `[Basic]` Regex From 11724518f5ca8a61f22fca4f2bcb971dc9550132 Mon Sep 17 00:00:00 2001 From: Ahmed Chaabni Date: Mon, 22 May 2017 05:38:45 +0200 Subject: [PATCH 41/98] doc: readme, fix typos (#31) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 090f9a3..f2808b9 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ This is the interview Index about it, and it shows you how to pass the Node.js i ## What is ElemeFE? -It means Frontend team of Eleme (饿了么) which seems like the bigest takeaway company of China. +It means Frontend team of Eleme (饿了么) which seems like the biggest takeaway company of China. There are some public projects of Eleme: @@ -20,7 +20,7 @@ There are some public projects of Eleme: * [restc](https://elemefe.github.io/restc/) A server-side middleware to visualize REST requests. * [vue-infinite-scroll](https://github.com/ElemeFE/vue-infinite-scroll) An infinite scroll directive for vue.js. -As you see, we are frontend team, but we'v been doing some backend job to support frontend to gain more. +As you see, we are frontend team, but we've been doing some backend job to support frontend to gain more. #### Language support From 2597e4eaee47f2b4a57fe3ad752a2b87585b8e62 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Mon, 22 May 2017 13:56:50 +0800 Subject: [PATCH 42/98] site: nav bar, update diff home for each language --- _navbar.md | 2 +- index.html | 11 +---------- sections/en-us/_navbar.md | 4 ++++ sections/zh-cn/_navbar.md | 4 ++++ 4 files changed, 10 insertions(+), 11 deletions(-) create mode 100644 sections/en-us/_navbar.md create mode 100644 sections/zh-cn/_navbar.md diff --git a/_navbar.md b/_navbar.md index f32dd39..fdaee99 100644 --- a/_navbar.md +++ b/_navbar.md @@ -1,4 +1,4 @@ - [Home](/) - Translations - [English](sections/en-us/) - - [简体中文](sections/zh-cn/) \ No newline at end of file + - [简体中文](sections/zh-cn/) diff --git a/index.html b/index.html index 1e2acd3..e4f7644 100644 --- a/index.html +++ b/index.html @@ -19,15 +19,6 @@ .sidebar blockquote { margin-left: 12px; } - nav.app-nav { - position: fixed; - background-color: rgba(255, 255, 255, .8); - width: 100%; - margin-top: 0; - padding: 13px 0; - border-bottom: 1px solid rgba(0, 0, 0, .07); - margin-top: 0; - } section.content { padding-top: 50px; } @@ -41,7 +32,7 @@ name: 'Node.js Interview', auto2top: true, loadNavbar: true, - // loadSidebar: true + repo: 'ElemeFE/node-interview/' } diff --git a/sections/en-us/_navbar.md b/sections/en-us/_navbar.md new file mode 100644 index 0000000..9e6abc4 --- /dev/null +++ b/sections/en-us/_navbar.md @@ -0,0 +1,4 @@ +- [Home](sections/en-us/) +- Translations + - [English](sections/en-us/) + - [简体中文](sections/zh-cn/) diff --git a/sections/zh-cn/_navbar.md b/sections/zh-cn/_navbar.md new file mode 100644 index 0000000..da9c2d4 --- /dev/null +++ b/sections/zh-cn/_navbar.md @@ -0,0 +1,4 @@ +- [Home](sections/zh-cn/) +- Translations + - [English](sections/en-us/) + - [简体中文](sections/zh-cn/) From 86adcb3e42703334660bd4fb32738324ae148d31 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Mon, 22 May 2017 13:58:28 +0800 Subject: [PATCH 43/98] doc: readme, replace team intro to repo motivation --- README.md | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index f2808b9..642306f 100644 --- a/README.md +++ b/README.md @@ -2,29 +2,20 @@ # Node interview of ElemeFE -## What's this? +* ## What's this? -We were looking for developer which had three years development experience with Node.js & Backend dev. +We were looking for senior backend developer with Node.js. And, this repo is the interview catalog (not just questions & answers) about it, and it shows you how to pass the Node.js interview of [ElemeFE](https://github.com/ElemeFE/). -This is the interview Index about it, and it shows you how to pass the Node.js interview of ElemeFE. +* ## Motivation -## What is ElemeFE? +We had a lot of interviews about Node.js & full-stack in 2016. We found there are many developers came from frontend, and most of them had leaky knowledge of backend system. Thus we found it's difficult to got senior Node.js developer about backend (not full-stack). -It means Frontend team of Eleme (饿了么) which seems like the biggest takeaway company of China. +Due to our consistent of open source and sharing spirit, we prepared this Node.js advanced tutorial which called "node-interview". -There are some public projects of Eleme: +* ## Start Reading + * [English Entry](sections/en-us/) + * [简体中文入口](sections/zh-cn/) -* [Element](http://element.eleme.io/#/en-US) A Vue.js 2.0 UI Toolkit for Web. -* [Element-react](https://github.com/eleme/element-react) React.js version for Element. -* [Mint UI](http://mint-ui.github.io/#!/en) Mobile UI elements for Vue.js. -* [restc](https://elemefe.github.io/restc/) A server-side middleware to visualize REST requests. -* [vue-infinite-scroll](https://github.com/ElemeFE/vue-infinite-scroll) An infinite scroll directive for vue.js. +> The content is translated from chinese, We'd say sorry that the translation is just started, you may not able to read it completely. BTW, it is welcome to help us to improve the translations. -As you see, we are frontend team, but we've been doing some backend job to support frontend to gain more. -#### Language support - -This index is translated from chinese, We'd say sorry that the translation is just started, you may not able to read it completely. BTW, it is welcome to help us to improve the translations. - -##### [English](sections/en-us/) -##### [简体中文](sections/zh-cn/) From 156b872b8950883709f17350b19c5be32acb2458 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Mon, 22 May 2017 14:00:50 +0800 Subject: [PATCH 44/98] doc: sections, update title --- sections/en-us/README.md | 2 +- sections/en-us/error.md | 2 +- sections/zh-cn/README.md | 2 +- sections/zh-cn/error.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sections/en-us/README.md b/sections/en-us/README.md index 3efab96..7711127 100644 --- a/sections/en-us/README.md +++ b/sections/en-us/README.md @@ -102,7 +102,7 @@ [View more](sections/en-us/os.md) -## [Error/Debug/Opt](sections/en-us/error.md) +## [Error & Debug](sections/en-us/error.md) * `[Doc]` Errors * `[Doc]` Domain diff --git a/sections/en-us/error.md b/sections/en-us/error.md index f99aa2e..2dd289d 100644 --- a/sections/en-us/error.md +++ b/sections/en-us/error.md @@ -1,4 +1,4 @@ -# Error/Debug/Opt +# Error & Debug * `[Doc]` Errors * `[Doc]` Domain diff --git a/sections/zh-cn/README.md b/sections/zh-cn/README.md index 088f8eb..04e15f5 100644 --- a/sections/zh-cn/README.md +++ b/sections/zh-cn/README.md @@ -151,7 +151,7 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 [阅读更多](sections/zh-cn/os.md) -## [错误处理/调试/优化](sections/zh-cn/error.md) +## [错误处理/调试](sections/zh-cn/error.md) * [`[Doc]` Errors (异常)](sections/zh-cn/error.md#errors) * [`[Doc]` Domain (域)](sections/zh-cn/error.md#domain) diff --git a/sections/zh-cn/error.md b/sections/zh-cn/error.md index 96f23a9..ad3a42d 100644 --- a/sections/zh-cn/error.md +++ b/sections/zh-cn/error.md @@ -1,4 +1,4 @@ -# 错误处理/调试/优化 +# 错误处理/调试 * `[Doc]` Errors (异常) * `[Doc]` Domain (域) From 296dd0efe7ce7f0acfd8ea84bdbfed3a8b959520 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Mon, 22 May 2017 23:35:32 +0800 Subject: [PATCH 45/98] section: process in zh-cn, fix the detail about fork & cluster LB --- sections/zh-cn/process.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sections/zh-cn/process.md b/sections/zh-cn/process.md index 062f446..559258b 100644 --- a/sections/zh-cn/process.md +++ b/sections/zh-cn/process.md @@ -108,7 +108,7 @@ function test() { > child_process.fork 与 POSIX 的 fork 有什么区别? -Node.js 的 `child_process.fork()` 不像 POSIX [fork(2)](http://man7.org/linux/man-pages/man2/fork.2.html) 系统调用, 不会拷贝当前父进程. 这里对于其他语言转过的同学可能比较误导, 可以作为一个比较偏的面试题. +Node.js 的 `child_process.fork()` 在 Unix 上的实现最终调用了 POSIX [fork(2)](http://man7.org/linux/man-pages/man2/fork.2.html), 而 POSIX 的 fork 需要手动管理子进程的资源释放 (waitpid), child_process.fork 则不用关心这个问题, Node.js 会自动释放, 并且可以在 option 中选择父进程死后是否允许子进程存活. * spawn() 启动一个子进程来执行命令 * options.detached 父进程死后是否允许子进程存活 @@ -173,7 +173,7 @@ cluster 模块提供了两种分发连接的方式. 第二种方式是由主进程创建 socket 监听端口后, 将 socket 句柄直接分发给相应的 worker, 然后当连接进来时, 就直接由相应的 worker 来接收连接并处理. -使用第二种方式时, 多个 worker 之间会存在竞争关系, 产生一个老生常谈的 "[惊群效应](https://www.google.com.hk/search?q=%E6%83%8A%E7%BE%A4%E6%95%88%E5%BA%94)" 从而导致效率变低的问题. 该问题常见于 Apache. 并且各自竞争的情况下无法控制一个新的连接由哪个进程来处理, 从而导致各 worker 进程之间的负载不均衡, 比如通常 70% 的连接仅被 8 个进程中的 2 个处理, 而其他进程比较清闲. +使用第二种方式时理论上性能应该较高, 然后时间上存在负载不均衡的问题, 比如通常 70% 的连接仅被 8 个进程中的 2 个处理, 而其他进程比较清闲. ## 进程间通信 From 225a2be9b859210bec879f774227fadf14851838 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Tue, 23 May 2017 23:51:50 +0800 Subject: [PATCH 46/98] section: readme, fix bad url (cause 404) --- sections/en-us/README.md | 116 +++++++-------- sections/zh-cn/README.md | 298 +++++++++++++++++++-------------------- 2 files changed, 207 insertions(+), 207 deletions(-) diff --git a/sections/en-us/README.md b/sections/en-us/README.md index 7711127..b88efdb 100644 --- a/sections/en-us/README.md +++ b/sections/en-us/README.md @@ -6,60 +6,60 @@ ## Guide -## [Basic](sections/en-us/js-basic.md) +## [Common](/sections/en-us/common.md) > It's much more diff between frontend and backend. -* `[Basic]` Type judgment -* `[Basic]` Scope -* `[Basic]` Reference -* `[Basic]` Memory release -* `[Basic]` ES6+ featrues +* `[Common]` Type judgment +* `[Common]` Scope +* `[Common]` Reference +* `[Common]` Memory release +* `[Common]` ES6+ featrues **Common Problem** -[View more](sections/en-us/js-basic.md) +[View more](/sections/en-us/js-basic.md) -## [Module](sections/en-us/module.md) +## [Module](/sections/en-us/module.md) -* `[Basic]` Module -* `[Basic]` Hotfix -* `[Basic]` Context -* `[Basic]` Package Manager +* `[Common]` Module +* `[Common]` Hotfix +* `[Common]` Context +* `[Common]` Package Manager **Common Problem** -[View more](sections/en-us/module.md) +[View more](/sections/en-us/module.md) -## [Event & Async](sections/en-us/event-async.md) +## [Event & Async](/sections/en-us/event-async.md) -* `[Basic]` Promise +* `[Common]` Promise * `[Doc]` Events * `[Doc]` Timers -* `[Point]` Blocking & Non-blocking -* `[Point]` Parallel & Concurrent +* `[Bonus]` Blocking & Non-blocking +* `[Bonus]` Parallel & Concurrent **Common Problem** -[View more](sections/en-us/event-async.md) +[View more](/sections/en-us/event-async.md) -## [Process](sections/en-us/process.md) +## [Process](/sections/en-us/process.md) * `[Doc]` Process * `[Doc]` Child Processes * `[Doc]` Cluster -* `[Basic]` IPC -* `[Basic]` Daemon +* `[Bonus]` IPC +* `[Bonus]` Daemon **Common Problem** -[View more](sections/en-us/process.md) +[View more](/sections/en-us/process.md) -## [IO](sections/en-us/io.md) +## [IO](/sections/en-us/io.md) * `[Doc]` Buffer * `[Doc]` String Decoder @@ -72,103 +72,103 @@ **Common Problem** -[View more](sections/en-us/io.md) +[View more](/sections/en-us/io.md) -## [Network](sections/en-us/network.md) +## [Network](/sections/en-us/network.md) * `[Doc]` Net * `[Doc]` UDP/Datagram * `[Doc]` HTTP * `[Doc]` DNS * `[Doc]` ZLIB -* `[Point]` RPC +* `[Common]` RPC **Common Problem** -[View more](sections/en-us/network.md) +[View more](/sections/en-us/network.md) -## [OS](sections/en-us/os.md) +## [OS](/sections/en-us/os.md) * `[Doc]` TTY * `[Doc]` OS * `[Doc]` Command Line Options -* `[Basic]` Load -* `[Point]` CheckList -* `[Basic]` Indicators +* `[Common]` Load +* `[Bonus]` CheckList +* `[Common]` Indicators **Common Problem** -[View more](sections/en-us/os.md) +[View more](/sections/en-us/os.md) -## [Error & Debug](sections/en-us/error.md) +## [Error & Debug](/sections/en-us/error.md) * `[Doc]` Errors * `[Doc]` Domain * `[Doc]` Debugger * `[Doc]` C/C++ Addon * `[Doc]` V8 -* `[Point]` Memory snapshot -* `[Point]` CPU Profilling +* `[Bonus]` Memory snapshot +* `[Bonus]` CPU Profilling **Common Problem** -[View more](sections/en-us/error.md) +[View more](/sections/en-us/error.md) -## [Test](sections/en-us/test.md) +## [Test](/sections/en-us/test.md) -* `[Basic]` Methods -* `[Basic]` Unit Test -* `[Basic]` Benchmarks -* `[Basic]` Integration Test -* `[Basic]` Pressure Test +* `[Common]` Methods +* `[Common]` Unit Test +* `[Common]` Benchmarks +* `[Common]` Integration Test +* `[Common]` Pressure Test * `[Doc]` Assert **Common Problem** -[View more](sections/en-us/test.md) +[View more](/sections/en-us/test.md) -## [Util](sections/en-us/util.md) +## [Util](/sections/en-us/util.md) * `[Doc]` URL * `[Doc]` Query Strings * `[Doc]` Utilities -* `[Basic]` Regex +* `[Common]` Regex **Common Problem** -[View more](sections/en-us/util.md) +[View more](/sections/en-us/util.md) -## [Storage](sections/en-us/storage.md) +## [Storage](/sections/en-us/storage.md) -* `[Point]` Sql -* `[Point]` NoSql -* `[Point]` Cache -* `[Point]` Consistency +* `[Common]` Sql +* `[Common]` NoSql +* `[Bonus]` Cache +* `[Bonus]` Consistency **Common Problem** -[View more](sections/en-us/storage.md) +[View more](/sections/en-us/storage.md) -## [Security](sections/en-us/security.md) +## [Security](/sections/en-us/security.md) * `[Doc]` Crypto * `[Doc]` TLS/SSL * `[Doc]` HTTPS -* `[Point]` XSS -* `[Point]` CSRF -* `[Point]` MITM -* `[Point]` Sql/Nosql Injection +* `[Bonus]` XSS +* `[Bonus]` CSRF +* `[Bonus]` MITM +* `[Bonus]` Sql/Nosql Injection **Common Problem** -[View more](sections/en-us/security.md) +[View more](/sections/en-us/security.md) ## Final diff --git a/sections/zh-cn/README.md b/sections/zh-cn/README.md index 04e15f5..462569b 100644 --- a/sections/zh-cn/README.md +++ b/sections/zh-cn/README.md @@ -16,235 +16,235 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 整体上大纲列举的并不是很全面, 细节上覆盖率不高, 很多讨论只是点到即止, 希望大家带着问题去思考. -## [Js 基础问题](sections/zh-cn/js-basic.md) +## [Js 基础问题](/sections/zh-cn/js-basic.md) > 与前端 Js 不同, 后端是直面服务器的, 更加偏向内存方面. -* [`[Basic]` 类型判断](sections/zh-cn/js-basic.md#类型判断) -* [`[Basic]` 作用域](sections/zh-cn/js-basic.md#作用域) -* [`[Basic]` 引用传递](sections/zh-cn/js-basic.md#引用传递) -* [`[Basic]` 内存释放](sections/zh-cn/js-basic.md#内存释放) -* [`[Basic]` ES6 新特性](sections/zh-cn/js-basic.md#es6-新特性) +* [`[Basic]` 类型判断](/sections/zh-cn/js-basic.md#类型判断) +* [`[Basic]` 作用域](/sections/zh-cn/js-basic.md#作用域) +* [`[Basic]` 引用传递](/sections/zh-cn/js-basic.md#引用传递) +* [`[Basic]` 内存释放](/sections/zh-cn/js-basic.md#内存释放) +* [`[Basic]` ES6 新特性](/sections/zh-cn/js-basic.md#es6-新特性) **常见问题** -* js 中什么类型是引用传递, 什么类型是值传递? 如何将值类型的变量以引用的方式传递? [[more]](sections/zh-cn/js-basic.md#q-value) +* js 中什么类型是引用传递, 什么类型是值传递? 如何将值类型的变量以引用的方式传递? [[more]](/sections/zh-cn/js-basic.md#q-value) * js 中, 0.1 + 0.2 === 0.3 是否为 true ? 在不知道浮点数位数时应该怎样判断两个浮点数之和与第三数是否相等? -* const 定义的 Array 中间元素能否被修改? 如果可以, 那 const 修饰对象的意义是? [[more]](sections/zh-cn/js-basic.md#q-const) -* JavaScript 中不同类型以及不同环境下变量的内存都是何时释放? [[more]](sections/zh-cn/js-basic.md#q-mem) +* const 定义的 Array 中间元素能否被修改? 如果可以, 那 const 修饰对象的意义是? [[more]](/sections/zh-cn/js-basic.md#q-const) +* JavaScript 中不同类型以及不同环境下变量的内存都是何时释放? [[more]](/sections/zh-cn/js-basic.md#q-mem) -[阅读更多](sections/zh-cn/js-basic.md) +[阅读更多](/sections/zh-cn/js-basic.md) -## [模块](sections/zh-cn/module.md) +## [模块](/sections/zh-cn/module.md) -* [`[Basic]` 模块机制](sections/zh-cn/module.md#模块机制) -* [`[Basic]` 热更新](sections/zh-cn/module.md#热更新) -* [`[Basic]` 上下文](sections/zh-cn/module.md#上下文) -* [`[Basic]` 包管理](sections/zh-cn/module.md#包管理) +* [`[Basic]` 模块机制](/sections/zh-cn/module.md#模块机制) +* [`[Basic]` 热更新](/sections/zh-cn/module.md#热更新) +* [`[Basic]` 上下文](/sections/zh-cn/module.md#上下文) +* [`[Basic]` 包管理](/sections/zh-cn/module.md#包管理) **常见问题** -* a.js 和 b.js 两个文件互相 require 是否会死循环? 双方是否能导出变量? 如何从设计上避免这种问题? [[more]](sections/zh-cn/module.md#q-loop) -* 如果 a.js require 了 b.js, 那么在 b 中定义全局变量 `t = 111` 能否在 a 中直接打印出来? [[more]](sections/zh-cn/module.md#q-global) -* 如何在不重启 node 进程的情况下热更新一个 js/json 文件? 这个问题本身是否有问题? [[more]](sections/zh-cn/module.md#q-hot) +* a.js 和 b.js 两个文件互相 require 是否会死循环? 双方是否能导出变量? 如何从设计上避免这种问题? [[more]](/sections/zh-cn/module.md#q-loop) +* 如果 a.js require 了 b.js, 那么在 b 中定义全局变量 `t = 111` 能否在 a 中直接打印出来? [[more]](/sections/zh-cn/module.md#q-global) +* 如何在不重启 node 进程的情况下热更新一个 js/json 文件? 这个问题本身是否有问题? [[more]](/sections/zh-cn/module.md#q-hot) -[阅读更多](sections/zh-cn/module.md) +[阅读更多](/sections/zh-cn/module.md) -## [事件/异步](sections/zh-cn/event-async.md) +## [事件/异步](/sections/zh-cn/event-async.md) -* [`[Basic]` Promise](sections/zh-cn/event-async.md#promise) -* [`[Doc]` Events (事件)](sections/zh-cn/event-async.md#events) -* [`[Doc]` Timers (定时器)](sections/zh-cn/event-async.md#timers) -* [`[Point]` 阻塞/异步](sections/zh-cn/event-async.md#阻塞异步) -* [`[Point]` 并行/并发](sections/zh-cn/event-async.md#并行并发) +* [`[Basic]` Promise](/sections/zh-cn/event-async.md#promise) +* [`[Doc]` Events (事件)](/sections/zh-cn/event-async.md#events) +* [`[Doc]` Timers (定时器)](/sections/zh-cn/event-async.md#timers) +* [`[Point]` 阻塞/异步](/sections/zh-cn/event-async.md#阻塞异步) +* [`[Point]` 并行/并发](/sections/zh-cn/event-async.md#并行并发) **常见问题** -* Promise 中 .then 的第二参数与 .catch 有什么区别? [[more]](sections/zh-cn/event-async.md#q-1) -* Eventemitter 的 emit 是同步还是异步? [[more]](sections/zh-cn/event-async.md#q-2) -* 如何判断接口是否异步? 是否只要有回调函数就是异步? [[more]](sections/zh-cn/event-async.md#q-3) -* nextTick, setTimeout 以及 setImmediate 三者有什么区别? [[more]](sections/zh-cn/event-async.md#q-4) -* 如何实现一个 sleep 函数? [[more]](sections/zh-cn/event-async.md#q-5) -* 如何实现一个异步的 reduce? (注:不是异步完了之后同步 reduce) [[more]](sections/zh-cn/event-async.md#q-6) +* Promise 中 .then 的第二参数与 .catch 有什么区别? [[more]](/sections/zh-cn/event-async.md#q-1) +* Eventemitter 的 emit 是同步还是异步? [[more]](/sections/zh-cn/event-async.md#q-2) +* 如何判断接口是否异步? 是否只要有回调函数就是异步? [[more]](/sections/zh-cn/event-async.md#q-3) +* nextTick, setTimeout 以及 setImmediate 三者有什么区别? [[more]](/sections/zh-cn/event-async.md#q-4) +* 如何实现一个 sleep 函数? [[more]](/sections/zh-cn/event-async.md#q-5) +* 如何实现一个异步的 reduce? (注:不是异步完了之后同步 reduce) [[more]](/sections/zh-cn/event-async.md#q-6) -[阅读更多](sections/zh-cn/event-async.md) +[阅读更多](/sections/zh-cn/event-async.md) -## [进程](sections/zh-cn/process.md) +## [进程](/sections/zh-cn/process.md) -* [`[Doc]` Process (进程)](sections/zh-cn/process.md#process) -* [`[Doc]` Child Processes (子进程)](sections/zh-cn/process.md#child-process) -* [`[Doc]` Cluster (集群)](sections/zh-cn/process.md#cluster) -* [`[Basic]` 进程间通信](sections/zh-cn/process.md#进程间通信) -* [`[Basic]` 守护进程](sections/zh-cn/process.md#守护进程) +* [`[Doc]` Process (进程)](/sections/zh-cn/process.md#process) +* [`[Doc]` Child Processes (子进程)](/sections/zh-cn/process.md#child-process) +* [`[Doc]` Cluster (集群)](/sections/zh-cn/process.md#cluster) +* [`[Basic]` 进程间通信](/sections/zh-cn/process.md#进程间通信) +* [`[Basic]` 守护进程](/sections/zh-cn/process.md#守护进程) **常见问题** -* 进程的当前工作目录是什么? 有什么作用? [[more]](sections/zh-cn/process.md#q-cwd) -* child_process.fork 与 POSIX 的 fork 有什么区别? [[more]](sections/zh-cn/process.md#q-fork) -* 父进程或子进程的死亡是否会影响对方? 什么是孤儿进程? [[more]](sections/zh-cn/process.md#q-child) -* cluster 是如何保证负载均衡的? [[more]](sections/zh-cn/process.md#how-it-works) -* 什么是守护进程? 如何实现守护进程? [[more]](sections/zh-cn/process.md#守护进程) +* 进程的当前工作目录是什么? 有什么作用? [[more]](/sections/zh-cn/process.md#q-cwd) +* child_process.fork 与 POSIX 的 fork 有什么区别? [[more]](/sections/zh-cn/process.md#q-fork) +* 父进程或子进程的死亡是否会影响对方? 什么是孤儿进程? [[more]](/sections/zh-cn/process.md#q-child) +* cluster 是如何保证负载均衡的? [[more]](/sections/zh-cn/process.md#how-it-works) +* 什么是守护进程? 如何实现守护进程? [[more]](/sections/zh-cn/process.md#守护进程) -[阅读更多](sections/zh-cn/process.md) +[阅读更多](/sections/zh-cn/process.md) -## [IO](sections/zh-cn/io.md) +## [IO](/sections/zh-cn/io.md) -* [`[Doc]` Buffer](sections/zh-cn/io.md#buffer) -* [`[Doc]` String Decoder (字符串解码)](sections/zh-cn/io.md#string-decoder) -* [`[Doc]` Stream (流)](sections/zh-cn/io.md#stream) -* [`[Doc]` Console (控制台)](sections/zh-cn/io.md#console) -* [`[Doc]` File System (文件系统)](sections/zh-cn/io.md#file) -* [`[Doc]` Readline](sections/zh-cn/io.md#readline) -* [`[Doc]` REPL](sections/zh-cn/io.md#repl) +* [`[Doc]` Buffer](/sections/zh-cn/io.md#buffer) +* [`[Doc]` String Decoder (字符串解码)](/sections/zh-cn/io.md#string-decoder) +* [`[Doc]` Stream (流)](/sections/zh-cn/io.md#stream) +* [`[Doc]` Console (控制台)](/sections/zh-cn/io.md#console) +* [`[Doc]` File System (文件系统)](/sections/zh-cn/io.md#file) +* [`[Doc]` Readline](/sections/zh-cn/io.md#readline) +* [`[Doc]` REPL](/sections/zh-cn/io.md#repl) **常见问题** -* Buffer 一般用于处理什么数据? 其长度能否动态变化? [[more]](sections/zh-cn/io.md#buffer) -* Stream 的 highWaterMark 与 drain 事件是什么? 二者之间的关系是? [[more]](sections/zh-cn/io.md#缓冲区) -* Stream 的 pipe 的作用是? 在 pipe 的过程中数据是引用传递还是拷贝传递? [[more]](sections/zh-cn/io.md#pipe) -* 什么是文件描述符? 输入流/输出流/错误流是什么? [[more]](sections/zh-cn/io.md#file) -* console.log 是同步还是异步? 如何实现一个 console.log? [[more]](sections/zh-cn/io.md#console) -* 如何同步的获取用户的输入? [[more]](sections/zh-cn/io.md#如何同步的获取用户的输入) -* Readline 是如何实现的? (有思路即可) [[more]](sections/zh-cn/io.md#readline) +* Buffer 一般用于处理什么数据? 其长度能否动态变化? [[more]](/sections/zh-cn/io.md#buffer) +* Stream 的 highWaterMark 与 drain 事件是什么? 二者之间的关系是? [[more]](/sections/zh-cn/io.md#缓冲区) +* Stream 的 pipe 的作用是? 在 pipe 的过程中数据是引用传递还是拷贝传递? [[more]](/sections/zh-cn/io.md#pipe) +* 什么是文件描述符? 输入流/输出流/错误流是什么? [[more]](/sections/zh-cn/io.md#file) +* console.log 是同步还是异步? 如何实现一个 console.log? [[more]](/sections/zh-cn/io.md#console) +* 如何同步的获取用户的输入? [[more]](/sections/zh-cn/io.md#如何同步的获取用户的输入) +* Readline 是如何实现的? (有思路即可) [[more]](/sections/zh-cn/io.md#readline) -[阅读更多](sections/zh-cn/io.md) +[阅读更多](/sections/zh-cn/io.md) -## [Network](sections/zh-cn/network.md) +## [Network](/sections/zh-cn/network.md) -* [`[Doc]` Net (网络)](sections/zh-cn/network.md#net) -* [`[Doc]` UDP/Datagram](sections/zh-cn/network.md#udp) -* [`[Doc]` HTTP](sections/zh-cn/network.md#http) -* [`[Doc]` DNS (域名服务器)](sections/zh-cn/network.md#dns) -* [`[Doc]` ZLIB (压缩)](sections/zh-cn/network.md#zlib) -* [`[Point]` RPC](sections/zh-cn/network.md#rpc) +* [`[Doc]` Net (网络)](/sections/zh-cn/network.md#net) +* [`[Doc]` UDP/Datagram](/sections/zh-cn/network.md#udp) +* [`[Doc]` HTTP](/sections/zh-cn/network.md#http) +* [`[Doc]` DNS (域名服务器)](/sections/zh-cn/network.md#dns) +* [`[Doc]` ZLIB (压缩)](/sections/zh-cn/network.md#zlib) +* [`[Point]` RPC](/sections/zh-cn/network.md#rpc) **常见问题** -* cookie 与 session 的区别? 服务端如何清除 cookie? [[more]](sections/zh-cn/network.md#q-cookie-session) -* HTTP 协议中的 POST 和 PUT 有什么区别? [[more]](sections/zh-cn/network.md#q-post-put) -* 什么是跨域请求? 如何允许跨域? [[more]](sections/zh-cn/network.md#q-cors) -* TCP/UDP 的区别? TCP 粘包是怎么回事,如何处理? UDP 有粘包吗? [[more]](sections/zh-cn/network.md#q-tcp-udp) -* `TIME_WAIT` 是什么情况? 出现过多的 `TIME_WAIT` 可能是什么原因? [[more]](sections/zh-cn/network.md#q-time-wait) +* cookie 与 session 的区别? 服务端如何清除 cookie? [[more]](/sections/zh-cn/network.md#q-cookie-session) +* HTTP 协议中的 POST 和 PUT 有什么区别? [[more]](/sections/zh-cn/network.md#q-post-put) +* 什么是跨域请求? 如何允许跨域? [[more]](/sections/zh-cn/network.md#q-cors) +* TCP/UDP 的区别? TCP 粘包是怎么回事,如何处理? UDP 有粘包吗? [[more]](/sections/zh-cn/network.md#q-tcp-udp) +* `TIME_WAIT` 是什么情况? 出现过多的 `TIME_WAIT` 可能是什么原因? [[more]](/sections/zh-cn/network.md#q-time-wait) * ECONNRESET 是什么错误? 如何复现这个错误? -* socket hang up 是什么意思? 可能在什么情况下出现? [[more]](sections/zh-cn/network.md#socket-hang-up) +* socket hang up 是什么意思? 可能在什么情况下出现? [[more]](/sections/zh-cn/network.md#socket-hang-up) * hosts 文件是什么? 什么叫 DNS 本地解析? * 列举几个提高网络传输速度的办法? -[阅读更多](sections/zh-cn/network.md) +[阅读更多](/sections/zh-cn/network.md) -## [OS](sections/zh-cn/os.md) +## [OS](/sections/zh-cn/os.md) -* [`[Doc]` TTY](sections/zh-cn/os.md#tty) -* [`[Doc]` OS (操作系统)](sections/zh-cn/os.md#os-1) -* [`[Doc]` Path](sections/zh-cn/os.md#path) -* [`[Doc]` 命令行参数](sections/zh-cn/os.md#命令行参数) -* [`[Basic]` 负载](sections/zh-cn/os.md#负载) -* [`[Point]` CheckList](sections/zh-cn/os.md#checklist) +* [`[Doc]` TTY](/sections/zh-cn/os.md#tty) +* [`[Doc]` OS (操作系统)](/sections/zh-cn/os.md#os-1) +* [`[Doc]` Path](/sections/zh-cn/os.md#path) +* [`[Doc]` 命令行参数](/sections/zh-cn/os.md#命令行参数) +* [`[Basic]` 负载](/sections/zh-cn/os.md#负载) +* [`[Point]` CheckList](/sections/zh-cn/os.md#checklist) **常见问题** -* 什么是 TTY? 如何判断是否处于 TTY 环境? [[more]](sections/zh-cn/os.md#tty) -* 不同操作系统的换行符 (EOL) 有什么区别? [[more]](sections/zh-cn/os.md#os) -* 服务器负载是什么概念? 如何查看负载? [[more]](sections/zh-cn/os.md#负载) -* ulimit 是用来干什么的? [[more]](sections/zh-cn/os.md#ulimit) +* 什么是 TTY? 如何判断是否处于 TTY 环境? [[more]](/sections/zh-cn/os.md#tty) +* 不同操作系统的换行符 (EOL) 有什么区别? [[more]](/sections/zh-cn/os.md#os) +* 服务器负载是什么概念? 如何查看负载? [[more]](/sections/zh-cn/os.md#负载) +* ulimit 是用来干什么的? [[more]](/sections/zh-cn/os.md#ulimit) -[阅读更多](sections/zh-cn/os.md) +[阅读更多](/sections/zh-cn/os.md) -## [错误处理/调试](sections/zh-cn/error.md) +## [错误处理/调试](/sections/zh-cn/error.md) -* [`[Doc]` Errors (异常)](sections/zh-cn/error.md#errors) -* [`[Doc]` Domain (域)](sections/zh-cn/error.md#domain) -* [`[Doc]` Debugger (调试器)](sections/zh-cn/error.md#debugger) -* [`[Doc]` C/C++ 插件](sections/zh-cn/error.md#c-c++-addon) -* [`[Doc]` V8](sections/zh-cn/error.md#v8) -* [`[Point]` 内存快照](sections/zh-cn/error.md#内存快照) -* [`[Point]` CPU profiling](sections/zh-cn/error.md#cpu-profiling) +* [`[Doc]` Errors (异常)](/sections/zh-cn/error.md#errors) +* [`[Doc]` Domain (域)](/sections/zh-cn/error.md#domain) +* [`[Doc]` Debugger (调试器)](/sections/zh-cn/error.md#debugger) +* [`[Doc]` C/C++ 插件](/sections/zh-cn/error.md#c-c++-addon) +* [`[Doc]` V8](/sections/zh-cn/error.md#v8) +* [`[Point]` 内存快照](/sections/zh-cn/error.md#内存快照) +* [`[Point]` CPU profiling](/sections/zh-cn/error.md#cpu-profiling) **常见问题** -* 怎么处理未预料的出错? 用 try/catch ,domains 还是其它什么? [[more]](sections/zh-cn/error.md#q-handle-error) -* 什么是 `uncaughtException` 事件? 一般在什么情况下使用该事件? [[more]](sections/zh-cn/error.md#uncaughtexception) -* domain 的原理是? 为什么要弃用 domain? [[more]](sections/zh-cn/error.md#domain) +* 怎么处理未预料的出错? 用 try/catch ,domains 还是其它什么? [[more]](/sections/zh-cn/error.md#q-handle-error) +* 什么是 `uncaughtException` 事件? 一般在什么情况下使用该事件? [[more]](/sections/zh-cn/error.md#uncaughtexception) +* domain 的原理是? 为什么要弃用 domain? [[more]](/sections/zh-cn/error.md#domain) * 什么是防御性编程? 与其相对的 let it crash 又是什么? * 为什么要在 cb 的第一参数传 error? 为什么有的 cb 第一个参数不是 error, 例如 http.createServer? -* 为什么有些异常没法根据报错信息定位到代码调用? 如何准确的定位一个异常? [[more]](sections/zh-cn/error.md#错误栈丢失) -* 内存泄漏通常由哪些原因导致? 如何分析以及定位内存泄漏? [[more]](sections/zh-cn/error.md#内存快照) +* 为什么有些异常没法根据报错信息定位到代码调用? 如何准确的定位一个异常? [[more]](/sections/zh-cn/error.md#错误栈丢失) +* 内存泄漏通常由哪些原因导致? 如何分析以及定位内存泄漏? [[more]](/sections/zh-cn/error.md#内存快照) -[阅读更多](sections/zh-cn/error.md) +[阅读更多](/sections/zh-cn/error.md) -## [测试](sections/zh-cn/test.md) +## [测试](/sections/zh-cn/test.md) -* [`[Basic]` 测试方法](sections/zh-cn/test.md#测试方法) -* [`[Basic]` 单元测试](sections/zh-cn/test.md#单元测试) -* [`[Basic]` 集成测试](sections/zh-cn/test.md#集成测试) -* [`[Basic]` 基准测试](sections/zh-cn/test.md#基准测试) -* [`[Basic]` 压力测试](sections/zh-cn/test.md#压力测试) -* [`[Doc]` Assert (断言)](sections/zh-cn/test.md#assert) +* [`[Basic]` 测试方法](/sections/zh-cn/test.md#测试方法) +* [`[Basic]` 单元测试](/sections/zh-cn/test.md#单元测试) +* [`[Basic]` 集成测试](/sections/zh-cn/test.md#集成测试) +* [`[Basic]` 基准测试](/sections/zh-cn/test.md#基准测试) +* [`[Basic]` 压力测试](/sections/zh-cn/test.md#压力测试) +* [`[Doc]` Assert (断言)](/sections/zh-cn/test.md#assert) **常见问题** -* 为什么要写测试? 写测试是否会拖累开发进度?[[more]](sections/zh-cn/test.md#q-why-write-test) -* 单元测试的单元是指什么? 什么是覆盖率?[[more]](sections/zh-cn/test.md#单元测试) -* 测试是如何保证业务逻辑中不会出现死循环的?[[more]](sections/zh-cn/test.md#q-death-loop) -* mock 是什么? 一般在什么情况下 mock?[[more]](sections/zh-cn/test.md#mock) +* 为什么要写测试? 写测试是否会拖累开发进度?[[more]](/sections/zh-cn/test.md#q-why-write-test) +* 单元测试的单元是指什么? 什么是覆盖率?[[more]](/sections/zh-cn/test.md#单元测试) +* 测试是如何保证业务逻辑中不会出现死循环的?[[more]](/sections/zh-cn/test.md#q-death-loop) +* mock 是什么? 一般在什么情况下 mock?[[more]](/sections/zh-cn/test.md#mock) -[阅读更多](sections/zh-cn/test.md) +[阅读更多](/sections/zh-cn/test.md) -## [util](sections/zh-cn/util.md) +## [util](/sections/zh-cn/util.md) -* [`[Doc]` URL](sections/zh-cn/util.md#url) -* [`[Doc]` Query Strings (查询字符串)](sections/zh-cn/util.md#query-strings) -* [`[Doc]` Utilities (实用函数)](sections/zh-cn/util.md#util-1) -* [`[Basic]` 正则表达式](sections/zh-cn/util.md#正则表达式) +* [`[Doc]` URL](/sections/zh-cn/util.md#url) +* [`[Doc]` Query Strings (查询字符串)](/sections/zh-cn/util.md#query-strings) +* [`[Doc]` Utilities (实用函数)](/sections/zh-cn/util.md#util-1) +* [`[Basic]` 正则表达式](/sections/zh-cn/util.md#正则表达式) **常见问题** -* HTTP 如何通过 GET 方法 (URL) 传递 let arr = [1,2,3,4] 给服务器? [[more]](sections/zh-cn/util.md#get-param) -* Node.js 中继承 (util.inherits) 的实现? [[more]](sections/zh-cn/util.md#utilinherits) -* 如何递归获取某个文件夹下所有的文件名? [[more]](sections/zh-cn/util.md#q-traversal) +* HTTP 如何通过 GET 方法 (URL) 传递 let arr = [1,2,3,4] 给服务器? [[more]](/sections/zh-cn/util.md#get-param) +* Node.js 中继承 (util.inherits) 的实现? [[more]](/sections/zh-cn/util.md#utilinherits) +* 如何递归获取某个文件夹下所有的文件名? [[more]](/sections/zh-cn/util.md#q-traversal) -[阅读更多](sections/zh-cn/util.md) +[阅读更多](/sections/zh-cn/util.md) -## [存储](sections/zh-cn/storage.md) +## [存储](/sections/zh-cn/storage.md) -* [`[Point]` Mysql](sections/zh-cn/storage.md#mysql) -* [`[Point]` Mongodb](sections/zh-cn/storage.md#mongodb) -* [`[Point]` Replication](sections/zh-cn/storage.md#replication) -* [`[Point]` 数据一致性](sections/zh-cn/storage.md#数据一致性) -* [`[Point]` 缓存](sections/zh-cn/storage.md#缓存) +* [`[Point]` Mysql](/sections/zh-cn/storage.md#mysql) +* [`[Point]` Mongodb](/sections/zh-cn/storage.md#mongodb) +* [`[Point]` Replication](/sections/zh-cn/storage.md#replication) +* [`[Point]` 数据一致性](/sections/zh-cn/storage.md#数据一致性) +* [`[Point]` 缓存](/sections/zh-cn/storage.md#缓存) **常见问题** -* 备份数据库与 M/S, M/M 等部署方式的区别? [[more]](sections/zh-cn/storage.md#replication) -* 索引有什么用,大致原理是什么? 设计索引有什么注意点? [[more]](sections/zh-cn/storage.md#索引) -* Monogdb 连接问题(超时/断开等)有可能是什么问题导致的? [[more]](sections/zh-cn/storage.md#Mongodb) -* 什么情况下数据会出现脏数据? 如何避免? [[more]](sections/zh-cn/storage.md#数据一致性) -* redis 与 memcached 的区别? [[more]](sections/zh-cn/storage.md#缓存) +* 备份数据库与 M/S, M/M 等部署方式的区别? [[more]](/sections/zh-cn/storage.md#replication) +* 索引有什么用,大致原理是什么? 设计索引有什么注意点? [[more]](/sections/zh-cn/storage.md#索引) +* Monogdb 连接问题(超时/断开等)有可能是什么问题导致的? [[more]](/sections/zh-cn/storage.md#Mongodb) +* 什么情况下数据会出现脏数据? 如何避免? [[more]](/sections/zh-cn/storage.md#数据一致性) +* redis 与 memcached 的区别? [[more]](/sections/zh-cn/storage.md#缓存) -[阅读更多](sections/zh-cn/storage.md) +[阅读更多](/sections/zh-cn/storage.md) -## [安全](sections/zh-cn/security.md) +## [安全](/sections/zh-cn/security.md) -* [`[Doc]` Crypto (加密)](sections/zh-cn/security.md#crypto) -* [`[Doc]` TLS/SSL](sections/zh-cn/security.md#tlsssl) -* [`[Doc]` HTTPS](sections/zh-cn/security.md#https) -* [`[Point]` XSS](sections/zh-cn/security.md#xss) -* [`[Point]` CSRF](sections/zh-cn/security.md#csrf) -* [`[Point]` 中间人攻击](sections/zh-cn/security.md#中间人攻击) -* [`[Point]` Sql/Nosql 注入](sections/zh-cn/security.md#sqlnosql-注入) +* [`[Doc]` Crypto (加密)](/sections/zh-cn/security.md#crypto) +* [`[Doc]` TLS/SSL](/sections/zh-cn/security.md#tlsssl) +* [`[Doc]` HTTPS](/sections/zh-cn/security.md#https) +* [`[Point]` XSS](/sections/zh-cn/security.md#xss) +* [`[Point]` CSRF](/sections/zh-cn/security.md#csrf) +* [`[Point]` 中间人攻击](/sections/zh-cn/security.md#中间人攻击) +* [`[Point]` Sql/Nosql 注入](/sections/zh-cn/security.md#sqlnosql-注入) **常见问题** -* 加密是如何保证用户密码的安全性? [[more]](sections/zh-cn/security.md#crypto) -* TLS 与 SSL 有什么区别? [[more]](sections/zh-cn/security.md#tlsssl) -* HTTPS 能否被劫持? [[more]](sections/zh-cn/security.md#https) -* XSS 攻击是什么? 有什么危害? [[more]](sections/zh-cn/security.md#xss) -* 过滤 Html 标签能否防止 XSS? 请列举不能的情况? [[more]](sections/zh-cn/security.md#xss) -* CSRF 是什么? 如何防范? [[more]](sections/zh-cn/security.md#csrf) -* 如何避免中间人攻击? [[more]](sections/zh-cn/security.md#中间人攻击) +* 加密是如何保证用户密码的安全性? [[more]](/sections/zh-cn/security.md#crypto) +* TLS 与 SSL 有什么区别? [[more]](/sections/zh-cn/security.md#tlsssl) +* HTTPS 能否被劫持? [[more]](/sections/zh-cn/security.md#https) +* XSS 攻击是什么? 有什么危害? [[more]](/sections/zh-cn/security.md#xss) +* 过滤 Html 标签能否防止 XSS? 请列举不能的情况? [[more]](/sections/zh-cn/security.md#xss) +* CSRF 是什么? 如何防范? [[more]](/sections/zh-cn/security.md#csrf) +* 如何避免中间人攻击? [[more]](/sections/zh-cn/security.md#中间人攻击) -[阅读更多](sections/zh-cn/security.md) +[阅读更多](/sections/zh-cn/security.md) ## 最后 From 92178b3fa385a4064cd52e0d23a0c9508f32a931 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Wed, 24 May 2017 13:07:53 +0800 Subject: [PATCH 47/98] section: zh-cn, fix bad URL --- sections/zh-cn/event-async.md | 10 +++++----- sections/zh-cn/io.md | 18 +++++++++--------- sections/zh-cn/js-basic.md | 10 +++++----- sections/zh-cn/os.md | 2 +- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/sections/zh-cn/event-async.md b/sections/zh-cn/event-async.md index dd882ef..74a7332 100644 --- a/sections/zh-cn/event-async.md +++ b/sections/zh-cn/event-async.md @@ -1,10 +1,10 @@ # 事件/异步 -* [`[Basic]` Promise](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#promise) -* [`[Doc]` Events (事件)](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#events) -* [`[Doc]` Timers (定时器)](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#timers) -* [`[Point]` 阻塞/异步](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#阻塞异步) -* [`[Point]` 并行/并发](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#并行并发) +* [`[Basic]` Promise](/sections/zh-cn/event-async.md#promise) +* [`[Doc]` Events (事件)](/sections/zh-cn/event-async.md#events) +* [`[Doc]` Timers (定时器)](/sections/zh-cn/event-async.md#timers) +* [`[Point]` 阻塞/异步](/sections/zh-cn/event-async.md#阻塞异步) +* [`[Point]` 并行/并发](/sections/zh-cn/event-async.md#并行并发) ## 简述 diff --git a/sections/zh-cn/io.md b/sections/zh-cn/io.md index 0c021b7..d9bf669 100644 --- a/sections/zh-cn/io.md +++ b/sections/zh-cn/io.md @@ -1,12 +1,12 @@ # IO -* [`[Doc]` Buffer](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#buffer) -* [`[Doc]` String Decoder (字符串解码)](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#string-decoder) -* [`[Doc]` Stream (流)](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#stream) -* [`[Doc]` Console (控制台)](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#console) -* [`[Doc]` File System (文件系统)](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#file) -* [`[Doc]` Readline](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#readline) -* [`[Doc]` REPL](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#repl) +* [`[Doc]` Buffer](/sections/zh-cn/io.md#buffer) +* [`[Doc]` String Decoder (字符串解码)](/sections/zh-cn/io.md#string-decoder) +* [`[Doc]` Stream (流)](/sections/zh-cn/io.md#stream) +* [`[Doc]` Console (控制台)](/sections/zh-cn/io.md#console) +* [`[Doc]` File System (文件系统)](/sections/zh-cn/io.md#file) +* [`[Doc]` Readline](/sections/zh-cn/io.md#readline) +* [`[Doc]` REPL](/sections/zh-cn/io.md#repl) # 简述 @@ -285,7 +285,7 @@ int printf(FILE *stream, 要打印的内容) 当你使用 ssh 在远程服务器上运行一个命令的时候, 在服务器上的命令输出虽然也是写入到服务器上 shell 的 stdout, 但是这个远程的 shell 是从 sshd 服务上 fork 出来的, 其 stdout 是继承自 sshd 的一个 fd, 这个 fd 其实是个 socket, 所以最终其实是写入到了一个 socket 中, 通过这个 socket 传输你本地的计算机上的 shell 的 stdout. -如果你理解了上述情况, 那么你也就能理解为什么守护进程需要关闭 stdio, 如果切到后台的守护进程没有关闭 stdio 的话, 那么你在用 shell 操作的过程中, 屏幕上会莫名其妙的多出来一些输出. 此处对应[守护进程](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#守护进程)的 C 实现中的这一段: +如果你理解了上述情况, 那么你也就能理解为什么守护进程需要关闭 stdio, 如果切到后台的守护进程没有关闭 stdio 的话, 那么你在用 shell 操作的过程中, 屏幕上会莫名其妙的多出来一些输出. 此处对应[守护进程](/sections/zh-cn/process.md#守护进程)的 C 实现中的这一段: ```c for (; i < getdtablesize(); ++i) { @@ -301,7 +301,7 @@ console.log(process.stdout.fd); // 1 console.log(process.stderr.fd); // 2 ``` -在上一节中的 [在 IPC 通道建立之前, 父进程与子进程是怎么通信的? 如果没有通信, 那 IPC 是怎么建立的?](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#q-child) 中使用环境变量传递 fd 的方法, 这么看起来就很直白了, 因为传递 fd 其实是直接传递了一个整型数字. +在上一节中的 [在 IPC 通道建立之前, 父进程与子进程是怎么通信的? 如果没有通信, 那 IPC 是怎么建立的?](/sections/zh-cn/process.md#q-child) 中使用环境变量传递 fd 的方法, 这么看起来就很直白了, 因为传递 fd 其实是直接传递了一个整型数字. ### 如何同步的获取用户的输入? diff --git a/sections/zh-cn/js-basic.md b/sections/zh-cn/js-basic.md index b778d42..36010a3 100644 --- a/sections/zh-cn/js-basic.md +++ b/sections/zh-cn/js-basic.md @@ -1,10 +1,10 @@ # Javascript 基础问题 -* [`[Basic]` 类型判断](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#类型判断) -* [`[Basic]` 作用域](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#作用域) -* [`[Basic]` 引用传递](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#引用传递) -* [`[Basic]` 内存释放](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#内存释放) -* [`[Basic]` ES6 新特性](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#es6-新特性) +* [`[Basic]` 类型判断](/sections/zh-cn/js-basic.md#类型判断) +* [`[Basic]` 作用域](/sections/zh-cn/js-basic.md#作用域) +* [`[Basic]` 引用传递](/sections/zh-cn/js-basic.md#引用传递) +* [`[Basic]` 内存释放](/sections/zh-cn/js-basic.md#内存释放) +* [`[Basic]` ES6 新特性](/sections/zh-cn/js-basic.md#es6-新特性) ## 简述 diff --git a/sections/zh-cn/os.md b/sections/zh-cn/os.md index a8d4c31..a8ccf5d 100644 --- a/sections/zh-cn/os.md +++ b/sections/zh-cn/os.md @@ -35,7 +35,7 @@ $ ps -x 23733 ? Ssl 2:53 PM2 v1.1.2: God Daemon ``` -其中为 `?` 的是没有依赖 TTY 的进程, 即[守护进程](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#%E5%AE%88%E6%8A%A4%E8%BF%9B%E7%A8%8B). +其中为 `?` 的是没有依赖 TTY 的进程, 即[守护进程](/sections/zh-cn/process.md#%E5%AE%88%E6%8A%A4%E8%BF%9B%E7%A8%8B). 在 Node.js 中你可以通过 stdio 的 isTTY 来判断当前进程是否处于 TTY (如终端) 的环境. From 8fbc90ee27e9a5551057fa925f20aab937ad1811 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Wed, 24 May 2017 13:09:20 +0800 Subject: [PATCH 48/98] section: process.md, fix bad url --- sections/zh-cn/process.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sections/zh-cn/process.md b/sections/zh-cn/process.md index 559258b..80db4cd 100644 --- a/sections/zh-cn/process.md +++ b/sections/zh-cn/process.md @@ -1,10 +1,10 @@ # 进程 -* [`[Doc]` Process (进程)](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#process) -* [`[Doc]` Child Processes (子进程)](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#child-process) -* [`[Doc]` Cluster (集群)](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#cluster) -* [`[Basic]` 进程间通信](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#进程间通信) -* [`[Basic]` 守护进程](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#守护进程) +* [`[Doc]` Process (进程)](/sections/zh-cn/process.md#process) +* [`[Doc]` Child Processes (子进程)](/sections/zh-cn/process.md#child-process) +* [`[Doc]` Cluster (集群)](/sections/zh-cn/process.md#cluster) +* [`[Basic]` 进程间通信](/sections/zh-cn/process.md#进程间通信) +* [`[Basic]` 守护进程](/sections/zh-cn/process.md#守护进程) ## 简述 From ee51b68bd0ad22f43ebaf3ae0a9ef5bd7eb201fe Mon Sep 17 00:00:00 2001 From: Lellansin Date: Mon, 26 Jun 2017 14:43:06 +0800 Subject: [PATCH 49/98] translate: os.md (#34) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [翻译][os.md] 完成100% * [校对][os.md] 完成 50% * [校对][os.md] 完成 --- sections/en-us/os.md | 368 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 367 insertions(+), 1 deletion(-) diff --git a/sections/en-us/os.md b/sections/en-us/os.md index 30ac190..25c0700 100644 --- a/sections/en-us/os.md +++ b/sections/en-us/os.md @@ -1,8 +1,374 @@ # OS * `[Doc]` TTY -* `[Doc]` OS +* `[Doc]` OS (Operating System) * `[Doc]` Command Line Options * `[Basic]` Load * `[Point]` CheckList * `[Basic]` Indicators + +## TTY + +"TTY" means "teletype", a typewriter, and "pty" is "pseudo-teletype", a pseudo typewriter. In Unix, `/dev/tty*` refers to any device that acts as a typewriter, such as the terminal. + +You can view the currently logged in user through the `w` command, and you'll find a new tty every time you login to a window. + +```shell +$ w + 11:49:43 up 482 days, 19:38, 3 users, load average: 0.03, 0.08, 0.07 +USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT +dev pts/0 10.0.128.252 10:44 1:01m 0.09s 0.07s -bash +dev pts/2 10.0.128.252 11:08 2:07 0.17s 0.14s top +root pts/3 10.0.240.2 11:43 7.00s 0.04s 0.00s w +``` + +Using the ps command to see process information, there is also information about tty: + +```shell +$ ps -x + PID TTY STAT TIME COMMAND + 5530 ? S 0:00 sshd: dev@pts/3 + 5531 pts/3 Ss+ 0:00 -bash +11296 ? S 0:00 sshd: dev@pts/4 +11297 pts/4 Ss 0:00 -bash +13318 pts/4 R+ 0:00 ps -x +23733 ? Ssl 2:53 PM2 v1.1.2: God Daemon +``` + +The process marked with `?` is not depending on TTY, which is called [Daemon](/sections/en-us/process.md#%E5%AE%88%E6%8A%A4%E8%BF%9B%E7%A8%8B). + +In Node.js, you can use stdio's isTTY attribute to determine whether the current process is in a TTY (such as terminal) environment. + +```shell +$ node -p -e "Boolean(process.stdout.isTTY)" +true +$ node -p -e "Boolean(process.stdout.isTTY)" | cat +false +``` + +## OS + +You can get some auxiliary functions to the basic information of the current system through the OS module. + +|Attribute|Description| +|---|---| +|os.EOL|Returns the current system's `End Of Line`, based on the current system| +|os.arch()|Returns the CPU architecture of the current system, such as `'x86'` or `'x64'`| +|os.constants|Returns system constants| +|os.cpus()|Returns the information for each kernel of the CPU| +|os.endianness()|Returns byte order of CPU, return `BE` if it is big endian, return `LE` if it is little endian.| +|os.freemem()|Returns the size of the system's free memory, in bytes| +|os.homedir()|Returns the root directory of the current user| +|os.hostname()|Returns the hostname of the current system| +|os.loadavg()|Returns load information| +|os.networkInterfaces()|Returns the NIC information (similar to `ifconfig`)| +|os.platform()|Returns the platform information specified at compile time, such as `win32`, `linux`, same as `process.platform()`| +|os.release()|Returns the distribution version number of the operating system| +|os.tmpdir()|Returns the default temporary folder of the system| +|os.totalmem()|Returns the total memory size (the same as the memory bar size)| +|os.type()|Returns the name of the system according to [`uname`](https://en.wikipedia.org/wiki/Uname#Examples)| +|os.uptime()|Returns the running time of the system, in seconds| +|os.userInfo([options])|Returns the current user information| + +> What's the difference between the line breaks (EOL) of different operating systems? + +End of line (EOL) is the same as newline, line ending and line break. + +And it's usually composed of line feed (LF, `\n`) and carriage return (CR, `\r`). Here are some common cases: + +|Symbol|System| +|---|---| +|LF|In Unix or Unix compatible systems (GNU/Linux, AIX, Xenix, Mac OS X, ...), BeOS, Amiga, RISC OS| +|CR+LF|MS-DOS, Microsoft Windows, Most non Unix systems| +|CR|Apple II family, Mac OS to version 9| + +If you don't understand the cross-system compatibility of EOL, you might have problems dealing with the line segmentation/row statistics of the file. + +### OS Constants + +* Signal Constants, such as `SIGHUP`, `SIGKILL`, etc. +* POSIX Error Constants, such as `EACCES`, `EADDRINUSE`, etc. +* Windows Specific Error Constants, such as `WSAEACCES`, `WSAEBADF`, etc. +* libuv Constants, only `UV_UDP_REUSEADDR`. + + +## Path + +The built-in path in Node.js is a module for handling path problems, but as we all know, the paths are irreconcilable in different operating systems. + +### Windows vs. POSIX + +|POSIX|Value|Windows|Value| +|---|---|---|---| +|path.posix.sep|`'/'`|path.win32.sep|`'\\'`| +|path.posix.normalize('/foo/bar//baz/asdf/quux/..')|`'/foo/bar/baz/asdf'`|path.win32.normalize('C:\\temp\\\\foo\\bar\\..\\')|`'C:\\temp\\foo\\'`| +|path.posix.basename('/tmp/myfile.html')|`'myfile.html'`|path.win32.basename('C:\\temp\\myfile.html')|`'myfile.html'`| +|path.posix.join('/asdf', '/test.html')|`'/asdf/test.html'`|path.win32.join('/asdf', '/test.html')|`'\\asdf\\test.html'`| +|path.posix.relative('/root/a', '/root/b')|`'../b'`|path.win32.relative('C:\\a', 'c:\\b')|`'..\\b'` +|path.posix.isAbsolute('/baz/..')|`true`|path.win32.isAbsolute('C:\\foo\\..')|`true`| +|path.posix.delimiter|`':'`|path.win32.delimiter|`','`| +|process.env.PATH|`'/usr/bin:/bin'`|process.env.PATH|`C:\Windows\system32;C:\Program Files\node\'`| +|PATH.split(path.posix.delimiter)|`['/usr/bin', '/bin']`|PATH.split(path.win32.delimiter)|`['C:\\Windows\\system32', 'C:\\Program Files\\node\\']`| + + +After looking at the table, you should realize that when under a certain platform, the `path` module is actually the method of the corresponding platform. For example, I uses Mac here, so: + +```javascript +const path = require('path'); +console.log(path.basename === path.posix.basename); // true +``` + +If you are on one of these platforms, but you need to deal with the path of another platform, you need to be aware of this cross platform issue. + +### path Object + +on POSIX: + +```javascript +path.parse('/home/user/dir/file.txt') +// Returns: +// { +// root : "/", +// dir : "/home/user/dir", +// base : "file.txt", +// ext : ".txt", +// name : "file" +// } +``` + +```javascript +┌─────────────────────┬────────────┐ +│ dir │ base │ +├──────┬ ├──────┬─────┤ +│ root │ │ name │ ext │ +" / home/user/dir / file .txt " +└──────┴──────────────┴──────┴─────┘ +``` + +on Windows: + +```javascript +path.parse('C:\\path\\dir\\file.txt') +// Returns: +// { +// root : "C:\\", +// dir : "C:\\path\\dir", +// base : "file.txt", +// ext : ".txt", +// name : "file" +// } +``` + +```javascript +┌─────────────────────┬────────────┐ +│ dir │ base │ +├──────┬ ├──────┬─────┤ +│ root │ │ name │ ext │ +" C:\ path\dir \ file .txt " +└──────┴──────────────┴──────┴─────┘ +``` + +### path.extname(path) + +|case|return| +|---|---| +|path.extname('index.html')|`'.html'`| +|path.extname('index.coffee.md')|`'.md'`| +|path.extname('index.')|`'.'`| +|path.extname('index')|`''`| +|path.extname('.index')|`''`| + + +## Command Line Options + +Command Line Options is some documentation on the use of CLI. There are 4 main ways of using CLI: + +* node [options] [v8 options] [script.js | -e "script"] [arguments] +* node debug [script.js | -e "script" | :] … +* node --v8-options +* Starts REPL environment without parameters directly + +### Options + +|Parameter|Introduction| +|---|---| +|-v, --version|Shows the version of current node| +|-h, --help|Shows help documentation| +|-e, --eval "script"|The parameter string is executed as code +|-p, --print "script"|Prints the return value of `-e` +|-c, --check|Checks syntax without executing the code +|-i, --interactive|Opens REPL mode even if stdin is not the terminal +|-r, --require module|`require` the Specified module before startup +|--no-deprecation|Closes the scrap module warning +|--trace-deprecation|Prints stack trace information for an obsolete module +|--throw-deprecation|Throws errors while executing an obsolete module +|--no-warnings|Ignores warnings (including obsolete warnings) +|--trace-warnings|Prints warning stack (including discarded modules) +|--trace-sync-io|As soon as the asynchronous I/O is detected at the beginning of the event loop, the stack trace will be printed +|--zero-fill-buffers|Zero-fill **Buffer** and **SlowBuffer** +|--preserve-symlinks|Instructs the module loader to save symbolic links when parsing and caching modules +|--track-heap-objects|Tracks the allocation of heap objects for heap snapshot +|--prof-process|Using the V8 option `--prof` to generate the Profilling Report +|--v8-options|Shows the V8 command line options +|--tls-cipher-list=list|Specifies the list of alternative default TLS encryption devices +|--enable-fips|Turns on FIPS-compliant crypto at startup +|--force-fips|Enforces FIPS-compliant at startup +|--openssl-config=file|Loads the OpenSSL configuration file at startup +|--icu-data-dir=file|Specifies the loading path of ICU data + +### Environment Variable + +|Environment variable|Introduction| +|----|----| +|`NODE_DEBUG=module[,…]`|Specifies the list of core modules to print debug information +|`NODE_PATH=path[:…]`|Specifies prefix list of the module search directory +|`NODE_DISABLE_COLORS=1`|Closes the color display for REPL +|`NODE_ICU_DATA=file`|ICU (Intl, object) data path +|`NODE_REPL_HISTORY=file`|Path of persistent storage REPL history file +|`NODE_TTY_UNSAFE_ASYNC=1`|When set to 1, The stdio operation will proceed synchronously (such as console.log becomes synchronous) +|`NODE_EXTRA_CA_CERTS=file`|Specifies an extra certificate path for CA (such as VeriSign) + +## Load + +Load is an important concept to measure the running state of the server. Through the load situation, we can know whether the server is idle, good, busy or about to crash. + +Typically, the load we want to look at is the CPU load, for more information you can read this blog: [Understanding Linux CPU Load](http://blog.scoutapp.com/articles/2009/07/31/understanding-load-averages). + +To get the current system load, you can use `uptime`, `top` command in terminal or `os.loadavg()` in Node.js: + +``` +load average: 0.09, 0.05, 0.01 +``` + +Here are the average load on the system of the last 1 minutes, 5 minutes, 15 minutes. When one of the CPU core is working in full load, the value of load will be 1, so the value represents how many CPU cores are in full load. + +In Node.js, the CPU load of a single process can be viewed using the [pidusage](https://github.com/soyuka/pidusage) module. + +In addition to the CPU load, the server (prefer maintain) needs to know about the network load, disk load, and so on. + +## CheckList + +> A police officer sees a drunken man intently searching the ground near a lamppost and asks him the goal of his quest. The inebriate replies that he is looking for his car keys, and the officer helps for a few minutes without success then he asks whether the man is certain that he dropped the keys near the lamppost. +“No,” is the reply, “I lost the keys somewhere across the street.” “Why look here?” asks the surprised and irritated officer. “The light is much better here,” the intoxicated man responds with aplomb. + +When it comes to checking server status, many server-side friends only know how to use the `top` command. In fact, the situation is the same as the jokes above, because `top` is the brightest street lamp for them. + +For server-side programmers, the full server-side checklist is the [USE Method](http://www.brendangregg.com/USEmethod/use-linux.html) described in the second chapter of [《Systems Performance》](https://www.amazon.cn/%E5%9B%BE%E4%B9%A6/dp/0133390098). + +The USE Method provides a strategy for performing a complete check of system health, identifying common bottlenecks and errors. For each system resource, metrics for utilization, saturation and errors are identified and checked. Any issues discovered are then investigated using further strategies. + +This is an example USE-based metric list for Linux operating systems (eg, Ubuntu, CentOS, Fedora). This is primarily intended for system administrators of the physical systems, who are using command line tools. Some of these metrics can be found in remote monitoring tools. + +### Physical Resources + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
componenttypemetric
CPUutilizationsystem-wide: vmstat 1, "us" + "sy" + "st"; sar -u, sum fields except "%idle" and "%iowait"; dstat -c, sum fields except "idl" and "wai"; per-cpu: mpstat -P ALL 1, sum fields except "%idle" and "%iowait"; sar -P ALL, same as mpstat; per-process: top, "%CPU"; htop, "CPU%"; ps -o pcpu; pidstat 1, "%CPU"; per-kernel-thread: top/htop ("K" to toggle), where VIRT == 0 (heuristic). [1]
CPUsaturationsystem-wide: vmstat 1, "r" > CPU count [2]; sar -q, "runq-sz" > CPU count; dstat -p, "run" > CPU count; per-process: /proc/PID/schedstat 2nd field (sched_info.run_delay); perf sched latency (shows "Average" and "Maximum" delay per-schedule); dynamic tracing, eg, SystemTap schedtimes.stp "queued(us)" [3]
CPUerrorsperf (LPE) if processor specific error events (CPC) are available; eg, AMD64's "04Ah Single-bit ECC Errors Recorded by Scrubber" [4]
Memory capacityutilizationsystem-wide: free -m, "Mem:" (main memory), "Swap:" (virtual memory); vmstat 1, "free" (main memory), "swap" (virtual memory); sar -r, "%memused"; dstat -m, "free"; slabtop -s c for kmem slab usage; per-process: top/htop, "RES" (resident main memory), "VIRT" (virtual memory), "Mem" for system-wide summary
Memory capacitysaturationsystem-wide: vmstat 1, "si"/"so" (swapping); sar -B, "pgscank" + "pgscand" (scanning); sar -W; per-process: 10th field (min_flt) from /proc/PID/stat for minor-fault rate, or dynamic tracing [5]; OOM killer: dmesg | grep killed
Memory capacityerrorsdmesg for physical failures; dynamic tracing, eg, SystemTap uprobes for failed malloc()s
Network Interfacesutilizationsar -n DEV 1, "rxKB/s"/max "txKB/s"/max; ip -s link, RX/TX tput / max bandwidth; /proc/net/dev, "bytes" RX/TX tput/max; nicstat "%Util" [6]
Network Interfacessaturationifconfig, "overruns", "dropped"; netstat -s, "segments retransmited"; sar -n EDEV, *drop and *fifo metrics; /proc/net/dev, RX/TX "drop"; nicstat "Sat" [6]; dynamic tracing for other TCP/IP stack queueing [7]
Network Interfaceserrorsifconfig, "errors", "dropped"; netstat -i, "RX-ERR"/"TX-ERR"; ip -s link, "errors"; sar -n EDEV, "rxerr/s" "txerr/s"; /proc/net/dev, "errs", "drop"; extra counters may be under /sys/class/net/...; dynamic tracing of driver function returns 76]
Storage device I/Outilizationsystem-wide: iostat -xz 1, "%util"; sar -d, "%util"; per-process: iotop; pidstat -d; /proc/PID/sched "se.statistics.iowait_sum"
Storage device I/Osaturationiostat -xnz 1, "avgqu-sz" > 1, or high "await"; sar -d same; LPE block probes for queue length/latency; dynamic/static tracing of I/O subsystem (incl. LPE block probes)
Storage device I/Oerrors/sys/devices/.../ioerr_cnt; smartctl; dynamic/static tracing of I/O subsystem response codes [8]
Storage capacityutilizationswap: swapon -s; free; /proc/meminfo "SwapFree"/"SwapTotal"; file systems: "df -h"
Storage capacitysaturationnot sure this one makes sense - once it's full, ENOSPC
Storage capacityerrorsstrace for ENOSPC; dynamic tracing for ENOSPC; /var/log/messages errs, depending on FS
Storage controllerutilizationiostat -xz 1, sum devices and compare to known IOPS/tput limits per-card
Storage controllersaturationsee storage device saturation, ...
Storage controllererrorssee storage device errors, ...
Network controllerutilizationinfer from ip -s link (or /proc/net/dev) and known controller max tput for its interfaces
Network controllersaturationsee network interface saturation, ...
Network controllererrorssee network interface errors, ...
CPU interconnectutilizationLPE (CPC) for CPU interconnect ports, tput / max
CPU interconnectsaturationLPE (CPC) for stall cycles
CPU interconnecterrorsLPE (CPC) for whatever is available
Memory interconnectutilizationLPE (CPC) for memory busses, tput / max; or CPI greater than, say, 5; CPC may also have local vs remote counters
Memory interconnectsaturationLPE (CPC) for stall cycles
Memory interconnecterrorsLPE (CPC) for whatever is available
I/O interconnectutilizationLPE (CPC) for tput / max if available; inference via known tput from iostat/ip/...
I/O interconnectsaturationLPE (CPC) for stall cycles
I/O interconnecterrorsLPE (CPC) for whatever is available
+ + +### Software Resources + + + + + + + + + + + + + + + + + + + + +
componenttypemetric
Kernel mutexutilizationWith CONFIG_LOCK_STATS=y, /proc/lock_stat "holdtime-totat" / "acquisitions" (also see "holdtime-min", "holdtime-max") [8]; dynamic tracing of lock functions or instructions (maybe)
Kernel mutexsaturationWith CONFIG_LOCK_STATS=y, /proc/lock_stat "waittime-total" / "contentions" (also see "waittime-min", "waittime-max"); dynamic tracing of lock functions or instructions (maybe); spinning shows up with profiling (perf record -a -g -F 997 ..., oprofile, dynamic tracing)
Kernel mutexerrorsdynamic tracing (eg, recusive mutex enter); other errors can cause kernel lockup/panic, debug with kdump/crash
User mutexutilizationvalgrind --tool=drd --exclusive-threshold=... (held time); dynamic tracing of lock to unlock function time
User mutexsaturationvalgrind --tool=drd to infer contention from held time; dynamic tracing of synchronization functions for wait time; profiling (oprofile, PEL, ...) user stacks for spins
User mutexerrorsvalgrind --tool=drd various errors; dynamic tracing of pthread_mutex_lock() for EAGAIN, EINVAL, EPERM, EDEADLK, ENOMEM, EOWNERDEAD, ...
Task capacityutilizationtop/htop, "Tasks" (current); sysctl kernel.threads-max, /proc/sys/kernel/threads-max (max)
Task capacitysaturationthreads blocking on memory allocation; at this point the page scanner should be running (sar -B "pgscan*"), else examine using dynamic tracing
Task capacityerrors"can't fork()" errors; user-level threads: pthread_create() failures with EAGAIN, EINVAL, ...; kernel: dynamic tracing of kernel_thread() ENOMEM
File descriptorsutilizationsystem-wide: sar -v, "file-nr" vs /proc/sys/fs/file-max; dstat --fs, "files"; or just /proc/sys/fs/file-nr; per-process: ls /proc/PID/fd | wc -l vs ulimit -n
File descriptorssaturationdoes this make sense? I don't think there is any queueing or blocking, other than on memory allocation.
File descriptorserrorsstrace errno == EMFILE on syscalls returning fds (eg, open(), accept(), ...).
+ +#### ulimit + +ulimit is used to manage user access to system resources. + +``` +-a All current limits are reported +-c The maximum size of core files created, take block as a unit +-d The maximum size of a process's data segment, take KB as a unit +-f The maximum size of files written by the shell and its children, take block as a unit +-H Set a hard limit to the resource, that is the limits set by the administrator +-m The maximum resident set size, take KB as a unit +-n The maximum number of open file descriptors at the same time +-p The pipe size in 512-byte blocks, take 512-byte as a unit +-s The maximum stack size, take KB as a unit +-S Set flexible limits for resources +-t The maximum amount of cpu time, in seconds +-u The maximum number of processes available to a single user +-v The maximum amount of virtual memory available to the shell, take KB as a unit +``` + +For example: + +``` +$ ulimit -a +core file size (blocks, -c) 0 +data seg size (kbytes, -d) unlimited +scheduling priority (-e) 0 +file size (blocks, -f) unlimited +pending signals (-i) 127988 +max locked memory (kbytes, -l) 64 +max memory size (kbytes, -m) unlimited +open files (-n) 655360 +pipe size (512 bytes, -p) 8 +POSIX message queues (bytes, -q) 819200 +real-time priority (-r) 0 +stack size (kbytes, -s) 8192 +cpu time (seconds, -t) unlimited +max user processes (-u) 4096 +virtual memory (kbytes, -v) unlimited +file locks (-x) unlimited +``` + +Note: open socket and other resources are also kind of file descriptor, if `ulimit -n` is too small, not only will you not open the file, but also can not establish a socket link. \ No newline at end of file From 4e663585831192ac2bdd275370d9274c730c17e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E6=A0=91=40=E9=98=BF=E9=87=8C?= Date: Thu, 13 Jul 2017 11:06:59 +0800 Subject: [PATCH 50/98] fix: correct `CPS` to `CSP` (#36) --- sections/zh-cn/security.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sections/zh-cn/security.md b/sections/zh-cn/security.md index fac89e3..a7ad377 100644 --- a/sections/zh-cn/security.md +++ b/sections/zh-cn/security.md @@ -87,9 +87,9 @@ alert('xss')"> ``` -### CPS 策略 +### CSP 策略 -在百般无奈, 没有统一解决方案的情况下, 厂商们推出了 CPS 策略. +在百般无奈, 没有统一解决方案的情况下, 厂商们推出了 CSP 策略. 以 Node.js 为例, 计算脚本的 hashes 值: ``` From 1deb5910277bf439f1d05c6e31cec00cbf09d9c2 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Tue, 25 Jul 2017 23:06:34 +0800 Subject: [PATCH 51/98] translate: os.md (#37) --- sections/en-us/io.md | 391 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 384 insertions(+), 7 deletions(-) diff --git a/sections/en-us/io.md b/sections/en-us/io.md index e9aaa72..3d6ca6c 100644 --- a/sections/en-us/io.md +++ b/sections/en-us/io.md @@ -1,9 +1,386 @@ # IO -* `[Doc]` Buffer -* `[Doc]` String Decoder -* `[Doc]` Stream -* `[Doc]` Console -* `[Doc]` File System -* `[Doc]` Readline -* `[Doc]` REPL +* [`[Doc]` Buffer](/sections/zh-cn/io.md#buffer) +* [`[Doc]` String Decoder](/sections/zh-cn/io.md#string-decoder) +* [`[Doc]` Stream](/sections/zh-cn/io.md#stream) +* [`[Doc]` Console](/sections/zh-cn/io.md#console) +* [`[Doc]` File System](/sections/zh-cn/io.md#file) +* [`[Doc]` Readline](/sections/zh-cn/io.md#readline) +* [`[Doc]` REPL](/sections/zh-cn/io.md#repl) + +# Brief introduction + +Node.js was famous as handling IO-intensive business. Then here are the questions, What do you really know about IO? What is it called IO intensive business? + +## Buffer + +Buffer is the class to handle binary data in Node.js, IO-related operations (network / file, etc.) are all based on Buffer. An instance of the Buffer class is very similar to an array of integers, ***but its size is fixed***, And its original memory space is allocated outside the V8 stack. After the instance of the Buffer class is created, the memory size occupied by it can no longer be adjusted. + +`New Buffer ()` interface was deprecated from Node.js v6.x, The reason is that different types of parameters will return different types of Buffer objects, So when the developer does not correctly verify the parameters or does not correctly initialize the contents of the Buffer object, it will inadvertently introduce security and reliability problems to the code. + +Interface |use +---|--- +Buffer.from()|Creates a Buffer object based on the existing data +Buffer.alloc()|Creates an initialized Buffer object +Buffer.allocUnsafe()|Creates an uninitialized Buffer object + +### TypedArray + +After introducing TypedArray in ES6, Node.js modified the implementation of the original Buffer to Uint8Array in TypedArray, thus enhancing the performance. + +Here are the things you need to know when using it: + +```javascript +const arr = new Uint16Array(2); +arr[0] = 5000; +arr[1] = 4000; + +const buf1 = Buffer.from(arr); // Copy the buffer +const buf2 = Buffer.from(arr.buffer); // Share memory with the array + +console.log(buf1); +// Output: , The copied buffer only contains to element +console.log(buf2); +// Output: + +arr[1] = 6000; +console.log(buf1); +// Output: +console.log(buf2); +// Output: +``` + +## String Decoder + +String Decoder is a module to decode buffers to strings, as a supplement to Buffer.toString, it supports multi-byte UTF-8 and UTF-16 characters. Such as: + +```javascript +const StringDecoder = require('string_decoder').StringDecoder; +const decoder = new StringDecoder('utf8'); + +const cent = Buffer.from([0xC2, 0xA2]); +console.log(decoder.write(cent)); // ¢ + +const euro = Buffer.from([0xE2, 0x82, 0xAC]); +console.log(decoder.write(euro)); // € +``` + +Of course can be done step by step. + +```javascript +const StringDecoder = require('string_decoder').StringDecoder; +const decoder = new StringDecoder('utf8'); + +decoder.write(Buffer.from([0xE2])); +decoder.write(Buffer.from([0x82])); +console.log(decoder.end(Buffer.from([0xAC]))); // € +``` + +## Stream + +Built-in `stream` module in Node.js is the basis of multiple core modules. But stream is a popular programming method very early. We can use the more familiar C language to see stream operation: + +```c + +int copy(const char *src, const char *dest) +{ + FILE *fpSrc, *fpDest; + char buf[BUF_SIZE] = {0}; + int lenSrc, lenDest; + + // open src file + if ((fpSrc = fopen(src, "r")) == NULL) + { + printf("file '%s' can not be opened\n", src); + return FAILURE; + } + + // open dest file + if ((fpDest = fopen(dest, "w")) == NULL) + { + printf("file '%s' can not be opened\n", dest); + fclose(fpSrc); + return FAILURE; + } + + // Read the BUF_SIZE data from src to buf + while ((lenSrc = fread(buf, 1, BUF_SIZE, fpSrc)) > 0) + { + // write data in buf to dest + if ((lenDest = fwrite(buf, 1, lenSrc, fpDest)) != lenSrc) + { + printf("write file '%s' failed\n", dest); + fclose(fpSrc); + fclose(fpDest); + return FAILURE; + } + // clean buf when success + memset(buf, 0, BUF_SIZE); + } + + // close file + fclose(fpSrc); + fclose(fpDest); + return SUCCESS; +} +``` + +The application scenario is simple, When you need to copy a 20G file, if you have 20G of data read into memory at once, your memory may not be enough, or seriously affect performance. But if you use a 1MB size cache (buf), Read 1Mb, then write 1Mb, Then no matter how much of this file will only take up 1Mb of memory. + +In Node.js, the principle is similar to the above C code, But its IO operation is implemented through libuv and EventEmitter with asynchronous features. You can use `|` to feel the stream operation in linux/unix. + +### Type of Stream + + +Class| Scenario |Overrided method +---|---|--- +[Readable](https://github.com/substack/stream-handbook#readable-streams)|Read only|_read +[Writable](https://github.com/substack/stream-handbook#writable-streams)|Write only|_write +[Duplex](https://github.com/substack/stream-handbook#duplex)|Read and write|_read, _write +[Transform](https://github.com/substack/stream-handbook#transform)|Operate the writed data, and then read out the results|_transform, _flush + + +### Object mode + +The stream created by the Node API can only manipulate strings or buffer objects. But the implementation of the stream can be based on other types of Javascript(Except for null, it has a special meaning in stream). This stream is in the "object mode (objectMode)". +You can generate an object-mode stream by providing the `objectMode` parameter when creating a stream object. It is not safe to attempt to convert an existing stream to object mode. + +### Buffer + +The buffer of stream in Node.js, using the copy file code at the begining that written in C Languange as a template to discuss, (Despite the difference with asynchronous) is reading data from `src` to` buf`, and not directly written to `dest`, but first placed in a relatively large buffer, Waiting for be written into (comsumed) `dest` . That is, with the help of the buffer we can achieve Read and write separation. + +Both the Readable and Writable streams store the data in an internal buffer. The buffers can be accessed by `writable._writableState.getBuffer ()` and `readable._readableState.buffer` respectively. The size of the buffer is specified by the `highWaterMark` flag when creating the stream, for `objectMode` stream, this flag indicates the number of objects that can be accommodated. + +#### Readable stream + +When a readable instance calls the `stream.push ()` method, the data will be pushed into the buffer. If the data is not consumed, That is, If you call the `stream.read ()` method to read the words, the data will remain in the buffer queue. When the data in the buffer reaches the threshold specified by `highWaterMark`, The readable stream will stop drawing data from the bottom until the current buffered report is successfully consumed. + +#### Writable stream + +The data is written to the buffer of the writable stream when a writable.write (chunk) is kept on a writable instance. If the buffer amount of the current buffer is less than the value set by `highWaterMark`, Calling the writable.write () method will return true (indicating that the data has been written to the buffer), Otherwise, the write method will return false when the amount of data buffered reaches the threshold and the data can not be written to the buffer, then you can continue to call write to write Until the drain event is triggered. + +```javascript +// Write the data to the supplied writable stream one million times. +// Be attentive to back-pressure. +function writeOneMillionTimes(writer, data, encoding, callback) { + let i = 1000000; + write(); + function write() { + var ok = true; + do { + i--; + if (i === 0) { + // last time! + writer.write(data, encoding, callback); + } else { + // see if we should continue, or wait + // don't pass the callback, because we're not done yet. + ok = writer.write(data, encoding); + } + } while (i > 0 && ok); + if (i > 0) { + // had to stop early! + // write some more once it drains + writer.once('drain', write); + } + } +} +``` + +#### Duplex and Transform + +Duplex stream and the Transform stream are both readable and writable simultaneously, They will maintain two internal buffer respectively, corresponding to read and write, so that you can allow both sides to operate at the same time independently, thus to maintain efficient data flow. Such as net.Socket is a Duplex stream, The Readable side allows you to get data from the socket and consume data, while the Writable side allows you to write data to it. The speed of data writing is likely to be different from the speed of consumption, so it is important to operate and buffer both ends independently. + +### pipe + +The `.pipe()` method of stream appends a writable stream to a readable stream while switching the writable stream to stream mode, and push all the data to the writable stream. In the process of passing data in the pipe, `objectMode` is passing by references, while non-`objectMode` is passing by value. + +The main purpose of the pipe method is to buffer the flow of data to an acceptable level, so that the difference between the different data sources won't cause the memory to be filled. For more details about pipe, see David Cai's [Analyzes the implementation of pipe in Node.js by source code](https://cnodejs.org/topic/56ba030271204e03637a3870) + +## Console + +[Generally console.log is asynchronous, unless you use `new Console(stdout[, stderr])` to specify a file as a destination](https://nodejs.org/dist/latest-v6.x/docs/api/console.html#console_asynchronous_vs_synchronous_consoles). However, mostly it looks like this ([6.x source code](https://github.com/nodejs/node/blob/v6.x/lib/console.js#L42)): + +```javascript +// As of v8 5.0.71.32, the combination of rest param, template string +// and .apply(null, args) benchmarks consistently faster than using +// the spread operator when calling util.format. +Console.prototype.log = function(...args) { + this._stdout.write(`${util.format.apply(null, args)}\n`); +}; +``` + +Refering to the following code if you want to implement a console.log by yourself: + +```javascript +let print = (str) => process.stdout.write(str + '\n'); + +print('hello world'); +``` + +Note: The code does not handle multiple arguments, nor does it handle placeholders (the function of util.format). + +### The console.log.bind(console) problem + +```javascript +// From https://github.com/nodejs/node/blob/v6.x/lib/console.js +function Console(stdout, stderr) { + // ... init ... + + // bind the prototype functions to this Console instance + var keys = Object.keys(Console.prototype); + for (var v = 0; v < keys.length; v++) { + var k = keys[v]; + this[k] = this[k].bind(this); + } +} +``` + +## File + +“Everything is a file” is one of the basic philosophy of Unix/Linux, Not only normal files, but also directory, character device, block device, socket, and so on are all treated as files in Unix/Linux, that is, the operating objects of these resources are all fd (file descriptor), they can be read and written through the same set of system call. You can use ulimit to manage the fd resources in linux. + +Node.js encapsulates the collection of standard POSIX file I / O operations. The module can be loaded by require ('fs'). All the methods in the module have both asynchronous execution and synchronous execution. You can get a file's file descriptor via fs.open. + +### Encoding + +// TODO + +Supports for UTF8, GBK, es6 encoding, how to calculate the length of a Chinese character + +BOM + +### stdio + +stdio (standard input output), includes stdin, stdout and stderr. Corresponding to `process.stdin` (Readable),` process.stdout` (Writable) and `process.stderr` (Writable) respectively in Node.js. + +The output function is the first function that everyone needs to learn when learning a programming language. Such as `printf("hello, world!");` of C language, `print 'hello, world!'` of python/ruby and `console.log('hello, world!');` in Javascript. + +Here is the implementation of such an output function in the C language pseudo-code: + +```c +int printf(FILE *stream, The content to be printed) +{ + // ... + + // 1. Apply for a temporary memory space + char *s = malloc(4096); + + // 2. Handle the contents of the print, the value stored in the s + // ... + + // 3. Write the contents of s into the stream + fwrite(s, stream); + + // 4. Release temporary space + free(s); + + // ... +} +``` + +What we need to know is step 3, where stream refers to stdout (output stream). In fact, when running an application on the shell, the first operation of the shell is fork the current process (So, if you see the process you started from the shell through ps, its parent process pid is the current shell pid), in this process your current application process also inherited the shell stdio, so when you write the data to stdout in the current process, it is also written to the shell stdout, that is the current shell. + +So it is the input, The current process inherits the shell's stdin, So when you read data from stdin, in fact, it's to get the data you enter in the shell. (PS: shell can be cmd, powershell in windows, or bash and zsh in linux) + +When using ssh to run a command on a remote server, Although the command output on the server is also written to the shell on the server stdout, but the remote shell is forked from the sshd service, its stdout is a fd inherited from sshd, so the fd is actually a socket, and the data is actually written to a socket in the end, then being sent to the shell stdout on your local computer through the socket. + +If you understand the things we mentioned above, then you can understand why the daemon needs to close stdio, if the daemon that is cut into the background does not close stdio, then when you using the shell to do some operation, the screen will come out some inexplicably output. Here is the code that written in C language in [daemon](/sections/zh-cn/process.md#daemon): + +```c +for (; i < getdtablesize(); ++i) { + close(i); // close fd +} +``` + +fd in Linux/unix was designed as an integer number starts from 0. You can try running the following code to view it. + +``` +console.log(process.stdin.fd); // 0 +console.log(process.stdout.fd); // 1 +console.log(process.stderr.fd); // 2 +``` + +So it looks very straightforward for the method that using the environment variable to pass fd mentioned in the previous section: [How did the parent process communicate with the child process before the IPC channel was established? How did the IPC build if there was no communication?](/sections/zh-cn/process.md#q-child), because the transmited fd is actually passed an integer number. + +### How to get user input synchronizely? + +If you already understood the content above, Getting the user's input is actually reading Node.js process in the input stream (ie process.stdin stream) data in Node.js. + +And to read synchronously, it is not using the asynchronous read interface, but with the synchronous readSync interface to read the stdin data. The following comes from the Almighty stackoverflow: + +```javascript +/* + * http://stackoverflow.com/questions/3430939/node-js-readsync-from-stdin + * @mklement0 + */ +var fs = require('fs'); + +var BUFSIZE = 256; +var buf = new Buffer(BUFSIZE); +var bytesRead; + +module.exports = function() { + var fd = ('win32' === process.platform) ? process.stdin.fd : fs.openSync('/dev/stdin', 'rs'); + bytesRead = 0; + + try { + bytesRead = fs.readSync(fd, buf, 0, BUFSIZE); + } catch (e) { + if (e.code === 'EAGAIN') { // 'resource temporarily unavailable' + // Happens on OS X 10.8.3 (not Windows 7!), if there's no + // stdin input - typically when invoking a script without any + // input (for interactive stdin input). + // If you were to just continue, you'd create a tight loop. + console.error('ERROR: interactive stdin input not supported.'); + process.exit(1); + } else if (e.code === 'EOF') { + // Happens on Windows 7, but not OS X 10.8.3: + // simply signals the end of *piped* stdin input. + return ''; + } + throw e; // unexpected exception + } + + if (bytesRead === 0) { + // No more stdin input available. + // OS X 10.8.3: regardless of input method, this is how the end + // of input is signaled. + // Windows 7: this is how the end of input is signaled for + // *interactive* stdin input. + return ''; + } + // Process the chunk read. + + var content = buf.toString(null, 0, bytesRead - 1); + + return content; +}; +``` + +## Readline + +The `readline` module provides an interface for reading a row from a stream of Readble (for example, process.stdin). Of course, you can also use it to read the file or net, http stream, for example: + +```javascript +const readline = require('readline'); +const fs = require('fs'); + +const rl = readline.createInterface({ + input: fs.createReadStream('sample.txt') +}); + +rl.on('line', (line) => { + console.log(`Line from file: ${line}`); +}); +``` + +For implementation, realine uses `input.on('keypress', onkeypress)` method to determine whether it is new line or not when reading TTY data', for normal stream, it caches the data and then uses the regular `.test` to determine whether it is new line. + +PS: If you are not used to getting input asynchronously when writing a script and want to get the input synchronously, see this module [scanf](https://github.com/Lellansin/node-scanf/) (typescript supported). + +## REPL + +Read-Eval-Print-Loop (REPL) + +Coming soon... From d0971ef53956dd22972d9d9c4e2eb9bdcb78ed77 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Thu, 27 Jul 2017 19:52:08 +0800 Subject: [PATCH 52/98] chore: update en-us dir --- sections/en-us/sections/error.md | 0 sections/en-us/sections/event-async.md | 0 sections/en-us/sections/io.md | 0 sections/en-us/sections/js-basic.md | 0 sections/en-us/sections/module.md | 0 sections/en-us/sections/network.md | 0 sections/en-us/sections/os.md | 0 sections/en-us/sections/process.md | 0 8 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 sections/en-us/sections/error.md delete mode 100644 sections/en-us/sections/event-async.md delete mode 100644 sections/en-us/sections/io.md delete mode 100644 sections/en-us/sections/js-basic.md delete mode 100644 sections/en-us/sections/module.md delete mode 100644 sections/en-us/sections/network.md delete mode 100644 sections/en-us/sections/os.md delete mode 100644 sections/en-us/sections/process.md diff --git a/sections/en-us/sections/error.md b/sections/en-us/sections/error.md deleted file mode 100644 index e69de29..0000000 diff --git a/sections/en-us/sections/event-async.md b/sections/en-us/sections/event-async.md deleted file mode 100644 index e69de29..0000000 diff --git a/sections/en-us/sections/io.md b/sections/en-us/sections/io.md deleted file mode 100644 index e69de29..0000000 diff --git a/sections/en-us/sections/js-basic.md b/sections/en-us/sections/js-basic.md deleted file mode 100644 index e69de29..0000000 diff --git a/sections/en-us/sections/module.md b/sections/en-us/sections/module.md deleted file mode 100644 index e69de29..0000000 diff --git a/sections/en-us/sections/network.md b/sections/en-us/sections/network.md deleted file mode 100644 index e69de29..0000000 diff --git a/sections/en-us/sections/os.md b/sections/en-us/sections/os.md deleted file mode 100644 index e69de29..0000000 diff --git a/sections/en-us/sections/process.md b/sections/en-us/sections/process.md deleted file mode 100644 index e69de29..0000000 From 54306e056cdd06e0818ff71da641d86dcf5518b4 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Thu, 27 Jul 2017 19:52:59 +0800 Subject: [PATCH 53/98] section: event-async.md, update link --- sections/zh-cn/event-async.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sections/zh-cn/event-async.md b/sections/zh-cn/event-async.md index 74a7332..8c55fe7 100644 --- a/sections/zh-cn/event-async.md +++ b/sections/zh-cn/event-async.md @@ -208,7 +208,7 @@ function sleep(ms) { └───────────────────────┘ ``` -关于事件循环, Timers 以及 nextTick 的关系详见官方文档 [The Node.js Event Loop, Timers, and process.nextTick() (英文)](https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/) 以及阮一峰的 [JavaScript 运行机制详解:再谈Event Loop (中文)](http://www.ruanyifeng.com/blog/2014/10/event-loop.html) 等. +关于事件循环, Timers 以及 nextTick 的关系详见官方文档 The Node.js Event Loop, Timers, and process.nextTick(): [英文](https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/), [论坛中文讨论](https://cnodejs.org/topic/57d68794cb6f605d360105bf) ## 并行/并发 From 71aaffc5595b0f9fcd3d8c3096843d875758722e Mon Sep 17 00:00:00 2001 From: Lellansin Date: Thu, 27 Jul 2017 19:53:17 +0800 Subject: [PATCH 54/98] translate: util.md, init --- sections/en-us/util.md | 224 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) diff --git a/sections/en-us/util.md b/sections/en-us/util.md index 29975d9..5bb71f5 100644 --- a/sections/en-us/util.md +++ b/sections/en-us/util.md @@ -4,3 +4,227 @@ * `[Doc]` Query Strings * `[Doc]` Utilities * `[Basic]` Regex + + +## URL + +```javascript +┌─────────────────────────────────────────────────────────────────────────────┐ +│ href │ +├──────────┬┬───────────┬─────────────────┬───────────────────────────┬───────┤ +│ protocol ││ auth │ host │ path │ hash │ +│ ││ ├──────────┬──────┼──────────┬────────────────┤ │ +│ ││ │ hostname │ port │ pathname │ search │ │ +│ ││ │ │ │ ├─┬──────────────┤ │ +│ ││ │ │ │ │ │ query │ │ +" http: // user:pass @ host.com : 8080 /p/a/t/h ? query=string #hash " +│ ││ │ │ │ │ │ │ │ +└──────────┴┴───────────┴──────────┴──────┴──────────┴─┴──────────────┴───────┘ +``` + +### Characters escape + +A common list of characters that need to be escaped: + +|Character|encodeURI| +|---|---| +|`' '`|`'%20'`| +|`<`|`'%3C'`| +|`>`|`'%3E'`| +|`"`|`'%22'`| +|\`|`'%60'`| +|`\r`|`'%0D'`| +|`\n`|`'%0A'`| +|`\t`|`'%09'`| +|`{`|`'%7B'`| +|`}`|`'%7D'`| +|`|`|`'%7C'`| +|`\\`|`'%5C'`| +|`^`|`'%5E'`| +|`'`|`'%27'`| + +Want to know more? Try this: + +```javascript +Array(range).fill(0) + .map((_, i) => String.fromCharCode(i)) + .map(encodeURI) +``` + +Try to set the range to 255 first (doge. + +## Query Strings + +A query string is the part of a URL referring to the table above. Node.js provides a module called `querystring`. + +|Method|Description| +|---|---| +|.parse(str[, sep[, eq[, options]]])|Parse a query string into a json object| +|.unescape(str)|Inner method used by .parse(). It is exported primarily to allow application code to provide a replacement decoding implementation if necessary| +|.stringify(obj[, sep[, eq[, options]]])| +Converts a json object to a query string| +|.escape(str)|Inner method used by .stringify(). It is exported primarily to allow application code to provide a replacement percent-encoding implementation if necessary.| + +So far, the Node.js built-in querystring does not support for the deep structure: + +```javascript +const qs = require('qs'); // +Third party +const querystring = require('querystring'); // Node.js built-in + +let obj = { a: { b: { c: 1 } } }; + +console.log(qs.stringify(obj)); // 'a%5Bb%5D%5Bc%5D=1' +console.log(querystring.stringify(obj)); // 'a=' + +let str = 'a%5Bb%5D%5Bc%5D=1'; + +console.log(qs.parse(str)); // { a: { b: { c: '1' } } } +console.log(querystring.parse(str)); // { 'a[b][c]': '1' } +``` + +> How does HTTP pass `let arr = [1,2,3,4]` to the server by GET method? + +```javascript +const qs = require('qs'); + +let arr = [1,2,3,4]; +let str = qs.stringify({arr}); + +console.log(str); // arr%5B0%5D=1&arr%5B1%5D=2&arr%5B2%5D=3&arr%5B3%5D=4 +console.log(decodeURI(str)); // 'arr[0]=1&arr[1]=2&arr[2]=3&arr[3]=4' +console.log(qs.parse(str)); // { arr: [ '1', '2', '3', '4' ] } +``` + +You can pass arr Array to the server vir `https://your.host/api/?arr[0]=1&arr[1]=2&arr[2]=3&arr[3]=4`. + +## util + +In v4.0.0 or later, util.is*() is not recommended and deprecated. Maybe it is because that maintaining the library is thankless and there are so many popular libraries. The following is the list: + +* util.debug(string) +* util.error([...strings]) +* util.isArray(object) +* util.isBoolean(object) +* util.isBuffer(object) +* util.isDate(object) +* util.isError(object) +* util.isFunction(object) +* util.isNull(object) +* util.isNullOrUndefined(object) +* util.isNumber(object) +* util.isObject(object) +* util.isPrimitive(object) +* util.isRegExp(object) +* util.isString(object) +* util.isSymbol(object) +* util.isUndefined(object) +* util.log(string) +* util.print([...strings]) +* util.puts([...strings]) +* util._extend(target, source) + +Most of them can be used as an interview to ask how to implement. + +### util.inherits + +> how to implement util.inherits in Node.js? + +https://github.com/nodejs/node/blob/v7.6.0/lib/util.js#L960 + +```javascript +/** + * Inherit the prototype methods from one constructor into another. + * + * The Function.prototype.inherits from lang.js rewritten as a standalone + * function (not on Function.prototype). NOTE: If this file is to be loaded + * during bootstrapping this function needs to be rewritten using some native + * functions as prototype setup using normal JavaScript does not work as + * expected during bootstrapping (see mirror.js in r114903). + * + * @param {function} ctor Constructor function which needs to inherit the + * prototype. + * @param {function} superCtor Constructor function to inherit prototype from. + * @throws {TypeError} Will error if either constructor is null, or if + * the super constructor lacks a prototype. + */ +exports.inherits = function(ctor, superCtor) { + + if (ctor === undefined || ctor === null) + throw new TypeError('The constructor to "inherits" must not be ' + + 'null or undefined'); + + if (superCtor === undefined || superCtor === null) + throw new TypeError('The super constructor to "inherits" must not ' + + 'be null or undefined'); + + if (superCtor.prototype === undefined) + throw new TypeError('The super constructor to "inherits" must ' + + 'have a prototype'); + + ctor.super_ = superCtor; + Object.setPrototypeOf(ctor.prototype, superCtor.prototype); +}; +``` + +## Regex + +At first, regular expression is a biological expression that used to describe the brain neurons, GNU beard used to do the string match after the original road drifting away. Then it is used by men of GNU to match string, and +deviates from the original road. + +Collecting... + +## Common Modules + +[Awesome Node.js](https://github.com/sindresorhus/awesome-nodejs) +[Most depended-upon packages](https://www.npmjs.com/browse/depended) + +> How do I get all the file names under a folder? + +```javascript +const fs = require('fs'); +const path = require('path'); + +function traversal(dir) { + let res = [] + for (let item of fs.readdirSync(dir)) { + let filepath = path.join(dir, item); + try { + let fd = fs.openSync(filepath, 'r'); + let flag = fs.fstatSync(fd).isDirectory(); + fs.close(fd); // TODO + if (flag) { + res.push(...traversal(filepath)); + } else { + res.push(filepath); + } + } catch(err) { + if (err.code === 'ENOENT' && // can not open link file + !!fs.readlinkSync(filepath)) { // if it is a link file + res.push(filepath); + } else { + console.error('err', err); + } + } + } + return res.map((file) => path.basename(file)); +} + +console.log(traversal('.')); + + +``` + + +Of course you can also use Oh my [glob](https://github.com/isaacs/node-glob): + + +```javascript +const glob = require("glob"); + +glob("**/*.js", (err, files) { + if (err) { + throw new Error(err); + } + console.log('Here you are:', files.map(path.basename)); +}); From d37d11c58fa983084ae996ac80c119f999cb18c6 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Thu, 27 Jul 2017 19:53:32 +0800 Subject: [PATCH 55/98] translate: process.md, init --- sections/en-us/process.md | 247 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 247 insertions(+) diff --git a/sections/en-us/process.md b/sections/en-us/process.md index 7741a7f..14dd729 100644 --- a/sections/en-us/process.md +++ b/sections/en-us/process.md @@ -5,3 +5,250 @@ * `[Doc]` Cluster * `[Basic]` IPC * `[Basic]` Daemon + +## Introduction + +For Process, we will discuss two concepts,① the process of the operating system, ② the Process object in Node.js. Operation process is basis for the server-side just like Html for the Front-end. No one can do server-side programming without Unix/Linux. Excuting `ps -ef` command in Linux/Unix/Mac system, and you will see the running processes of the current system. +Each parameter is as follows: + +|Column name|Meaning| +|-----|---| +|UID|User ID of the process's owner| +|PID|Process ID number| +|PPID|ID number of the process's parent process| +|C|CPU usage| +|STIME|Time when the process started| +|TTY|Terminal associated with the process| +|TIME|Total CPU time used by the process since it started| +|CMD|Command and arguments for the process| + +For more details about the process and the operating system, you can read the APUE(Advanced Programming in the UNIX® Environment). + +## Process + + +Here we will discuss the `project` object in Node.js. It can be printed out by using `console.log (process)` in the code. You can see the process object exposed a lot of useful properties and methods. For more details you can refer [Official document](https://nodejs.org/dist/latest-v6.x/docs/api/process.html), which has been very detailed, +including but not limited to: + +* The basic information of the process +* The usage of the process +* Process Events +* Dependencies/versions +* The basic information of the operating system platform +* The information of the user +* Signal Events +* The three standard streams + +### process.nextTick + +The previous chapter has already mentioned `process.nextTick`, which is an important, basic method you have to know. + +``` + ┌───────────────────────┐ +┌─>│ timers │ +│ └──────────┬────────────┘ +│ ┌──────────┴────────────┐ +│ │ I/O callbacks │ +│ └──────────┬────────────┘ +│ ┌──────────┴────────────┐ +│ │ idle, prepare │ +│ └──────────┬────────────┘ ┌───────────────┐ +│ ┌──────────┴────────────┐ │ incoming: │ +│ │ poll │<─────┤ connections, │ +│ └──────────┬────────────┘ │ data, etc. │ +│ ┌──────────┴────────────┐ └───────────────┘ +│ │ check │ +│ └──────────┬────────────┘ +│ ┌──────────┴────────────┐ +└──┤ close callbacks │ + └───────────────────────┘ +``` + +`process.nextTick` is not technically part of the event loop. Instead, the `nextTickQueue` will be processed after the current operation completes, regardless of the current phase of the event loop. So the question is coming, what will happen if you call process.nextTick recursively?(doge + +```javascript +function test() { + process.nextTick(() => test()); +} +``` + +What is the difference between this situation and the following? Why? + +```javascript +function test() { + setTimeout(() => test(), 0); +} +``` + +### Configuration + +Configuration is a very common problem in development deployments. As usual, there are two ways for configuration, one is to define configuration file, another is to to use the environment variables. + + ![node-configuration](https://blog-assets.risingstack.com/2016/Sep/node-js-survey/node-js-survey-envvar-config-new.png) + +You can specify the configuration by [Setting Environment Variables](http://cn.bing.com/search?q=linux+Setting+Environment+Variable&qs=n&form=QBRE&sp=-1&pq=linux+setting+environment+variable&sc=1-34&sk=&cvid=1027E58E457E42DEB5A4A4E495EEC4A9), then obtain the configuration item by using `process.env`. In addition, you can obtain by reading the configuration file. There are many excellent libraries such as `Dotenv`, ` node-config`, etc. in this field. But when loading the configuration file by using these libraries, it usually encounters a problem with the current working directory. + +> What is the current working directory of the process? What is its role? + +You can obtain the current working directory by using `process.cwd()`. It usually is the directory when the command line starts. It can also be specified at startup. File operations, etc. obtain the file by using the relative path which is relative to the current working directory. + +Some of the third-party modules that can obtain the configuration look for the configuration file through your current directory. So if running the start script in the wrong directory, you will get wrong rusults. You can change working directory by using `process.chdir()` in your code. + +### Standard Stream + +The process object also exposes three standard stream, `process.stderr`, `process.stdout`, `process.stdin`. If you have used C/C++/Java before, you will not feel unfamiliar with this. So the common interview questions come: **Is `console.log` synchronous or asynchronous? How to implement a `console.log`?** + +If there are keywords such as C/C++ in your resume, you will be asked how to implement a synchronous input(similar to the `scanf` in the C, `cin` in C++, `raw_input` in Python, etc.). + +## Maintaining + +Familiar with basic commands about process, such as top, ps, pstree , etc. + +## Child Process + +Child Process is an important concept in the process. In Node.js, you can use `child_process` module to execute executable files, call commands in command line , such as programs in other languages, etc. You can also execute js code as a sub-process by using this module. The well-known Netease's distributed architecture [pomelo](https://github.com/NetEase/pomelo) is based on the module(not `cluster`) to implement the multi-process distributed architecture. + +> What are the differences between child_process.fork and fork in POSIX? + +In Node.js, `child_process.fork()` calls POSIX [fork(2)](http://man7.org/linux/man-pages/man2/fork.2.html). You need manually manage the release of resources in the child process for fork POSIX. You don't need to care about this problem when using `child_process.fork`, beacuse Node.js will automatic release, and provide options whether the child process can survive after the parent process is destroyed. + +* spawn() - Spawns a child process to execute the command + * options.detached - Whether the child process can survive after the parent process is destroyed + * options.stdio - Configure the three pipes that are established between the parent and child process +* spawnSync() - A synchronous version of spawn(). + You can set the timeout, and obtain the child process by the return object +* exec() - Spawns a child process to execute the command, with the callback parameters to get information of the child process. You can set the timeout for the process +* execSync() - A synchronous version of exec(). You can set the timeout. It returns stdout of the child process +* execFile() - Spawns a child process to execute an executable file. You can set the timeout for the process +* execFileSync() - A synchronous version of execFile(). It returns stdout of the child process. It will throw Error if it is timeout or its exit code is not 0 +* fork() - Enhanced version of spawn(). It returns a child process object and allows sending messages between parent and child. + +The exec/execSync method will directly call bash to explain the command. So if there are external parameters of the command, you need to pay attention to the situation was injected. + +### child.kill and child.send + +The common interview question is what are the differences between `child.kill` and `child.send`. +One is based on the signal system, the other is based on IPC. + +> Does the parent process or child process death affect each other? What is an orphan process? + +The death of a child process will not affect the parent process. When the child process dies (the last thread of the thread group, usually when the "lead" thread dies), it will send a death signal to its parent process. On the other hand, when the parent process dies, by default, the child process will follow the death. But at this time, if the child process is in the operational state, dead state, etc., it will be adopted by process identifier 1(the init system process) and become an orphaned process. In addition, when the child process dies("terminated" state), the parent process does not call `wait()` or `waitpid()` to return the child's infomation in time, there is a `PCB` remaining in the process table. The child process is called a zombie process. + +## Cluster + +Cluster is a common way to use multi-core systems in Node.js. It is based on `child_process.fork ()` implementation. For this reason, the worker processes can communicate with the parent via IPC, and do not copy parent's memory space. You can distinguish between the parent process and the child process by adding `cluster.isMaster`, making it +similar to the [fork](http://man7.org/linux/man-pages/man2/fork.2.html) in POSIX. + +```javascript +const cluster = require('cluster'); // | | +const http = require('http'); // | | +const numCPUs = require('os').cpus().length; // | | Both executed + // | | +if (cluster.isMaster) { // |-|----------------- + // Fork workers. // | + for (var i = 0; i < numCPUs; i++) { // | + cluster.fork(); // | + } // | Only the parent process is executed (a.js) + cluster.on('exit', (worker) => { // | + console.log(`${worker.process.pid} died`); // | + }); // | +} else { // |------------------- + // Workers can share any TCP connection // | + // In this case it is an HTTP server // | + http.createServer((req, res) => { // | + res.writeHead(200); // | Only the child process is executed (b.js) + res.end('hello world\n'); // | + }).listen(8000); // | +} // |------------------- + // | | +console.log('hello'); // | | Both executed +``` + +In the code above numCPUs is a global variable. However, it will not change in the child process when modified in the parent process, because the child process and the parent process run in separate memory spaces. The so-called shared is that they both run, but in separate memory spaces. + +The execution of the parent process can be seen as `a.js`, and the execution of the child process seen as `b.js`. You can imagine that it executes `node a.js` first, and then `cluster.fork` several times(execute `node b.js` several times). The cluster module is a bridge between them. They two can communicate between each other by using methods provided by cluster. + +### How It Works + +The worker processes are spawned using the child_process.fork() method, so that they can communicate with the parent via IPC and pass server handles back and forth. + +The cluster module supports two methods of distributing incoming connections. + +The first one (and the default one on all platforms except Windows), is the round-robin approach, where the master process listens on a port, accepts new connections and distributes them across the workers in a round-robin fashion, with some built-in smarts to avoid overloading a worker process. + +The second approach is where the master process creates the listen socket and sends it to interested workers. The workers then accept incoming connections directly. + +The second approach should, in theory, give the best performance. In practice however, distribution tends to be very unbalanced due to operating system scheduler vagaries. Loads have been observed where over 70% of all connections ended up in just two processes, out of a total of eight. + + +## IPC(Inter-process communication) + +Inter-process communication techniques can be divided into various types. These are: + +Type|Without Connnection|Stability|Flow Control|Priority +---|-----|----|-----|----- +Pipe|N|Y|Y|N +Named Pipe|N|Y|Y|N +Message Queues|N|Y|Y|N +Semaphores|N|Y|Y|Y +Shared Memory|N|Y|Y|Y +UNIX Stream Socket|N|Y|Y|N +UNIX Datagram Socket|Y|Y|N|N + +IPC in Node.js is implemented through Pipe based on libuv. It is implemented by Named Pipe(the second item in the list above) in windows, and UDS (Unix Domain Socket) in *nix. + +Ordinary socket is designed for network communications, which itself is unreliable. But the socket for the IPC is not the case, because local network environment is reliable by default. So you can simplify much unnecessary encode/decode and calculate the verification, etc., get more efficient UDS communication. + + +If understanding the IPC in Node.js, you will be asked an interesting question: + +> Before IPC channel was +set up, how the parent process and the child process +communicate between each other? If there is no communication, how is IPC set up? + +This question is very simple, just a problem of thinking. When you create a child process via child_process, you can specify the env (environment variable) of the child process. When starting the child process in Node.js, the main process sets up the IPC channel first, then pass the fd(file descriptor) of the IPC channel to the child process via environment variable (`NODE_CHANNEL_FD`). Then the child process connects to the parent process via fd. + +Finally, for the issue of inter-process communication (IPC), we generally do not directly ask the IPC implementation, but will ask under what conditions you need IPC, and the use of IPC to deal with any business scene. + +## Daemon Process + +Daemon Process is a very basic concept of the server side. Many people may only know that we can start a process as a daemon by using tools such as pm2, but not what is a process and why using it. For excellent guys, daemon process implement should be known. + +The normal process will be directly shut down after the user exits the terminal. The Process starting with `&` and running in the background will be shut down when the session (session group) is released. The daemon process is not dependent on the terminal(tty) process and will not be shut down because of the user exiting the terminal. + +```c +// Daemon Process Implement (Written in C) +void init_daemon() +{ + pid_t pid; + int i = 0; + + if ((pid = fork()) == -1) { + printf("Fork error !\n"); + exit(1); + } + + if (pid != 0) { + exit(0); // parent process exits + } + + setsid(); // the child process opens a new session and becomes the session header and the process group leader + if ((pid = fork()) == -1) { + printf("Fork error !\n"); + exit(-1); + } + if (pid != 0) { + exit(0); // End the first process, and the second process is not the session header any more. + // avoid the current session group to re-connect with the tty + } + chdir("/tmp"); // change the working directory + umask(0); // reset the file umask + for (; i < getdtablesize(); ++i) { + close(i); // close the file descriptor + } + + return; +} +``` + +[Code for Daemon Process in Node.js](https://cnodejs.org/topic/57adfadf476898b472247eac) From 7f86422bb39328f9ddee696f57aa01b51ac69bc0 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Thu, 27 Jul 2017 19:53:42 +0800 Subject: [PATCH 56/98] translate: event-async.md, init --- sections/en-us/event-async.md | 251 +++++++++++++++++++++++++++++++++- 1 file changed, 245 insertions(+), 6 deletions(-) diff --git a/sections/en-us/event-async.md b/sections/en-us/event-async.md index ca3963c..432ce20 100644 --- a/sections/en-us/event-async.md +++ b/sections/en-us/event-async.md @@ -1,7 +1,246 @@ -# Event/Async +# event/Asynchronous -* `[Basic]` Promise -* `[Doc]` Events -* `[Doc]` Timers -* `[Point]` Blocking & Non-blocking -* `[Point]` Parallel & Concurrent +* [`[Basic]` Promise](/sections/en-us/event-async.md#promise) +* [`[Doc]` Events ](/sections/en-us/event-async.md#events) +* [`[Doc]` Timers ](/sections/en-us/event-async.md#timers) +* [`[Point]` Blocking/non-blocking](/sections/en-us/event-async.md#blocking-non-blocking) +* [`[Point]` Paraller/Concurrent](/sections/en-us/event-async.md#paraller-concurrent) + +## Summary + +Synchronous or Asynchronous ?That is a question. + +## Promise + +![callback-hell](/assets/callback-hell.jpg) + + +I believe that in the interview, many students have been asked such a question: how to handle `Callback Hell`. In the early years, there are lots of solutions like [Q](https://www.npmjs.com/package/q), [async](https://www.npmjs.com/package/async), [EventProxy](https://www.npmjs.com/package/eventproxy). Finally from the prevalence point of view, promise has been the winner of them, and has been the part of the ECMAScript 6 specification. + + +To learn more basic knowledge about `promise`, we recommend this article. [Promise](http://javascript.ruanyifeng.com/advanced/promise.html#toc9) + +> What the difference between the second argument of '.then' function and '.catch' function? + +To distinguish the difference, you can read this article [We have a problem with promises](https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html) + +About Synchronous or Asynchronous, I hope you can pay attention to this question, a simple `promise` example below. + +```javascript +let doSth = new Promise((resolve, reject) => { + console.log('hello'); + resolve(); +}); + +doSth.then(() => { + console.log('over'); +}); +``` + +there is no doubt that you can get the output + +``` +hello +over +``` + + +the first question is that the code wrapped by Promise is certainly synchronized, but whether the execution of `then` is Asynchronous? + +the second quesiton is `setTimeout` and `then` will be called after 10s, but how about `hello`? will `hello` be printed after 10s or at the beginning? + +```javascript +let doSth = new Promise((resolve, reject) => { + console.log('hello'); + resolve(); +}); + +setTimeout(() => { + doSth.then(() => { + console.log('over'); + }) +}, 10000); +``` + + +and how to understand the execution order of the code below: ([resource](https://zhuanlan.zhihu.com/p/25407758)) + + +```javascript +setTimeout(function() { + console.log(1) +}, 0); +new Promise(function executor(resolve) { + console.log(2); + for( var i=0 ; i<10000 ; i++ ) { + i == 9999 && resolve(); + } + console.log(3); +}).then(function() { + console.log(4); +}); +console.log(5); +``` + + +If you don't kown the answers of these questions, you can print the output at local environment. I hope you can understand the change of `promise` status, includes the relationship between `promise` and asynchronous, how promise help you to handle async situation , it would be better if you know the implementations of `promise` + +## Events + + +`Events` module is a very important core module in Node.js. There are many important core APIs in the node that depend on `Events` , for example, `Stream` is implemented based on `Events`, and `fs`, `net`, 'http' are implemented based on 'Stream', 'Events' is so important to Node.js. + +A class or a Object can get basic `events` methods by extending `EventEmitter` class, and we call it 'emitter', and the callback funciton that emit a kind of event is called as 'listener'. It is diffrent from DOM tree in browser, there are no bubble and capture actions or methods to handle event. + + +>Whether the emit of Eventemitter is synchronous or asynchronous? + +The answer is **synchronous**, there are some description on Node.js documentation: + +>The EventListener calls all listeners synchronously in the order in which they were registered. This is important to ensure the proper sequencing of events and to avoid race conditions or logic errors. + + +let's discuss the output is 'hi 1' or 'hi 2'? + +```javascript +const EventEmitter = require('events'); + +let emitter = new EventEmitter(); + +emitter.on('myEvent', () => { + console.log('hi 1'); +}); + +emitter.on('myEvent', () => { + console.log('hi 2'); +}); + +emitter.emit('myEvent'); +``` +and whether there is a endless loop? + +```javascript +const EventEmitter = require('events'); + +let emitter = new EventEmitter(); + +emitter.on('myEvent', () => { + console.log('hi'); + emitter.emit('myEvent'); +}); + +emitter.emit('myEvent'); +``` + +and how about this case? + +```javascript +const EventEmitter = require('events'); + +let emitter = new EventEmitter(); + +emitter.on('myEvent', function sth () { + emitter.on('myEvent', sth); + console.log('hi'); +}); + +emitter.emit('myEvent'); +``` + +Emitter can handle many complex state scenarios, such as TCP complex state machine, and if you are handling a multiple asynchronous operation and each step may throw an error, at this time .emit error and the excute some .once operations can save you from the mud. + + +Pay attention to that some students prefer to monitor the status of certain class, but when you destroy this class, don't forget to destroy these emitters too , because inside the class, some listener may cause memory leak. + +## Blocking/non-blocking + +> how to judge whether a interface is asynchronous? Whether it must be asynchronous if a callback function provided? + + +This is a open question, you can have your own way to judge. + +* review documentation +* console.log and print the output +* whether there is IO operation + + +Simply use the callback function is not asynchronous, IO operation may be asynchronous, in addition to the use of setTimeout and other ways are asynchronous. + + +> if you have built a website by koa, this website has a interface A, and in some cases, interface A can be the endless loop, unfortunately, if you triggered this endless loop, what will be the impact on your website? + + +In Node.js environment javascript code has only one single thread. Only the current code has been excuted, the process will cut into the event loop, and then pop out the next callback function from the event queue to start the implementation of the code. so ① to achieve a sleep function, as long as an infinite loop can block the execution of the entire js process (on how to avoid the colleagues write deadless loop, see the chapter of `test`.) + + +> how to acheive a sleep function ① + +```javascript +function sleep(ms) { + var start = Date.now(), expire = start + ms; + while (Date.now() < expire) ; + return; +} +``` + +Asynchronous in Node.js means an event queue in other thread achived by libuv module. + +If endless loop logic trigger in your website, the whole process will be blocked, and all request will timeout, asynchronous code will never be excuted, and your website will be crashed. + +> How to achieve an async reduce? + +You need to konw that reduce is analyze a recursive data structure and through use of a given combining operation, recombine the results of recursively processing its constituent parts, building up a return value. + +## Timers + +The writter think there are two kinds of 'asynchronous' in Node.js: `hard asynchronous` and `soft asynchronous`. + +`hard asynchronous` means IO operation or some cases that you need libuv module externally and of course includes `readFileSync` or `execSync`. Because of the single thread feature of Node.js, it is unwise to do some IO operation in synchronous way as it will block the excutation of other code. + +`soft asynchronous` is that some asynchronous cases achieved by `setTimeout`. To understand the diffrence between nextTick, setTimeout and setImmediate , you can see this article. [article](https://cnodejs.org/topic/5556efce7cabb7b45ee6bcac) + + + + +**Event loop example** + +``` + ┌───────────────────────┐ +┌─>│ timers │ +│ └──────────┬────────────┘ +│ ┌──────────┴────────────┐ +│ │ I/O callbacks │ +│ └──────────┬────────────┘ +│ ┌──────────┴────────────┐ +│ │ idle, prepare │ +│ └──────────┬────────────┘ ┌───────────────┐ +│ ┌──────────┴────────────┐ │ incoming: │ +│ │ poll │<─────┤ connections, │ +│ └──────────┬────────────┘ │ data, etc. │ +│ ┌──────────┴────────────┐ └───────────────┘ +│ │ check │ +│ └──────────┬────────────┘ +│ ┌──────────┴────────────┐ +└──┤ close callbacks │ + └───────────────────────┘ +``` + +To know more about event loop, Timers, nextTick,we recommend the Node.js documentation and this article: [The Node.js Event Loop, Timers, and process.nextTick()](https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/). + + +## Paraller/Concurrent + + +Parallelism and Concurrency are two very common concepts. You can read this blog of Joe Armstrong(the creator of Erlang) ([Concurrent and Parallel](http://joearms.github.io/2013/04/05/concurrent-and-parallel-programming.html)) + +![con_and_par](http://joearms.github.io/images/con_and_par.jpg) + +Coucurrent = 2 queues with 1 coffee machine + +Parallel = 2 queues with 2 coffee machines + + +Node.js executes each task of events queue one by one by event loop, by this way, it avoids that in some traditional multithreading situation, when '2 queues with 1 coffee machine', the context switch and resource scramble/synchronize problems, and achives high concurrent。 + + +you can add another 'coffee machine' by using `cluster` module to achieve paraller in Node.js. From cffbf69647f4dd8db1dd0995aee1daeada60e101 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Thu, 27 Jul 2017 19:54:02 +0800 Subject: [PATCH 57/98] translate: error.md init --- sections/en-us/error.md | 374 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 366 insertions(+), 8 deletions(-) diff --git a/sections/en-us/error.md b/sections/en-us/error.md index 2dd289d..79e1f57 100644 --- a/sections/en-us/error.md +++ b/sections/en-us/error.md @@ -1,10 +1,368 @@ -# Error & Debug +# Error handling/Debug/Optimization -* `[Doc]` Errors -* `[Doc]` Domain -* `[Doc]` Debugger -* `[Doc]` C/C++ Addon -* `[Doc]` V8 -* `[Point]` Memory snapshot -* `[Point]` CPU Profilling +* [`[Doc]` Errors](/sections/en-us/error.md#errors) +* [`[Doc]` Domain](/sections/en-us/error.md#domain) +* [`[Doc]` Debugger](/sections/en-us/error.md#debugger) +* [`[Doc]` C/C++ Addon](/sections/en-us/error.md#cc-addon) +* [`[Doc]` V8](/sections/en-us/error.md#v8) +* [`[Point]` Memory snapshots](/sections/en-us/error.md#memory-snapshots) +* [`[Point]` CPU profiling](/sections/en-us/error.md#cpu-profiling) + +## Errors + +There are mainly four types of Errors in Node.js: + +|Error| Triggered by | +|---|---| +|Standard JavaScript errors|error codes| +|System errors|operating system| +|User-specified errors|throw method| +|Assertion errors| `assert` module| + +Here are the common standard JavaScript errors: + +* [EvalError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/EvalError): Thrown when error occurs when calling eval(). +* [SyntaxError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError): Thrown when codes are not conforming to JavaScript syntax style. +* [RangeError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError): Thrown when out of bounds. +* [ReferenceError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ReferenceError): Thrown when referencing undefined variables. +* [TypeError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError): Thrown when parameter types are error. +* [URIError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/URIError): Thrown when misusing global URI handling functions. + +And the common system errors list can be viewed by os object in Node.js: + +```javascript +const os = require('os'); + +console.log(os.constants.errno); +``` + +When searching interview questions of Node.js, We find that most of them are out-of-date. In a older post [Best practices about error handling in NodeJS](https://cnodejs.org/topic/55714dfac4e7fbea6e9a2e5d), which was translated from Joyent's official blog, we've found the words below: + +> In fact, the only commonly-used case where you'd use `try/catch` is `JSON.parse` and other user-input validation functions. + +But in nowadays you can easily use `try/catch` to catch asynchronous exceptions in Node.js. and after using the upgraded v8 engine from Node.js v7.6, the problem that `try/catch` codes can not be optimized was also solved. Now let's see the question + +> How should I handle unexpected errors? Should I use `try/catch`, domains, or something else? + +Here are the error handling methods in Node.js: + +* `callback(err, data)` Callback agreement +* throw / try / catch +* Error event of EventEmitter + +Using `callback(err, data)` to handle errors is cumbersome and does not have compulsion, so we recommend you understand it, but don't use it. As for the domain module, it's already half foot into the coffin. + +1) Thank [co](https://github.com/visionmedia/co) for his leading forward in this domain, now you can use `try/catch` to protect key position easily, Such as koa's error processing can be done in the form of middleware, for more details, see [Koa error handling](https://github.com/koajs/koa/wiki/Error-Handling). async/await are the same way as koa. + +2) Adding error callback for the key object through error listening form of EventEmitter. Such as `error` events of http server, tcp server and `uncaughtException`, `unhandledRejection` of process object. + +3) Using Promise to encapsulate asynchronous, and the error handling of it to handle errors. + +4) If the methods above can't play a good role, then you should learn how to [Let It Crash](http://wiki.c2.com/?LetItCrash) gracefully. + +> Why is the first parameter of cb should be error? And why is the first parameter of some cb is not error, such as http.createServer? + +TODO + + +### Error stack is missing + +```javascript +function test() { + throw new Error('test error'); +} + +function main() { + test(); +} + +main(); +``` + +Then you get an error message: + +```javascript +/data/node-interview/error.js:2 + throw new Error('test error'); + ^ + +Error: test error + at test (/data/node-interview/error.js:2:9) + at main (/data/node-interview/error.js:6:3) + at Object. (/data/node-interview/error.js:9:1) + at Module._compile (module.js:570:32) + at Object.Module._extensions..js (module.js:579:10) + at Module.load (module.js:487:32) + at tryModuleLoad (module.js:446:12) + at Function.Module._load (module.js:438:3) + at Module.runMain (module.js:604:10) + at run (bootstrap_node.js:394:7) +``` + +You can find that the number of rows reported, call hierarchy of test and main function are all displayed clearly in the stack. + +When we use timers such as setImmediate to set asynchronously: + +```javascript +function test() { + throw new Error('test error'); +} + +function main() { + setImmediate(() => test()); +} + +main(); + +``` + +We find this: + +```javascript +/data/node-interview/error.js:2 + throw new Error('test error'); + ^ + +Error: test error + at test (/data/node-interview/error.js:2:9) + at Immediate.setImmediate (/data/node-interview/error.js:6:22) + at runCallback (timers.js:637:20) + at tryOnImmediate (timers.js:610:5) + at processImmediate [as _immediateCallback] (timers.js:582:5) +``` + +The error stack only outputs to the line where the function was called in `test` function, and the call information of `main` function is lost. That is if you have many layers of nested function calls, it is very hard to trace this asynchronous call when error occurs, because the up-layer stack is already lost. If you've used modules such as [async](https://github.com/caolan/async), You may also find that the error stack is very long and tortuous, so it's difficult to locate the error position through the stack. + +This won't be a problem if the project is small / the coder knows all the thing, but it will become a big pain when the project growing bigger and there is more coders. We've talked about this issue in the `Suggestions for writing new functions` section of [best practices about error handling in Node.js](https://cnodejs.org/topic/55714dfac4e7fbea6e9a2e5d) mentioned above. Errors are packaged layer by layer through the way of using [verror](https://www.npmjs.com/package/verror) so that we can get the key information for locating error in the finally got Error. + +Let's see the download statistics from yesterday (2017-3-13). Last month, the downloads of [verror](https://www.npmjs.com/package/verror) is `1100w`, higher than [express](https://www.npmjs.com/package/express) (`1070w`). Now you can feel how popular is this way of writing codes. + +### Defensive programming + +It's not terrible to make mistakes, what makes a terrible mistake is you are not prepared to deal with it————[Introduction and skills of defensive programming](http://blog.jobbole.com/101651/) + +### let it crash + +[Let It Crash](http://wiki.c2.com/?LetItCrash) + +### uncaughtException + +The `uncaughtException` event of process object will be triggered when the exception is not caught and bubbling to the Event Loop. By default, Node.js will ouput the stack trace information to the `stderr` and end process for such exceptions, And adding listener to `uncaughtException` event can override the default behavior, thus not end the process directly. + +```javascript +process.on('uncaughtException', (err) => { + console.log(`Caught exception: ${err}`); +}); + +setTimeout(() => { + console.log('This will still run.'); +}, 500); + +// Intentionally cause an exception, but don't catch it. +nonexistentFunc(); +console.log('This will not run.'); +``` + +#### Using uncaughtException reasonably + +The original intention of `uncaughtException` is to let you do some recycling processing and then process.exit after getting the error. Official comrades have discussed to remove this event. (See [issues](https://github.com/nodejs/node-v0.x-archive/issues/2582)) + +So you need to know `uncaughtException` is already a non-conventional means, try to avoid using it to handle errors. Because capturing the error through the event does not mean that `you can continue to run happily (On Error Resume Next)`. There is an unhandled exception inside the program, which means that the application is in an unknown state. If you can not properly restore its status, then it is likely to trigger unforeseen problems. (Even worse if using domain, and all kinds of puzzled questions will be produced) + +If the error is not caught in the callback listener specified by the `.on` function, the process of Node.js will be interrupted and return a non-zero exit code, and finally output the corresponding stack information. Otherwise, there will be infinite recursion. In addition, memory crashes / underlying errors also can not be captured, **We currently guess** the reason is v8/C++ did not deal with the problem, while Node.js was unable to handle it (TODO: We suddenly found this idea has not been verified yet, please help us to verify it if convenient). + +So the right way advised by the officials to use `uncaughtException` is cleaning up the used resources synchronously (file descriptors, handles, and so on) and then process.exit. + +Actually It's not safe to perform a normal restore operation after uncaughtException event. Officials advise you to prepare for a monitor process to do health checks, manage recoveries and restart when necessary (So the officials are reminding you to use tools such as pm2 implicitly). + + +### unhandledRejection + +This event will be triggered When a Promise without binded handler is rejected. It is very useful when investigating and tracking Promise not handles reject behavior. + +Here are the parameters of the callback of this event: + +* `reason` [``](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) | `` Rejected reason (Usually Error) +* `p` The rejected Promise + +Such as: + +```javascript +process.on('unhandledRejection', (reason, p) => { + console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); + // application specific logging, throwing an error, or other logic here +}); + +somePromise.then((res) => { + return reportToUser(JSON.pasre(res)); // note the typo (`pasre`) +}); // no `.catch` or `.then` +``` + +The following code also triggers the `unhandledRejection` event: + +```javascript +function SomeResource() { + // Initially set the loaded status to a rejected promise + this.loaded = Promise.reject(new Error('Resource not yet loaded!')); +} + +var resource = new SomeResource(); +// no .catch or .then on resource.loaded for at least a turn +``` + +> In this example case, it is possible to track the rejection as a developer error as would typically be the case for other 'unhandledRejection' events. To address such failures, a non-operational `.catch(() => { })` handler may be attached to resource.loaded, which would prevent the 'unhandledRejection' event from being emitted. Alternatively, the 'rejectionHandled' event may be used. + + +## Domain + +In the early Node.js, try/catch is unable to capture asynchronous errors, And the error first callback is just an agreement, without mandataries and very cumbersome to write. So in order to catch the exception very well, Node.js introduces domain module in v0.8. + +domain is an EventEmitter object, The basic idea of capturing an asynchronous exception is to create a domain, The `cb` function will inherit the domain of the upper layer, and the errors will be passed through the error events triggered by `.emit('error', err)` function in current domain, so that asynchronous errors can be forced to capture. (For more details, see [Asynchronous exception handling in Node.js and analysis of domain module](https://cnodejs.org/topic/516b64596d38277306407936)) + +But domain also brought more new problems. Such as dependent modules can not inherit the domain you defined, Causing it can't cover errors in dependent modules. Furthermore, Many people (especially new bie) didn't understand memory / asynchronous processes and other issues in Node.js, they didn't do well when using domain to process errors and let the code continue, this is likely to cause **the project to be completely unserviceable** (Any problems are possible, And all kinds of shit...) + +You can see the latest news about this module at: [deprecate domains](https://github.com/nodejs/node/issues/66) + + +## Debugger + +![node-js-survey-debug](/assets/node-js-survey-debug.png) + +Command line debug tool like gdb (Build-in debugger in the image above), it also supports remote debug (like [node-inspector](https://github.com/node-inspector/node-inspector), but still in trial). Of course, many developers feel that [vscode](https://code.visualstudio.com/) has maken a better integration to the debug tools. + +We recommend reading [official ducoment](https://nodejs.org/dist/latest-v6.x/docs/api/debugger.html) to learn how to use this build-in debugger. If you want to dig deeper, see: [Modify the value of a variable in the NodeJS program dynamically](http://code.oneapm.com/nodejs/2015/06/27/intereference/) + + +## C/C++ Addon + +The most painful thing when developing addon in Node.js is the incompatible of C/C++ codes caused by V8 upgrades, and it has lasted for a long time. So someone opened a project named [nan](https://github.com/nodejs/nan) to solve this problem. + +To learn addon development, We recommend reading: [https://github.com/nodejs/node-addon-examples](https://github.com/nodejs/node-addon-examples) in addition to [official document](https://nodejs.org/docs/latest/api/addons.html) + + +## V8 + +We are not talking about V8, but V8 module in Node.js. It's used for opening built-in events and interfaces of V8 engine in Node.js. Because these interfaces are defined by underlying part of V8, so we can't say it's absolutely stable. + +|Interface|Description| +|---|---| +|v8.getHeapStatistics()|Get heap informations| +|v8.getHeapSpaceStatistics()|Get heap space informations | +|v8.setFlagsFromString(string)|Settings V8 options dynamicly| + +### v8.setFlagsFromString(string) + +This method is used to add additional V8 command line flags. But be cautious, modifying the configuration after the VM starts may cause unpredictable behavior, crash, and data loss; Or nothing. + +You can query the available V8 options in the current Node.js environment by `node --v8-options` command. Furthermore, you can also refer to an unofficial maintenance [V8 options list](https://github.com/thlorenz/v8-flags/blob/master/flags-0.11.md). + +Example: + +```javascript +// Print GC events to stdout for one minute. +const v8 = require('v8'); +v8.setFlagsFromString('--trace_gc'); +setTimeout(function() { v8.setFlagsFromString('--notrace_gc'); }, 60e3); +``` + +## Memory snapshots + +Memory snapshots are commonly used to resolve memory leaks. We recommend you use [heapdump](https://github.com/bnoordhuis/node-heapdump) to save memory snapshots, and [devtool](https://github.com/Jam3/devtool) to view memory snapshots. When using heapdump to save memory snapshots, it only contains objects in Node.js (but for [node-inspector](https://github.com/node-inspector/node-inspector), there will be front-end variables in the snapshot). + +For more details about memory leaks and how to resolve them, see: [How to analysis memory leaks in Node.js](https://zhuanlan.zhihu.com/p/25736931?group_id=825001468703674368). + +## CPU profiling + +CPU profiling is commonly used in performance optimization. And there are many third-party tools to do it, but in most cases, the easiest way is using the built-in one in Node.js - [V8 internal profiler](https://github.com/v8/v8/wiki/Using%20V8%E2%80%99s%20internal%20profiler), it can do interval sampling analysis during the execution of the program. + +Using `--prof` to turn on built-in profilling. + +```shell +node --prof app.js +``` + +It will generate one `isolate-0xnnnnnnnnnnnn-v8.log` file in the current run directory after the program runs. + +You can use `--prof-process` to generate a report. + +``` +node --prof-process isolate-0xnnnnnnnnnnnn-v8.log +``` + +And the report is as followed: + +``` +Statistical profiling result from isolate-0x103001200-v8.log, (12042 ticks, 2634 unaccounted, 0 excluded). + + [Shared libraries]: + ticks total nonlib name + 35 0.3% /usr/lib/system/libsystem_platform.dylib + 27 0.2% /usr/lib/system/libsystem_pthread.dylib + 7 0.1% /usr/lib/system/libsystem_c.dylib + 3 0.0% /usr/lib/system/libsystem_kernel.dylib + 1 0.0% /usr/lib/system/libsystem_malloc.dylib + + [JavaScript]: + ticks total nonlib name + 208 1.7% 1.7% Stub: LoadICStub + 187 1.6% 1.6% KeyedLoadIC: A keyed load IC from the snapshot + 104 0.9% 0.9% Stub: VectorStoreICStub + 69 0.6% 0.6% LazyCompile: *emit events.js:136:44 + 68 0.6% 0.6% Builtin: CallFunction_ReceiverIsNotNullOrUndefined + 65 0.5% 0.5% KeyedStoreIC: A keyed store IC from the snapshot {2} + 47 0.4% 0.4% Builtin: CallFunction_ReceiverIsAny + 43 0.4% 0.4% LazyCompile: *storeHeader _http_outgoing.js:312:21 + 34 0.3% 0.3% LazyCompile: *removeListener events.js:315:28 + 33 0.3% 0.3% Stub: RegExpExecStub + 33 0.3% 0.3% LazyCompile: *_addListener events.js:210:22 + 32 0.3% 0.3% Stub: CEntryStub + 32 0.3% 0.3% Builtin: ArgumentsAdaptorTrampoline + 31 0.3% 0.3% Stub: FastNewClosureStub + 30 0.2% 0.3% Stub: InstanceOfStub + ... + + [C++]: + ticks total nonlib name + 460 3.8% 3.8% _mach_port_extract_member + 329 2.7% 2.7% _openat$NOCANCEL + 199 1.7% 1.7% ___bsdthread_register + 136 1.1% 1.1% ___mkdir_extended + 116 1.0% 1.0% node::HandleWrap::Close(v8::FunctionCallbackInfo const&) + 112 0.9% 0.9% void v8::internal::BodyDescriptorBase::IterateBodyImpl(v8::internal::Heap*, v8::internal::HeapObject*, int, int) + 106 0.9% 0.9% _http_parser_execute + 103 0.9% 0.9% _szone_malloc_should_clear + 99 0.8% 0.8% int v8::internal::BinarySearch<(v8::internal::SearchMode)1, v8::internal::DescriptorArray>(v8::internal::DescriptorArray*, v8::internal::Name*, int, int*) + 89 0.7% 0.7% node::TCPWrap::Connect(v8::FunctionCallbackInfo const&) + 86 0.7% 0.7% v8::internal::LookupIterator::State v8::internal::LookupIterator::LookupInRegularHolder(v8::internal::Map*, v8::internal::JSReceiver*) + ... + + [Bottom up (heavy) profile]: + Note: percentage shows a share of a particular caller in the total + amount of its parent calls. + Callers occupying less than 2.0% are not shown. + + ticks parent name + 2634 21.9% UNKNOWN + 764 29.0% LazyCompile: *connect net.js:815:17 + 764 100.0% LazyCompile: ~ net.js:966:30 + 764 100.0% LazyCompile: *_tickCallback internal/process/next_tick.js:87:25 + 193 7.3% LazyCompile: *createWriteReq net.js:732:24 + 101 52.3% LazyCompile: *Socket._writeGeneric net.js:660:42 + 99 98.0% LazyCompile: ~ net.js:667:34 + 99 100.0% LazyCompile: ~g events.js:287:13 + 99 100.0% LazyCompile: *emit events.js:136:44 + 92 47.7% LazyCompile: ~Socket._writeGeneric net.js:660:42 + 91 98.9% LazyCompile: ~ net.js:667:34 + 91 100.0% LazyCompile: ~g events.js:287:13 + 91 100.0% LazyCompile: *emit events.js:136:44 + ... +``` + +|Field|Description| +|---|---| +|ticks|Time slice| +|total|The ratio of the current operation to the total time| +|nonlib|Current ratio of non-System library execution time| + +Coming soon... From dd6ce4f74ff2fb739d5f0a80456411308ff9b09c Mon Sep 17 00:00:00 2001 From: Lellansin Date: Thu, 27 Jul 2017 19:54:30 +0800 Subject: [PATCH 58/98] chore: rename md file --- sections/en-us/common.md | 7 +++++++ sections/en-us/js-basic.md | 7 ------- 2 files changed, 7 insertions(+), 7 deletions(-) create mode 100644 sections/en-us/common.md delete mode 100644 sections/en-us/js-basic.md diff --git a/sections/en-us/common.md b/sections/en-us/common.md new file mode 100644 index 0000000..8a3bdda --- /dev/null +++ b/sections/en-us/common.md @@ -0,0 +1,7 @@ +# Basic + +* `[Common]` Type judgment +* `[Common]` Scope +* `[Common]` Reference +* `[Common]` Memory release +* `[Common]` ES6+ featrues diff --git a/sections/en-us/js-basic.md b/sections/en-us/js-basic.md deleted file mode 100644 index 35f5abc..0000000 --- a/sections/en-us/js-basic.md +++ /dev/null @@ -1,7 +0,0 @@ -# Basic - -* `[Basic]` Type judgment -* `[Basic]` Scope -* `[Basic]` Reference -* `[Basic]` Memory release -* `[Basic]` ES6+ featrues From 3e54feac5d4636e29b273db837b9f217b9a81785 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Thu, 27 Jul 2017 20:08:48 +0800 Subject: [PATCH 59/98] section: add question for event-async.md --- sections/en-us/README.md | 6 ++++++ sections/en-us/event-async.md | 12 ++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/sections/en-us/README.md b/sections/en-us/README.md index b88efdb..6c7bcd1 100644 --- a/sections/en-us/README.md +++ b/sections/en-us/README.md @@ -42,6 +42,12 @@ **Common Problem** +* What's the difference between the Promise's second argument of '.then' function and '.catch' function? [[more]](/sections/en-us/event-async.md#q-1) +* Is the Eventemitter.emit synchronous or asynchronous? [[more]](/sections/en-us/event-async.md#q-2) +* How to judge whether a interface is asynchronous? Is it asynchronous while a callback provided? [[more]](/sections/en-us/event-async.md#q-3) +* Diff among nextTick, setTimeout and setImmediate? [[more]](/sections/en-us/event-async.md#q-4) +* How to implement a Sleep function? [[more]](/sections/en-us/event-async.md#q-5) +* How to implement an async.reduce? [[more]](/sections/en-us/event-async.md#q-6) [View more](/sections/en-us/event-async.md) diff --git a/sections/en-us/event-async.md b/sections/en-us/event-async.md index 432ce20..c634145 100644 --- a/sections/en-us/event-async.md +++ b/sections/en-us/event-async.md @@ -20,7 +20,7 @@ I believe that in the interview, many students have been asked such a question: To learn more basic knowledge about `promise`, we recommend this article. [Promise](http://javascript.ruanyifeng.com/advanced/promise.html#toc9) -> What the difference between the second argument of '.then' function and '.catch' function? +> What's the difference between the second argument of '.then' function and '.catch' function? To distinguish the difference, you can read this article [We have a problem with promises](https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html) @@ -93,7 +93,7 @@ If you don't kown the answers of these questions, you can print the output at lo A class or a Object can get basic `events` methods by extending `EventEmitter` class, and we call it 'emitter', and the callback funciton that emit a kind of event is called as 'listener'. It is diffrent from DOM tree in browser, there are no bubble and capture actions or methods to handle event. ->Whether the emit of Eventemitter is synchronous or asynchronous? +>Is the Eventemitter.emit synchronous or asynchronous? The answer is **synchronous**, there are some description on Node.js documentation: @@ -154,7 +154,7 @@ Pay attention to that some students prefer to monitor the status of certain clas ## Blocking/non-blocking -> how to judge whether a interface is asynchronous? Whether it must be asynchronous if a callback function provided? +> How to judge whether a interface is asynchronous? Is it asynchronous while a callback provided? This is a open question, you can have your own way to judge. @@ -173,7 +173,7 @@ Simply use the callback function is not asynchronous, IO operation may be asynch In Node.js environment javascript code has only one single thread. Only the current code has been excuted, the process will cut into the event loop, and then pop out the next callback function from the event queue to start the implementation of the code. so ① to achieve a sleep function, as long as an infinite loop can block the execution of the entire js process (on how to avoid the colleagues write deadless loop, see the chapter of `test`.) -> how to acheive a sleep function ① +> How to implement a Sleep function? ① ```javascript function sleep(ms) { @@ -187,7 +187,7 @@ Asynchronous in Node.js means an event queue in other thread achived by libuv mo If endless loop logic trigger in your website, the whole process will be blocked, and all request will timeout, asynchronous code will never be excuted, and your website will be crashed. -> How to achieve an async reduce? +> How to implement an async.reduce? You need to konw that reduce is analyze a recursive data structure and through use of a given combining operation, recombine the results of recursively processing its constituent parts, building up a return value. @@ -197,7 +197,7 @@ The writter think there are two kinds of 'asynchronous' in Node.js: `hard async `hard asynchronous` means IO operation or some cases that you need libuv module externally and of course includes `readFileSync` or `execSync`. Because of the single thread feature of Node.js, it is unwise to do some IO operation in synchronous way as it will block the excutation of other code. -`soft asynchronous` is that some asynchronous cases achieved by `setTimeout`. To understand the diffrence between nextTick, setTimeout and setImmediate , you can see this article. [article](https://cnodejs.org/topic/5556efce7cabb7b45ee6bcac) +`soft asynchronous` is that some asynchronous cases achieved by `setTimeout`. To understand the diffrence among nextTick, setTimeout and setImmediate , you can see this article. [article](https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/) From 4517f4db767f61f60533e4004e94eed582bfc864 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Thu, 27 Jul 2017 20:11:30 +0800 Subject: [PATCH 60/98] section: event-async, adjust words --- sections/en-us/event-async.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sections/en-us/event-async.md b/sections/en-us/event-async.md index c634145..ba42d3c 100644 --- a/sections/en-us/event-async.md +++ b/sections/en-us/event-async.md @@ -170,7 +170,7 @@ Simply use the callback function is not asynchronous, IO operation may be asynch > if you have built a website by koa, this website has a interface A, and in some cases, interface A can be the endless loop, unfortunately, if you triggered this endless loop, what will be the impact on your website? -In Node.js environment javascript code has only one single thread. Only the current code has been excuted, the process will cut into the event loop, and then pop out the next callback function from the event queue to start the implementation of the code. so ① to achieve a sleep function, as long as an infinite loop can block the execution of the entire js process (on how to avoid the colleagues write deadless loop, see the chapter of `test`.) +In Node.js environment javascript code has only one single thread. Only the current code has been excuted, the process will cut into the event loop, and then pop out the next callback function from the event queue to start the implementation of the code. so ① to implement a Sleep function, as long as an infinite loop can block the execution of the entire js process (on how to avoid the colleagues write deadless loop, see the chapter of `test`.) > How to implement a Sleep function? ① @@ -197,7 +197,7 @@ The writter think there are two kinds of 'asynchronous' in Node.js: `hard async `hard asynchronous` means IO operation or some cases that you need libuv module externally and of course includes `readFileSync` or `execSync`. Because of the single thread feature of Node.js, it is unwise to do some IO operation in synchronous way as it will block the excutation of other code. -`soft asynchronous` is that some asynchronous cases achieved by `setTimeout`. To understand the diffrence among nextTick, setTimeout and setImmediate , you can see this article. [article](https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/) +`soft asynchronous` is that some asynchronous cases implemented by `setTimeout`. To understand the diffrence among nextTick, setTimeout and setImmediate , you can see this article. [article](https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/) From 935628c42228244d55f45b426ff031582d439086 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Thu, 27 Jul 2017 20:19:39 +0800 Subject: [PATCH 61/98] secion: process.md, add common quesion --- sections/en-us/README.md | 5 +++++ sections/en-us/process.md | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/sections/en-us/README.md b/sections/en-us/README.md index 6c7bcd1..3b9d3f3 100644 --- a/sections/en-us/README.md +++ b/sections/en-us/README.md @@ -61,6 +61,11 @@ **Common Problem** +* What's the current working directory of the process? What's it for? [[more]](/sections/zh-cn/process.md#q-cwd) +* Difference between child_process.fork and fork in POSIX? [[more]](/sections/zh-cn/process.md#q-fork) +* Does the death of parent process or child process affect each other? What is an orphan process? [[more]](/sections/zh-cn/process.md#q-child) +* How does the cluster load balance work? [[more]](/sections/zh-cn/process.md#how-it-works) +* What's daemon process? how to implement? [[more]](/sections/zh-cn/process.md#daemon-process) [View more](/sections/en-us/process.md) diff --git a/sections/en-us/process.md b/sections/en-us/process.md index 14dd729..29a54da 100644 --- a/sections/en-us/process.md +++ b/sections/en-us/process.md @@ -88,7 +88,7 @@ Configuration is a very common problem in development deployments. As usual, the You can specify the configuration by [Setting Environment Variables](http://cn.bing.com/search?q=linux+Setting+Environment+Variable&qs=n&form=QBRE&sp=-1&pq=linux+setting+environment+variable&sc=1-34&sk=&cvid=1027E58E457E42DEB5A4A4E495EEC4A9), then obtain the configuration item by using `process.env`. In addition, you can obtain by reading the configuration file. There are many excellent libraries such as `Dotenv`, ` node-config`, etc. in this field. But when loading the configuration file by using these libraries, it usually encounters a problem with the current working directory. -> What is the current working directory of the process? What is its role? +> What's the current working directory of the process? What's it for? You can obtain the current working directory by using `process.cwd()`. It usually is the directory when the command line starts. It can also be specified at startup. File operations, etc. obtain the file by using the relative path which is relative to the current working directory. @@ -108,7 +108,7 @@ Familiar with basic commands about process, such as top, ps, pstree , etc. Child Process is an important concept in the process. In Node.js, you can use `child_process` module to execute executable files, call commands in command line , such as programs in other languages, etc. You can also execute js code as a sub-process by using this module. The well-known Netease's distributed architecture [pomelo](https://github.com/NetEase/pomelo) is based on the module(not `cluster`) to implement the multi-process distributed architecture. -> What are the differences between child_process.fork and fork in POSIX? +> What're the difference between child_process.fork and fork in POSIX? In Node.js, `child_process.fork()` calls POSIX [fork(2)](http://man7.org/linux/man-pages/man2/fork.2.html). You need manually manage the release of resources in the child process for fork POSIX. You don't need to care about this problem when using `child_process.fork`, beacuse Node.js will automatic release, and provide options whether the child process can survive after the parent process is destroyed. @@ -130,7 +130,7 @@ The exec/execSync method will directly call bash to explain the command. So if t The common interview question is what are the differences between `child.kill` and `child.send`. One is based on the signal system, the other is based on IPC. -> Does the parent process or child process death affect each other? What is an orphan process? +> Does the death of parent process or child process affect each other? What is an orphan process? The death of a child process will not affect the parent process. When the child process dies (the last thread of the thread group, usually when the "lead" thread dies), it will send a death signal to its parent process. On the other hand, when the parent process dies, by default, the child process will follow the death. But at this time, if the child process is in the operational state, dead state, etc., it will be adopted by process identifier 1(the init system process) and become an orphaned process. In addition, when the child process dies("terminated" state), the parent process does not call `wait()` or `waitpid()` to return the child's infomation in time, there is a `PCB` remaining in the process table. The child process is called a zombie process. From 1ff67adb6b656c07986e318064e58be7f227803c Mon Sep 17 00:00:00 2001 From: Lellansin Date: Thu, 27 Jul 2017 20:29:25 +0800 Subject: [PATCH 62/98] section: add io.md quesions --- sections/en-us/README.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/sections/en-us/README.md b/sections/en-us/README.md index 3b9d3f3..e9d1e8c 100644 --- a/sections/en-us/README.md +++ b/sections/en-us/README.md @@ -61,11 +61,11 @@ **Common Problem** -* What's the current working directory of the process? What's it for? [[more]](/sections/zh-cn/process.md#q-cwd) -* Difference between child_process.fork and fork in POSIX? [[more]](/sections/zh-cn/process.md#q-fork) -* Does the death of parent process or child process affect each other? What is an orphan process? [[more]](/sections/zh-cn/process.md#q-child) -* How does the cluster load balance work? [[more]](/sections/zh-cn/process.md#how-it-works) -* What's daemon process? how to implement? [[more]](/sections/zh-cn/process.md#daemon-process) +* What's the current working directory of the process? What's it for? [[more]](/sections/en-us/process.md#q-cwd) +* Difference between child_process.fork and fork in POSIX? [[more]](/sections/en-us/process.md#q-fork) +* Does the death of parent process or child process affect each other? What is an orphan process? [[more]](/sections/en-us/process.md#q-child) +* How does the cluster load balance work? [[more]](/sections/en-us/process.md#how-it-works) +* What's daemon process? how to implement? [[more]](/sections/en-us/process.md#daemon-process) [View more](/sections/en-us/process.md) @@ -82,6 +82,13 @@ **Common Problem** +* What does Buffer for? Can we change the buffer's size? [[more]](/sections/en-us/io.md#buffer) +* What's the highWaterMark & drain event of Stream? What's their relation? [[more]](/sections/en-us/io.md#buffer-2) +* What's Stream.pipe for? Is it make copy or pass object while piping? [[more]](/sections/en-us/io.md#pipe) +* What is stdio, stdout, stderr and file descriptor? [[more]](/sections/en-us/io.md#file) +* Is console.log asynchronous? How to implement console.log? [[more]](/sections/en-us/io.md#console) +* How to get user input synchronously? [[more]](/sections/en-us/io.md#how-to-get-user-input-synchronizely) +* How to implement 'Readline'? [[more]](/sections/en-us/io.md#readline) [View more](/sections/en-us/io.md) From 403776ed6ac0343b3c08d720319aecc49debd70a Mon Sep 17 00:00:00 2001 From: Lellansin Date: Thu, 27 Jul 2017 20:34:39 +0800 Subject: [PATCH 63/98] secion: add os.md quesions --- sections/en-us/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sections/en-us/README.md b/sections/en-us/README.md index e9d1e8c..eb01ec9 100644 --- a/sections/en-us/README.md +++ b/sections/en-us/README.md @@ -117,6 +117,10 @@ **Common Problem** +* What's TTY? How to check if terminal is TTY? [[more]](/sections/en-us/os.md#tty) +* Is there different among operating system's EOL(end of line)? [[more]](/sections/en-us/os.md#os) +* What is system load? how to check it? [[more]](/sections/en-us/os.md#load) +* What's ulimit for? [[more]](/sections/en-us/os.md#ulimit) [View more](/sections/en-us/os.md) From 8afa5235c0f5e40e0439a9e462092a41deb349a8 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Thu, 27 Jul 2017 20:46:02 +0800 Subject: [PATCH 64/98] section: error.md, add questions --- sections/en-us/README.md | 9 ++++++++- sections/en-us/error.md | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/sections/en-us/README.md b/sections/en-us/README.md index eb01ec9..ef9d9ae 100644 --- a/sections/en-us/README.md +++ b/sections/en-us/README.md @@ -124,7 +124,7 @@ [View more](/sections/en-us/os.md) -## [Error & Debug](/sections/en-us/error.md) +## [Error handle & Debug](/sections/en-us/error.md) * `[Doc]` Errors * `[Doc]` Domain @@ -136,6 +136,13 @@ **Common Problem** +* How to handle unexpected errors? With try/catch, domains or something eles? [[more]](/sections/en-us/error.md#q-handle-error) +* What is `uncaughtException` event? when shoud we use it? [[more]](/sections/en-us/error.md#uncaughtexception) +* What is domain's principle? why domain is deprecated? [[more]](/sections/en-us/error.md#domain) +* What's defensive programming? how about 'let it crash'? +* Why we need error-first callback? why there are callback not error-first, such as http.createServer? +* Why there are errors can't location? how to locate accurately? [[more]](/sections/en-us/error.md#error-stack-is-missing) +* What cause memory leak? how to locate and analyse it? [[more]](/sections/en-us/error.md#memory-snapshots) [View more](/sections/en-us/error.md) diff --git a/sections/en-us/error.md b/sections/en-us/error.md index 79e1f57..c36bde0 100644 --- a/sections/en-us/error.md +++ b/sections/en-us/error.md @@ -1,4 +1,4 @@ -# Error handling/Debug/Optimization +# Error handle & Debug * [`[Doc]` Errors](/sections/en-us/error.md#errors) * [`[Doc]` Domain](/sections/en-us/error.md#domain) From 144b2f48d099dcb2cef2348ffff330bf74edd6c0 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Thu, 27 Jul 2017 20:50:10 +0800 Subject: [PATCH 65/98] section: util.md, add questions --- sections/en-us/README.md | 3 +++ sections/en-us/util.md | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/sections/en-us/README.md b/sections/en-us/README.md index ef9d9ae..35526ba 100644 --- a/sections/en-us/README.md +++ b/sections/en-us/README.md @@ -169,6 +169,9 @@ **Common Problem** +* How does HTTP pass `let arr = [1,2,3,4]` to the server by GET method? [[more]](/sections/en-us/util.md#get-param) +* How to implement util.inherits in Node.js? [[more]](/sections/en-us/util.md#utilinherits) +* How do I get all the file names under a folder? [[more]](/sections/en-us/util.md#q-traversal) [View more](/sections/en-us/util.md) diff --git a/sections/en-us/util.md b/sections/en-us/util.md index 5bb71f5..b7a376f 100644 --- a/sections/en-us/util.md +++ b/sections/en-us/util.md @@ -128,7 +128,7 @@ Most of them can be used as an interview to ask how to implement. ### util.inherits -> how to implement util.inherits in Node.js? +> How to implement util.inherits in Node.js? https://github.com/nodejs/node/blob/v7.6.0/lib/util.js#L960 From 640634434ea4c20287810bdbb30648062d4e9833 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Thu, 27 Jul 2017 21:05:37 +0800 Subject: [PATCH 66/98] section: rename js-basic -> common --- sections/zh-cn/README.md | 20 ++++++++++---------- sections/zh-cn/{js-basic.md => common.md} | 10 +++++----- 2 files changed, 15 insertions(+), 15 deletions(-) rename sections/zh-cn/{js-basic.md => common.md} (95%) diff --git a/sections/zh-cn/README.md b/sections/zh-cn/README.md index 462569b..7aad507 100644 --- a/sections/zh-cn/README.md +++ b/sections/zh-cn/README.md @@ -16,24 +16,24 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 整体上大纲列举的并不是很全面, 细节上覆盖率不高, 很多讨论只是点到即止, 希望大家带着问题去思考. -## [Js 基础问题](/sections/zh-cn/js-basic.md) +## [Js 基础问题](/sections/zh-cn/common.md) > 与前端 Js 不同, 后端是直面服务器的, 更加偏向内存方面. -* [`[Basic]` 类型判断](/sections/zh-cn/js-basic.md#类型判断) -* [`[Basic]` 作用域](/sections/zh-cn/js-basic.md#作用域) -* [`[Basic]` 引用传递](/sections/zh-cn/js-basic.md#引用传递) -* [`[Basic]` 内存释放](/sections/zh-cn/js-basic.md#内存释放) -* [`[Basic]` ES6 新特性](/sections/zh-cn/js-basic.md#es6-新特性) +* [`[Basic]` 类型判断](/sections/zh-cn/common.md#类型判断) +* [`[Basic]` 作用域](/sections/zh-cn/common.md#作用域) +* [`[Basic]` 引用传递](/sections/zh-cn/common.md#引用传递) +* [`[Basic]` 内存释放](/sections/zh-cn/common.md#内存释放) +* [`[Basic]` ES6 新特性](/sections/zh-cn/common.md#es6-新特性) **常见问题** -* js 中什么类型是引用传递, 什么类型是值传递? 如何将值类型的变量以引用的方式传递? [[more]](/sections/zh-cn/js-basic.md#q-value) +* js 中什么类型是引用传递, 什么类型是值传递? 如何将值类型的变量以引用的方式传递? [[more]](/sections/zh-cn/common.md#q-value) * js 中, 0.1 + 0.2 === 0.3 是否为 true ? 在不知道浮点数位数时应该怎样判断两个浮点数之和与第三数是否相等? -* const 定义的 Array 中间元素能否被修改? 如果可以, 那 const 修饰对象的意义是? [[more]](/sections/zh-cn/js-basic.md#q-const) -* JavaScript 中不同类型以及不同环境下变量的内存都是何时释放? [[more]](/sections/zh-cn/js-basic.md#q-mem) +* const 定义的 Array 中间元素能否被修改? 如果可以, 那 const 修饰对象的意义是? [[more]](/sections/zh-cn/common.md#q-const) +* JavaScript 中不同类型以及不同环境下变量的内存都是何时释放? [[more]](/sections/zh-cn/common.md#q-mem) -[阅读更多](/sections/zh-cn/js-basic.md) +[阅读更多](/sections/zh-cn/common.md) ## [模块](/sections/zh-cn/module.md) diff --git a/sections/zh-cn/js-basic.md b/sections/zh-cn/common.md similarity index 95% rename from sections/zh-cn/js-basic.md rename to sections/zh-cn/common.md index 36010a3..e3161ed 100644 --- a/sections/zh-cn/js-basic.md +++ b/sections/zh-cn/common.md @@ -1,10 +1,10 @@ # Javascript 基础问题 -* [`[Basic]` 类型判断](/sections/zh-cn/js-basic.md#类型判断) -* [`[Basic]` 作用域](/sections/zh-cn/js-basic.md#作用域) -* [`[Basic]` 引用传递](/sections/zh-cn/js-basic.md#引用传递) -* [`[Basic]` 内存释放](/sections/zh-cn/js-basic.md#内存释放) -* [`[Basic]` ES6 新特性](/sections/zh-cn/js-basic.md#es6-新特性) +* [`[Basic]` 类型判断](/sections/zh-cn/common.md#类型判断) +* [`[Basic]` 作用域](/sections/zh-cn/common.md#作用域) +* [`[Basic]` 引用传递](/sections/zh-cn/common.md#引用传递) +* [`[Basic]` 内存释放](/sections/zh-cn/common.md#内存释放) +* [`[Basic]` ES6 新特性](/sections/zh-cn/common.md#es6-新特性) ## 简述 From a12604a214d5ddd56432c73fb495e9352c7c8901 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Thu, 27 Jul 2017 21:05:54 +0800 Subject: [PATCH 67/98] doc: update en-us/readme.md --- sections/en-us/README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sections/en-us/README.md b/sections/en-us/README.md index 35526ba..68a8e1f 100644 --- a/sections/en-us/README.md +++ b/sections/en-us/README.md @@ -1,8 +1,5 @@ ![ElemeFE-background](/assets/ElemeFE-background.png) -## Movition - - ## Guide @@ -204,3 +201,4 @@ ## Final +Current repo is translating, you can report on [issues](https://github.com/ElemeFE/node-interview/issues) freely if there is typo or reading problem. From c4ba53cbf2ff15360a2d001f0c566bfeb6c3eb94 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Thu, 27 Jul 2017 23:24:39 +0800 Subject: [PATCH 68/98] section: event-async, add link about event loop --- sections/en-us/event-async.md | 2 +- sections/zh-cn/event-async.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sections/en-us/event-async.md b/sections/en-us/event-async.md index ba42d3c..81e0458 100644 --- a/sections/en-us/event-async.md +++ b/sections/en-us/event-async.md @@ -225,7 +225,7 @@ The writter think there are two kinds of 'asynchronous' in Node.js: `hard async └───────────────────────┘ ``` -To know more about event loop, Timers, nextTick,we recommend the Node.js documentation and this article: [The Node.js Event Loop, Timers, and process.nextTick()](https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/). +To know more about event loop, Timers, nextTick, we recommend the Node.js documentation, [*The Node.js Event Loop, Timers, and process.nextTick()*](https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/) and [*Tasks, microtasks, queues and schedules*](https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/). ## Paraller/Concurrent diff --git a/sections/zh-cn/event-async.md b/sections/zh-cn/event-async.md index 8c55fe7..8e7d1bd 100644 --- a/sections/zh-cn/event-async.md +++ b/sections/zh-cn/event-async.md @@ -208,7 +208,7 @@ function sleep(ms) { └───────────────────────┘ ``` -关于事件循环, Timers 以及 nextTick 的关系详见官方文档 The Node.js Event Loop, Timers, and process.nextTick(): [英文](https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/), [论坛中文讨论](https://cnodejs.org/topic/57d68794cb6f605d360105bf) +关于事件循环, Timers 以及 nextTick 的关系详见官方文档 The Node.js Event Loop, Timers, and process.nextTick(): [英文](https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/), [论坛中文讨论](https://cnodejs.org/topic/57d68794cb6f605d360105bf) 以及 [Tasks, microtasks, queues and schedules](https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/) ## 并行/并发 From 9932901e2c55f9285128c5d04486f0cc249e6fa9 Mon Sep 17 00:00:00 2001 From: matrixbirds Date: Mon, 31 Jul 2017 09:28:59 +0800 Subject: [PATCH 69/98] section: network, fix typo (#39) --- sections/zh-cn/network.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sections/zh-cn/network.md b/sections/zh-cn/network.md index e8f1605..0cf0d7e 100644 --- a/sections/zh-cn/network.md +++ b/sections/zh-cn/network.md @@ -49,7 +49,7 @@ ### 可靠传输 -为每一个发送的数据包分配一个序列号(SYN, Synchronise packet), 每一个包在对方收到后要返回一个对应的应答数据包(ACK, Acknowledgedgement),. 发送方如果发现某个包没有被对方 ACK, 则会选择重发. 接收方通过 SYN 序号来保证数据的不会乱序(reordering), 发送方通过 ACK 来保证数据不缺漏, 以此参考决定是否重传. 关于具体的序号计算, 丢包时的重传机制等可以参见阅读陈皓的 [《TCP的那些事儿(上)》](http://coolshell.cn/articles/11564.html) 此处不做赘述. +为每一个发送的数据包分配一个序列号(SYN, Synchronize packet), 每一个包在对方收到后要返回一个对应的应答数据包(ACK, Acknowledgement),. 发送方如果发现某个包没有被对方 ACK, 则会选择重发. 接收方通过 SYN 序号来保证数据的不会乱序(reordering), 发送方通过 ACK 来保证数据不缺漏, 以此参考决定是否重传. 关于具体的序号计算, 丢包时的重传机制等可以参见阅读陈皓的 [《TCP的那些事儿(上)》](http://coolshell.cn/articles/11564.html) 此处不做赘述. ### window From 32d782076caf09a70e7dccd76a6a300466e66dfc Mon Sep 17 00:00:00 2001 From: Lellansin Date: Mon, 31 Jul 2017 09:32:11 +0800 Subject: [PATCH 70/98] section: network, http patch is not idempotent --- sections/zh-cn/network.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sections/zh-cn/network.md b/sections/zh-cn/network.md index 0cf0d7e..ae0654f 100644 --- a/sections/zh-cn/network.md +++ b/sections/zh-cn/network.md @@ -155,7 +155,7 @@ methods|CRUD|幂等|缓存 GET|Read|✓|✓ POST|Create|| PUT|Update/Replace|✓ -PATCH|Update/Modify|✓ +PATCH|Update/Modify|| DELETE|Delete|✓ > GET 和 POST 有什么区别? From 6f103d8506e8d9873392a21408b6ab037d2fbf16 Mon Sep 17 00:00:00 2001 From: huangxiaohuai <30619306+huangxiaohuai@users.noreply.github.com> Date: Fri, 4 Aug 2017 12:39:19 +0800 Subject: [PATCH 71/98] chores: fix comma --- sections/zh-cn/network.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sections/zh-cn/network.md b/sections/zh-cn/network.md index ae0654f..3dfd699 100644 --- a/sections/zh-cn/network.md +++ b/sections/zh-cn/network.md @@ -297,9 +297,9 @@ DNS 服务主要基于 UDP, 这里简单介绍 Node.js 实现的接口中的两 > hosts 文件是什么? 什么叫 DNS 本地解析? -hosts 文件是个没有扩展名的系统文件,其作用就是将网址域名与其对应的 IP 地址建立一个关联“数据库”,当用户在浏览器中输入一个需要登录的网址时,系统会首先自动从 hosts 文件中寻找对应的IP地址。 +hosts 文件是个没有扩展名的系统文件, 其作用就是将网址域名与其对应的 IP 地址建立一个关联“数据库”, 当用户在浏览器中输入一个需要登录的网址时, 系统会首先自动从 hosts 文件中寻找对应的IP地址. -当我们访问一个域名时,实际上需要的是访问对应的 IP 地址。这时候,获取 IP 地址的方式,先是读取浏览器缓存,如果未命中 => 接着读取本地 hosts 文件,如果还是未命中 => 则向 DNS 服务器发送请求获取。在向 DNS 服务器获取 IP 地址之前的行为,叫做 DNS 本地解析。 +当我们访问一个域名时, 实际上需要的是访问对应的 IP 地址. 这时候, 获取 IP 地址的方式, 先是读取浏览器缓存, 如果未命中 => 接着读取本地 hosts 文件, 如果还是未命中 => 则向 DNS 服务器发送请求获取. 在向 DNS 服务器获取 IP 地址之前的行为, 叫做 DNS 本地解析. ## ZLIB From 6db78a8bb2b2e7646b072b6717395a8722e1c449 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Thu, 10 Aug 2017 18:56:45 +0800 Subject: [PATCH 72/98] fix: section, typos --- sections/en-us/README.md | 2 +- sections/en-us/common.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sections/en-us/README.md b/sections/en-us/README.md index 68a8e1f..efb06f8 100644 --- a/sections/en-us/README.md +++ b/sections/en-us/README.md @@ -11,7 +11,7 @@ * `[Common]` Scope * `[Common]` Reference * `[Common]` Memory release -* `[Common]` ES6+ featrues +* `[Common]` ES6+ features **Common Problem** diff --git a/sections/en-us/common.md b/sections/en-us/common.md index 8a3bdda..918ba33 100644 --- a/sections/en-us/common.md +++ b/sections/en-us/common.md @@ -4,4 +4,4 @@ * `[Common]` Scope * `[Common]` Reference * `[Common]` Memory release -* `[Common]` ES6+ featrues +* `[Common]` ES6+ features From 66dca6c74bfbc8387f8ada75a029a795430b8430 Mon Sep 17 00:00:00 2001 From: Yaozong Liu <750188453@qq.com> Date: Tue, 15 Aug 2017 05:09:00 -0500 Subject: [PATCH 73/98] chores: fix type --- sections/zh-cn/event-async.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sections/zh-cn/event-async.md b/sections/zh-cn/event-async.md index 8e7d1bd..d59ec8c 100644 --- a/sections/zh-cn/event-async.md +++ b/sections/zh-cn/event-async.md @@ -222,6 +222,6 @@ function sleep(ms) { 并行 (Parallel) = 2 队列对应 2 咖啡机. -Node.js 通过事件循环来挨个抽取实践队列中的一个个 Task 执行, 从而避免了传统的多线程情况下 `2个队列对应 1个咖啡机` 的时候上线文切换以及资源争抢/同步的问题, 所以获得了高并发的成就. +Node.js 通过事件循环来挨个抽取事件队列中的一个个 Task 执行, 从而避免了传统的多线程情况下 `2个队列对应 1个咖啡机` 的时候上线文切换以及资源争抢/同步的问题, 所以获得了高并发的成就. 至于在 node 中并行, 你可以通过 cluster 来再添加一个咖啡机. From 5068680986355e465e06eb885e1d013c0478b3c6 Mon Sep 17 00:00:00 2001 From: Shifeng Chen Date: Tue, 22 Aug 2017 10:42:25 +0800 Subject: [PATCH 74/98] chores: fix tpye (#46) --- sections/zh-cn/common.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sections/zh-cn/common.md b/sections/zh-cn/common.md index e3161ed..ca36d0b 100644 --- a/sections/zh-cn/common.md +++ b/sections/zh-cn/common.md @@ -11,7 +11,7 @@ 与前端 Js 不同, 后端方面除了SSR/爬虫之外很少会接触 DOM, 所以关于 DOM 方面的各种知识基本不会讨论. 前端很少碰到内存问题, 但是后端几乎是直面服务器内存的, 更加偏向内存方面, 对于一些更基础的问题也会更加关注. -不过由于 Js 方面的知识点是在太多, 《Javascript 权威指南》的厚度完全可以说明问题, 所以本教程并不会完整的带大家过一遍 Js 的基础问题, 只是简单列举一些饿了么在面试 Node.js 程序的时候通常会问的一些 Js 基础问题, 有的详细的地方会直接留下书名或者博文链接, 以供大家深入了解, 这里就不赘述了. +不过由于 Js 方面的知识点实在太多, 《Javascript 权威指南》的厚度完全可以说明问题, 所以本教程并不会完整的带大家过一遍 Js 的基础问题, 只是简单列举一些饿了么在面试 Node.js 程序的时候通常会问的一些 Js 基础问题, 有的详细的地方会直接留下书名或者博文链接, 以供大家深入了解, 这里就不赘述了. > 希望大家更多的是带着本文抛出的问题去学习, 而不是期待本文把所有答案列出来. @@ -25,7 +25,7 @@ Javascript 的类型判断其实是个挺折磨人的话题, 不然也不会有 ## 作用域 -在面试时, 作用域并不是一个很好问的知识点, 一般会问的是 `es6 中 let 与 var 的区别`, 或者列举代码, 然后通过对代码的解读来看你对作用域的掌握比较方便. +在面试时, 作用域并不是一个很好问的知识点, 一般会问的是 `es6 中 let 与 var 的区别`, 或者列举代码, 然后通过对代码的解读来看你对作用域的掌握比较方便. 印象中那本 [《你不知道的 Javascript》](https://book.douban.com/subject/26351021/) 讲的很好了, 有兴趣可以去看那本书, 以下是该书的部分目录: From 5fe6a6784f7edd99cef2a7eafa6b13f7b5bf4c38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B4=BE=E9=A1=BA=E5=90=8D=28shunming=20jia=29?= Date: Mon, 28 Aug 2017 12:59:13 +0800 Subject: [PATCH 75/98] chores: fix typo (#47) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 多了一个`了`... --- sections/zh-cn/network.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sections/zh-cn/network.md b/sections/zh-cn/network.md index 3dfd699..6baf80d 100644 --- a/sections/zh-cn/network.md +++ b/sections/zh-cn/network.md @@ -133,7 +133,7 @@ UDP socket 支持 n 对 m 的连接状态, 在[官方文档](https://nodejs.org/ ## HTTP -目前世界上运行最良好的分布式集群, 莫过于当前的万维网了 (http servers) 了. 目前前端工程师也都是靠 HTTP 协议吃饭的, 所以 2-3 年的前端同学都应该对 HTTP 有比较深的理解了, 所以这里不做太多的赘述. 推荐书籍[《图解HTTP》](https://www.amazon.cn/%E5%9B%BE%E4%B9%A6/dp/B00JTQK1L4/), 博客[HTTP 协议入门](http://www.ruanyifeng.com/blog/2016/08/http.html). +目前世界上运行最良好的分布式集群, 莫过于当前的万维网 (http servers) 了. 目前前端工程师也都是靠 HTTP 协议吃饭的, 所以 2-3 年的前端同学都应该对 HTTP 有比较深的理解了, 所以这里不做太多的赘述. 推荐书籍[《图解HTTP》](https://www.amazon.cn/%E5%9B%BE%E4%B9%A6/dp/B00JTQK1L4/), 博客[HTTP 协议入门](http://www.ruanyifeng.com/blog/2016/08/http.html). 另外最近几年开始大家对 HTTP 的面试的考察也渐渐偏向[理解 RESTful 架构](http://www.ruanyifeng.com/blog/2011/09/restful.html). 简单的说, RESTful 是把每个 URI 当做资源 (Resources), 通过 method 作为动词来对资源做不同的动作, 然后服务器返回 status 来得知资源状态的变化 (State Transfer); From f5196e70a6bb5f3ba2d94d18742ad4184a0ba043 Mon Sep 17 00:00:00 2001 From: dreamer Date: Mon, 9 Oct 2017 21:27:27 -0500 Subject: [PATCH 76/98] chores: remove repeat words (#50) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit “执行” 这个词在这里出现了俩次 --- sections/zh-cn/process.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sections/zh-cn/process.md b/sections/zh-cn/process.md index 80db4cd..10bcdfb 100644 --- a/sections/zh-cn/process.md +++ b/sections/zh-cn/process.md @@ -161,7 +161,7 @@ console.log('hello'); // | | 都执行了 在上述代码中 numCPUs 虽然是全局变量但是, 在父进程中修改它, 子进程中并不会改变, 因为父进程与子进程是完全独立的两个空间. 他们所谓的共有仅仅只是都执行了, 并不是同一份. -你可以把父进程执行的部分当做 `a.js`, 子进程执行的部分当做 `b.js`, 你可以把他们想象成是先执行了 `node a.js` 然后 cluster.fork 了几次, 就执行执行了几次 `node b.js`. 而 cluster 模块则是二者之间的一个桥梁, 你可以通过 cluster 提供的方法, 让其二者之间进行沟通交流. +你可以把父进程执行的部分当做 `a.js`, 子进程执行的部分当做 `b.js`, 你可以把他们想象成是先执行了 `node a.js` 然后 cluster.fork 了几次, 就执行了几次 `node b.js`. 而 cluster 模块则是二者之间的一个桥梁, 你可以通过 cluster 提供的方法, 让其二者之间进行沟通交流. ### How It Works From d5d99162bf2e5f9f3dc787bda4d6b5608295c78b Mon Sep 17 00:00:00 2001 From: Lellansin Date: Sat, 28 Oct 2017 20:51:28 +0800 Subject: [PATCH 77/98] section: common, update about memory --- sections/en-us/common.md | 6 ++++++ sections/zh-cn/common.md | 25 ++++++++----------------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/sections/en-us/common.md b/sections/en-us/common.md index 918ba33..bf57d96 100644 --- a/sections/en-us/common.md +++ b/sections/en-us/common.md @@ -5,3 +5,9 @@ * `[Common]` Reference * `[Common]` Memory release * `[Common]` ES6+ features + +## Summary + +With diffrence between frontend, there are few chance to work with DOM in backend. So we won't discuss about it. Unlike browser side, backend is directly facing memory, while the work duration is calculated by year, we do more concern about the machine side. + + diff --git a/sections/zh-cn/common.md b/sections/zh-cn/common.md index ca36d0b..5759103 100644 --- a/sections/zh-cn/common.md +++ b/sections/zh-cn/common.md @@ -9,7 +9,7 @@ ## 简述 -与前端 Js 不同, 后端方面除了SSR/爬虫之外很少会接触 DOM, 所以关于 DOM 方面的各种知识基本不会讨论. 前端很少碰到内存问题, 但是后端几乎是直面服务器内存的, 更加偏向内存方面, 对于一些更基础的问题也会更加关注. +与前端 Js 不同, 后端方面除了SSR/爬虫之外很少会接触 DOM, 所以关于 DOM 方面的各种知识基本不会讨论. 浏览器端除了图形业务外很少碰到内存问题, 但是后端几乎是直面服务器内存的, 更加偏向内存方面, 对于一些更基础的问题也会更加关注. 不过由于 Js 方面的知识点实在太多, 《Javascript 权威指南》的厚度完全可以说明问题, 所以本教程并不会完整的带大家过一遍 Js 的基础问题, 只是简单列举一些饿了么在面试 Node.js 程序的时候通常会问的一些 Js 基础问题, 有的详细的地方会直接留下书名或者博文链接, 以供大家深入了解, 这里就不赘述了. @@ -86,26 +86,17 @@ while(true) 思考完之后可以尝试找找别的情况如何爆掉 V8 的内存. 以及来聊聊内存泄漏? ```javascript -var theThing = null -var replaceThing = function () { - var originalThing = theThing - var unused = function () { - if (originalThing) - console.log("hi") +function out() { + const bigData = new Buffer(100); + inner = function () { + void bigData; } - theThing = { - longStr: new Array(1000000).join('*'), - someMethod: function () { - console.log(someMessage) - } - }; -}; -setInterval(replaceThing, 1000) +} ``` -比如上述情况中 `unused` 的函数中持有了 `originalThing` 的引用, 使得每次旧的对象不会释放从而导致内存泄漏 (例子出自[《Node.js 垃圾回收》](https://eggggger.xyz/2016/10/22/node-gc/)) +闭包会引用到父级函数中的变量,如果闭包未释放,就会导致内存泄漏。上面例子是 inner 直接挂在了 root 上,从而导致内存泄漏(bigData 不会释放)。详见[《如何分析 Node.js 中的内存泄漏》](https://zhuanlan.zhihu.com/p/25736931) -当然对于一些高水平的同学, 要求能清楚的了解 v8 内存 GC 的机制, 懂得内存快照等 (之后会在`调试/优化`的小结中讨论) 了. 比如 V8 中不同类型的数据存储的位置, 在内存释放的时候不同区域的不同策略等等. +对于一些高水平的同学, 要求能清楚的了解 v8 内存 GC 的机制, 懂得内存快照等 (之后会在`调试/优化`的小结中讨论) 了. 比如 V8 中不同类型的数据存储的位置, 在内存释放的时候不同区域的不同策略等等. ## ES6 新特性 From bd5caea53802e13d5c22aaf97a43aaa69a124a8b Mon Sep 17 00:00:00 2001 From: Lellansin Date: Sat, 28 Oct 2017 21:16:05 +0800 Subject: [PATCH 78/98] chores: common, adjust words --- sections/zh-cn/common.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sections/zh-cn/common.md b/sections/zh-cn/common.md index 5759103..f53c968 100644 --- a/sections/zh-cn/common.md +++ b/sections/zh-cn/common.md @@ -19,15 +19,15 @@ Javascript 的类型判断其实是个挺折磨人的话题, 不然也不会有 Typescript 出现了. 在类型判断的问题上, 基础上 推荐阅读 [lodash](https://github.com/lodash/lodash) 的源代码. -这类问题一般只是简单的开场, 不会因为说你不知道 `undefined == null` 的结果是 `true` 就一票否决一个人. 只是根据个人经验看来,这个问题答不清楚的有不小的概率属于基础较差. 如果你对这种问题没有任何概念, 也许要反思一下是不是该找本书过一下 Js 的基础了. +这类问题一般只是面试过程中简单的开场, 当然不会因为你不知道 `undefined == null` 的结果是 `true` 就一票否决一个人. 只是根据个人经验看来,这个问题答不清楚的有不小的概率属于基础较差. 如果你对这种问题没有任何概念, 也许要反思一下是不是该找本书过一下 Js 的基础了. -另外在这个问题上, 对使用 TypeScript 以及 flow 同学会有一定的加分. +另外在这个问题上, 对熟悉 TypeScript 以及 flow 同学会有一定的加分. ## 作用域 -在面试时, 作用域并不是一个很好问的知识点, 一般会问的是 `es6 中 let 与 var 的区别`, 或者列举代码, 然后通过对代码的解读来看你对作用域的掌握比较方便. +在面试时, 作用域并不是一个很好问的知识点但实际上这 JavaScript 中一个很重要的点. 饿厂一般开篇会问的是 `es6 中 let 与 var 的区别` 这一类问题, 或者列举代码, 然后通过对代码的解读来看你对作用域的掌握. -印象中那本 [《你不知道的 Javascript》](https://book.douban.com/subject/26351021/) 讲的很好了, 有兴趣可以去看那本书, 以下是该书的部分目录: +关于作用域的问题[《你不知道的 Javascript》](https://book.douban.com/subject/26351021/) 讲的很好, 推荐细读, 以下是该书的部分目录,各位可以感受一下: * 第1章 作用域是什么 * 第2章 词法作用域 From 24e54453a08aa8d4b0e5d1d7a85d51025fd5539e Mon Sep 17 00:00:00 2001 From: Lellansin Date: Sat, 28 Oct 2017 21:27:06 +0800 Subject: [PATCH 79/98] chores: update javascript -> JavaScript --- sections/en-us/io.md | 4 ++-- sections/zh-cn/common.md | 22 +++++++++++----------- sections/zh-cn/event-async.md | 2 +- sections/zh-cn/io.md | 4 ++-- sections/zh-cn/network.md | 2 +- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/sections/en-us/io.md b/sections/en-us/io.md index 3d6ca6c..7767c54 100644 --- a/sections/en-us/io.md +++ b/sections/en-us/io.md @@ -142,7 +142,7 @@ Class| Scenario |Overrided method ### Object mode -The stream created by the Node API can only manipulate strings or buffer objects. But the implementation of the stream can be based on other types of Javascript(Except for null, it has a special meaning in stream). This stream is in the "object mode (objectMode)". +The stream created by the Node API can only manipulate strings or buffer objects. But the implementation of the stream can be based on other types of JavaScript(Except for null, it has a special meaning in stream). This stream is in the "object mode (objectMode)". You can generate an object-mode stream by providing the `objectMode` parameter when creating a stream object. It is not safe to attempt to convert an existing stream to object mode. ### Buffer @@ -254,7 +254,7 @@ BOM stdio (standard input output), includes stdin, stdout and stderr. Corresponding to `process.stdin` (Readable),` process.stdout` (Writable) and `process.stderr` (Writable) respectively in Node.js. -The output function is the first function that everyone needs to learn when learning a programming language. Such as `printf("hello, world!");` of C language, `print 'hello, world!'` of python/ruby and `console.log('hello, world!');` in Javascript. +The output function is the first function that everyone needs to learn when learning a programming language. Such as `printf("hello, world!");` of C language, `print 'hello, world!'` of python/ruby and `console.log('hello, world!');` in JavaScript. Here is the implementation of such an output function in the C language pseudo-code: diff --git a/sections/zh-cn/common.md b/sections/zh-cn/common.md index f53c968..04af4e2 100644 --- a/sections/zh-cn/common.md +++ b/sections/zh-cn/common.md @@ -1,4 +1,4 @@ -# Javascript 基础问题 +# JavaScript 基础问题 * [`[Basic]` 类型判断](/sections/zh-cn/common.md#类型判断) * [`[Basic]` 作用域](/sections/zh-cn/common.md#作用域) @@ -11,13 +11,13 @@ 与前端 Js 不同, 后端方面除了SSR/爬虫之外很少会接触 DOM, 所以关于 DOM 方面的各种知识基本不会讨论. 浏览器端除了图形业务外很少碰到内存问题, 但是后端几乎是直面服务器内存的, 更加偏向内存方面, 对于一些更基础的问题也会更加关注. -不过由于 Js 方面的知识点实在太多, 《Javascript 权威指南》的厚度完全可以说明问题, 所以本教程并不会完整的带大家过一遍 Js 的基础问题, 只是简单列举一些饿了么在面试 Node.js 程序的时候通常会问的一些 Js 基础问题, 有的详细的地方会直接留下书名或者博文链接, 以供大家深入了解, 这里就不赘述了. +不过由于 Js 方面的知识点实在太多, 《JavaScript 权威指南》的厚度完全可以说明问题, 所以本教程并不会完整的带大家过一遍 Js 的基础问题, 只是简单列举一些饿了么在面试 Node.js 程序的时候通常会问的一些 Js 基础问题, 有的详细的地方会直接留下书名或者博文链接, 以供大家深入了解, 这里就不赘述了. > 希望大家更多的是带着本文抛出的问题去学习, 而不是期待本文把所有答案列出来. ## 类型判断 -Javascript 的类型判断其实是个挺折磨人的话题, 不然也不会有 Typescript 出现了. 在类型判断的问题上, 基础上 推荐阅读 [lodash](https://github.com/lodash/lodash) 的源代码. +JavaScript 的类型判断其实是个挺折磨人的话题, 不然也不会有 TypeScript 出现了. 在类型判断的问题上, 基础上 推荐阅读 [lodash](https://github.com/lodash/lodash) 的源代码. 这类问题一般只是面试过程中简单的开场, 当然不会因为你不知道 `undefined == null` 的结果是 `true` 就一票否决一个人. 只是根据个人经验看来,这个问题答不清楚的有不小的概率属于基础较差. 如果你对这种问题没有任何概念, 也许要反思一下是不是该找本书过一下 Js 的基础了. @@ -27,7 +27,7 @@ Javascript 的类型判断其实是个挺折磨人的话题, 不然也不会有 在面试时, 作用域并不是一个很好问的知识点但实际上这 JavaScript 中一个很重要的点. 饿厂一般开篇会问的是 `es6 中 let 与 var 的区别` 这一类问题, 或者列举代码, 然后通过对代码的解读来看你对作用域的掌握. -关于作用域的问题[《你不知道的 Javascript》](https://book.douban.com/subject/26351021/) 讲的很好, 推荐细读, 以下是该书的部分目录,各位可以感受一下: +关于作用域的问题[《你不知道的 JavaScript》](https://book.douban.com/subject/26351021/) 讲的很好, 推荐细读, 以下是该书的部分目录,各位可以感受一下: * 第1章 作用域是什么 * 第2章 词法作用域 @@ -42,18 +42,18 @@ Javascript 的类型判断其实是个挺折磨人的话题, 不然也不会有 简单点说, 对象是引用传递, 基础类型是值传递, 通过将基础类型包装 (boxing) 可以以引用的方式传递.(复杂见注①) -引用传递和值传递是一个非常简单的问题, 也是理解 Javascript 中的内存方面问题的一个基础. 如果不了解引用可能很难去看很多问题. +引用传递和值传递是一个非常简单的问题, 也是理解 JavaScript 中的内存方面问题的一个基础. 如果不了解引用可能很难去看很多问题. 面试写代码的话, 可以通过 `如何编写一个 json 对象的拷贝函数` 等类似的问题来考察对引用的了解. 不过笔者偶尔会有恶趣味, 喜欢先问应聘者对于 `==` 的 `===` 的区别的了解. 然后再问 `[1] == [1]` 是 `true` 还是 `false`. 如果基础不好的同学可能会被自己对于 `==` 和 `===` 的结论影响然后得出错误的结论. -注①: 对于技术好的, 希望能直接反驳这个问题本身是有问题的, 比如讲清楚 Javascript 中没有引用传递只是传递引用. 参见 [Is JavaScript a pass-by-reference or pass-by-value language?](http://stackoverflow.com/questions/518000/is-javascript-a-pass-by-reference-or-pass-by-value-language). 虽然说是复杂版, 但是这些知识对于 3年经验的同学真的应该是很简单的问题了. +注①: 对于技术好的, 希望能直接反驳这个问题本身是有问题的, 比如讲清楚 JavaScript 中没有引用传递只是传递引用. 参见 [Is JavaScript a pass-by-reference or pass-by-value language?](http://stackoverflow.com/questions/518000/is-javascript-a-pass-by-reference-or-pass-by-value-language). 虽然说是复杂版, 但是这些知识对于 3年经验的同学真的应该是很简单的问题了. 另外如果简历中有写 C++, 则必问 `指针与引用的区别`. ## 内存释放 -> Javascript 中不同类型以及不同环境下变量的内存都是何时释放? +> JavaScript 中不同类型以及不同环境下变量的内存都是何时释放? 引用类型是在没有引用之后, 通过 v8 的 GC 自动回收, 值类型如果是处于闭包的情况下, 要等闭包没有引用才会被 GC 回收, 非闭包的情况下等待 v8 的新生代 (new space) 切换的时候回收. @@ -61,7 +61,7 @@ Javascript 的类型判断其实是个挺折磨人的话题, 不然也不会有 你需要了解哪些操作一定会导致内存泄漏, 或者可以崩掉内存. 比如如下代码能否爆掉 V8 的内存? -```javascript +```javaScript let arr = []; while(true) arr.push(1); @@ -69,7 +69,7 @@ while(true) 然后上述代码与下方的情况有什么区别? -```javascript +```javaScript let arr = []; while(true) arr.push(); @@ -77,7 +77,7 @@ while(true) 如果 push 的是 `Buffer` 情况又会有什么区别? -```javascript +```javaScript let arr = []; while(true) arr.push(new Buffer(1000)); @@ -85,7 +85,7 @@ while(true) 思考完之后可以尝试找找别的情况如何爆掉 V8 的内存. 以及来聊聊内存泄漏? -```javascript +```javaScript function out() { const bigData = new Buffer(100); inner = function () { diff --git a/sections/zh-cn/event-async.md b/sections/zh-cn/event-async.md index d59ec8c..cbc511e 100644 --- a/sections/zh-cn/event-async.md +++ b/sections/zh-cn/event-async.md @@ -14,7 +14,7 @@ ![callback-hell](/assets/callback-hell.jpg) -相信很多同学在面试的时候都碰到过这样一个问题, `如何处理 Callback Hell`. 在早些年的时候, 大家会看到有很多的解决方案例如 [Q](https://www.npmjs.com/package/q), [async](https://www.npmjs.com/package/async), [EventProxy](https://www.npmjs.com/package/eventproxy) 等等. 最后从流行程度来看 `Promise` 当之无愧的独领风骚, 并且是在 ES6 的 Javascript 标准上赢得了支持. +相信很多同学在面试的时候都碰到过这样一个问题, `如何处理 Callback Hell`. 在早些年的时候, 大家会看到有很多的解决方案例如 [Q](https://www.npmjs.com/package/q), [async](https://www.npmjs.com/package/async), [EventProxy](https://www.npmjs.com/package/eventproxy) 等等. 最后从流行程度来看 `Promise` 当之无愧的独领风骚, 并且是在 ES6 的 JavaScript 标准上赢得了支持. 关于它的基础知识/概念推荐看阮一峰的 [Promise 对象](http://javascript.ruanyifeng.com/advanced/promise.html#toc9) 这里就不多不赘述. diff --git a/sections/zh-cn/io.md b/sections/zh-cn/io.md index d9bf669..701cacd 100644 --- a/sections/zh-cn/io.md +++ b/sections/zh-cn/io.md @@ -142,7 +142,7 @@ int copy(const char *src, const char *dest) ### 对象模式 -通过 Node API 创建的流, 只能够对字符串或者 buffer 对象进行操作. 但其实流的实现是可以基于其他的 Javascript 类型(除了 null, 它在流中有特殊的含义)的. 这样的流就处在 "对象模式(objectMode)" 中. +通过 Node API 创建的流, 只能够对字符串或者 buffer 对象进行操作. 但其实流的实现是可以基于其他的 JavaScript 类型(除了 null, 它在流中有特殊的含义)的. 这样的流就处在 "对象模式(objectMode)" 中. 在创建流对象的时候, 可以通过提供 `objectMode` 参数来生成对象模式的流. 试图将现有的流转换为对象模式是不安全的. ### 缓冲区 @@ -254,7 +254,7 @@ BOM stdio (standard input output) 标准的输入输出流, 即输入流 (stdin), 输出流 (stdout), 错误流 (stderr) 三者. 在 Node.js 中分别对应 `process.stdin` (Readable), `process.stdout` (Writable) 以及 `process.stderr` (Writable) 三个 stream. -输出函数是每个人在学习任何一门编程语言时所需要学到的第一个函数. 例如 C语言的 `printf("hello, world!");` python/ruby 的 `print 'hello, world!'` 以及 Javascript 中的 `console.log('hello, world!');` +输出函数是每个人在学习任何一门编程语言时所需要学到的第一个函数. 例如 C语言的 `printf("hello, world!");` python/ruby 的 `print 'hello, world!'` 以及 JavaScript 中的 `console.log('hello, world!');` 以 C语言的伪代码来看的话, 这类输出函数的实现思路如下: diff --git a/sections/zh-cn/network.md b/sections/zh-cn/network.md index 6baf80d..8062d80 100644 --- a/sections/zh-cn/network.md +++ b/sections/zh-cn/network.md @@ -205,7 +205,7 @@ location ~* ^/(?:v1|_) { ``` -详见 [Javascript Script Error.](https://sentry.io/answers/javascript-script-error/) +详见 [JavaScript Script Error.](https://sentry.io/answers/javascript-script-error/) ### Agent From 4d0e1919beefebe4a371a8fcf8da9bc7cb1a2681 Mon Sep 17 00:00:00 2001 From: qqqian Date: Sun, 29 Oct 2017 21:59:13 +0800 Subject: [PATCH 80/98] chores: security.md, fix typos --- sections/zh-cn/security.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sections/zh-cn/security.md b/sections/zh-cn/security.md index a7ad377..d9267ce 100644 --- a/sections/zh-cn/security.md +++ b/sections/zh-cn/security.md @@ -56,7 +56,7 @@ Node.js 的加密貌似有点问题, 某些算法算出来跟别的语言 (比 跨站脚本 (Cross-Site Scripting, XSS) 是一种代码注入方式, 为了与 CSS 区分所以被称作 XSS. 早期常见于网络论坛, 起因是网站没有对用户的输入进行严格的限制, 使得攻击者可以将脚本上传到帖子让其他人浏览到有恶意脚本的页面, 其注入方式很简单包括但不限于 JavaScript / VBScript / CSS / Flash 等. -当其他用户浏览到这些网页时, 就会执行这些恶意脚本, 对用户进行 Cookie 窃取/会话劫持/钓鱼欺骗等各种攻击. 其原理, 如使用 js 脚本收集当前用户环境的信息 (Cookie 等), 然后通过 img.src, Ajax, onclick/onload/onerror 事件等方式将用户数据传递到攻击者的服务器上. 钓鱼欺骗则常见于使用脚本进行视觉欺骗, 构建假的恶意的 Button 覆盖/替换真实的场景等情况 (该情况在用户上传 CSS 的时候也可能出现, 如早起淘宝网店装修, 使用 CSS 拼接假的评分数据等覆盖在真的评分数据上误导用户). +当其他用户浏览到这些网页时, 就会执行这些恶意脚本, 对用户进行 Cookie 窃取/会话劫持/钓鱼欺骗等各种攻击. 其原理, 如使用 js 脚本收集当前用户环境的信息 (Cookie 等), 然后通过 img.src, Ajax, onclick/onload/onerror 事件等方式将用户数据传递到攻击者的服务器上. 钓鱼欺骗则常见于使用脚本进行视觉欺骗, 构建假的恶意的 Button 覆盖/替换真实的场景等情况 (该情况在用户上传 CSS 的时候也可能出现, 如早期淘宝网店装修, 使用 CSS 拼接假的评分数据等覆盖在真的评分数据上误导用户). > 过滤 Html 标签能否防止 XSS? 请列举不能的情况? @@ -185,7 +185,7 @@ SELECT * FROM users WHERE usernae = 'myName' AND password = ''; DROP TABLE users 其能实现的功能, 包括但不限于删除数据 (经济损失), 篡改数据 (密码等), 窃取数据 (网站管理权限, 用户数据) 等. 防治手段常见于: * 给表名/字段名加前缀 (避免被猜到) -* 报错隐藏表信息 (避免被看到, 12306 早起就出现过的问题) +* 报错隐藏表信息 (避免被看到, 12306 早期就出现过的问题) * 过滤可以拼接 SQL 的关键字符 * 对用户输入进行转义 * 验证用户输入的类型 (避免 limit, order by 等注入) From e6fdfceb2ad9093fdc2b5b58a0f18cd336218f7e Mon Sep 17 00:00:00 2001 From: Lellansin Date: Mon, 4 Dec 2017 12:36:34 +0800 Subject: [PATCH 81/98] Network: fix cors detail --- sections/zh-cn/network.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sections/zh-cn/network.md b/sections/zh-cn/network.md index 8062d80..6c183bd 100644 --- a/sections/zh-cn/network.md +++ b/sections/zh-cn/network.md @@ -182,7 +182,7 @@ HTTP headers 是在进行 HTTP 请求的交互过程中互相支会对方一些 > 什么是跨域请求? 如何允许跨域? -出于安全考虑, 默认情况下使用 XMLHttpRequest 和 Fetch 发起 HTTP 请求必须遵守同源策略, 即只能向相同域名请求. 向不同域名的请求被称作跨域请求 (cross-origin HTTP request). 可以通过设置 [CORS headers](https://developer.mozilla.org/en-US/docs/Glossary/CORS) 即 `Access-Control-Allow-` 系列来允许跨域. 例如: +出于安全考虑, 默认情况下使用 XMLHttpRequest 和 Fetch 发起 HTTP 请求必须遵守同源策略, 即只能向相同 host 请求 (host = hostname : port). 向不同 host 的请求被称作跨域请求 (cross-origin HTTP request). 可以通过设置 [CORS headers](https://developer.mozilla.org/en-US/docs/Glossary/CORS) 即 `Access-Control-Allow-` 系列来允许跨域. 例如: ``` location ~* ^/(?:v1|_) { From 67e5b990e422c3620d0ecfd34314584c1fefd81e Mon Sep 17 00:00:00 2001 From: Lellansin Date: Mon, 4 Dec 2017 15:26:09 +0800 Subject: [PATCH 82/98] Network: add https tip for http cors --- sections/zh-cn/network.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sections/zh-cn/network.md b/sections/zh-cn/network.md index 6c183bd..17d6ff8 100644 --- a/sections/zh-cn/network.md +++ b/sections/zh-cn/network.md @@ -182,7 +182,7 @@ HTTP headers 是在进行 HTTP 请求的交互过程中互相支会对方一些 > 什么是跨域请求? 如何允许跨域? -出于安全考虑, 默认情况下使用 XMLHttpRequest 和 Fetch 发起 HTTP 请求必须遵守同源策略, 即只能向相同 host 请求 (host = hostname : port). 向不同 host 的请求被称作跨域请求 (cross-origin HTTP request). 可以通过设置 [CORS headers](https://developer.mozilla.org/en-US/docs/Glossary/CORS) 即 `Access-Control-Allow-` 系列来允许跨域. 例如: +出于安全考虑, 默认情况下使用 XMLHttpRequest 和 Fetch 发起 HTTP 请求必须遵守同源策略, 即只能向相同 host 请求 (host = hostname : port) 注[1]. 向不同 host 的请求被称作跨域请求 (cross-origin HTTP request). 可以通过设置 [CORS headers](https://developer.mozilla.org/en-US/docs/Glossary/CORS) 即 `Access-Control-Allow-` 系列来允许跨域. 例如: ``` location ~* ^/(?:v1|_) { @@ -197,6 +197,8 @@ location ~* ^/(?:v1|_) { } ``` +注[1]:同源除了相同 host 也包括相同协议. 所以即使 host 相同, 从 HTTP 到 HTTPS 也属于跨域, 见[讨论](https://github.com/ElemeFE/node-interview/issues/55). + > `Script error.` 是什么错误? 如何拿到更详细的信息? 接上题, 由于同源性策略 (CORS), 如果你引用的 js 脚本所在的域与当前域不同, 那么浏览器会把 onError 中的 msg 替换为 `Script error.` 要拿到详细错误的方法, 处理配好 `Access-Control-Allow-Origin` 还有在引用脚本的时候指定 `crossorigin` 例如: From c8c9f6e2236db8592a9c66ab2bb2ec45af9dc298 Mon Sep 17 00:00:00 2001 From: Chuck Date: Tue, 5 Dec 2017 21:51:41 +0800 Subject: [PATCH 83/98] Section Util: update glob example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 最后一个小例子好像有点问题 --- sections/zh-cn/util.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sections/zh-cn/util.md b/sections/zh-cn/util.md index c49f404..70ef812 100644 --- a/sections/zh-cn/util.md +++ b/sections/zh-cn/util.md @@ -221,10 +221,12 @@ console.log(traversal('.')); ```javascript const glob = require("glob"); -glob("**/*.js", (err, files) { +glob("**/*.js", (err, files) => { if (err) { throw new Error(err); } - console.log('Here you are:', files.map(path.basename)); + files.map((filename) => { + console.log('Here you are:', filename); + }); }); ``` From abffdbc98c2633586c10687742442be619860aea Mon Sep 17 00:00:00 2001 From: David Zhang Date: Sun, 17 Dec 2017 16:25:47 +0800 Subject: [PATCH 84/98] section: common, translate the basis (#57) * Translate [common] type judgement * Add scope and reference --- sections/en-us/common.md | 43 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/sections/en-us/common.md b/sections/en-us/common.md index bf57d96..08c5269 100644 --- a/sections/en-us/common.md +++ b/sections/en-us/common.md @@ -1,13 +1,48 @@ # Basic -* `[Common]` Type judgment -* `[Common]` Scope -* `[Common]` Reference +* [`[Common]` Type judgment](/sections/en-us/common.md#Type-judgement) +* [`[Common]` Scope](/sections/en-us/common.md#Scope) +* [`[Common]` Reference](/sections/en-us/common.md#Reference) * `[Common]` Memory release * `[Common]` ES6+ features ## Summary -With diffrence between frontend, there are few chance to work with DOM in backend. So we won't discuss about it. Unlike browser side, backend is directly facing memory, while the work duration is calculated by year, we do more concern about the machine side. +In contrast to frontend, there are few chances to work with DOM in backend, unless we deal with SSR or web crawlers. So we won't discuss about it. Unlike browser side, backend faces memory directly, we concern more about the fundamental knowledge. +## Type judgement +We suffer tortuously from type judgement in JavaScript. Otherwise, TypeScript may not be created. Basically, we recommend to read the source code of [lodash](https://github.com/lodash/lodash). + +Generally, this is a simple opening of an interview. We won't deny a candidate only because of not knowing the value of `undefined == null` is `true`. According to our personal experiences, candidate who cannot answer this question is probably to have a poor foundation. If you have no concept of such kind of question, you may reflect on whether to find a JavaScript book to review for basis. + +Additionally, it is a bonus point if candidate understands TypeScript or flow. + +## Scope + +In an interview, scope is not an easy-to-ask knowledge point but critical in JavaScript. Eleme typically asks questions like `what's the difference between let and var in es6` or asks candidate to interpret a given code example in the beginning, in order to assess how much does a candidate master scope. + +[You Don't Know JS](https://github.com/getify/You-Dont-Know-JS) has a great explanation on scope. Here it is the TOC of the book, we recommend you to do some intensive reading. + +* Chapter 1: What is Scope? +* Chapter 2: Lexical Scope +* Chapter 3: Function vs. Block Scope +* Chapter 4: Hoisting +* Chapter 5: Scope Closures +* ... + +## Reference + +> In JavaScript, which types are pass by reference? And which types are pass by value? How to pass a variable by reference? + +Simply speaking, objects are pass by reference. Basic types are pass by value. We can pass basic types by reference using boxing technique. (More information at note 1) + +Pass by reference and pass by value is a basic question. It is fundamental part to understand how does JavaScript's memory work. It is hardly to have further discussion without understanding reference. + +In coding session, we use questions like `how to write a json object copy function` to assess candidate. + +Sometimes, we ask about the difference between `==` and `===`. And then, `true` or `false` of `[1] == [1]`. Without a good foundation, candidate may make a wrong conclusion because of the wrong understanding of `==` and `===`. + +Note 1: For senior candidates, you are expected to question directly on the question. e.g. There is no pass by reference in JavaScript. There is call by sharing. Read about [Is JavaScript a pass-by-reference or pass-by-value language?](http://stackoverflow.com/questions/518000/is-javascript-a-pass-by-reference-or-pass-by-value-language). Though it is advanced, it is common for senior developer with more than 3 years experiences. + +If C++ is mentioned in resume, it is certain to ask `what is the difference between pointer and reference`. From a48634589f3335bc8653ab092babbc7d29b0fe9f Mon Sep 17 00:00:00 2001 From: "soda.wang" Date: Sun, 24 Dec 2017 19:48:50 +0800 Subject: [PATCH 85/98] section: io, specify the console's situation --- sections/zh-cn/io.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sections/zh-cn/io.md b/sections/zh-cn/io.md index 701cacd..74240f2 100644 --- a/sections/zh-cn/io.md +++ b/sections/zh-cn/io.md @@ -199,7 +199,7 @@ pipe 方法最主要的目的就是将数据的流动缓冲到一个可接受的 ## Console -[console.log 正常情况下是异步的, 除非你使用 `new Console(stdout[, stderr])` 指定了一个文件为目的地](https://nodejs.org/dist/latest-v6.x/docs/api/console.html#console_asynchronous_vs_synchronous_consoles). 不过一般情况下的实现都是如下 ([6.x 源代码](https://github.com/nodejs/node/blob/v6.x/lib/console.js#L42)): +[console.log 同步还是异步取决于与谁相连和`os`](https://nodejs.org/dist/latest-v6.x/docs/api/process.html#process_a_note_on_process_i_o). 不过一般情况下的实现都是如下 ([6.x 源代码](https://github.com/nodejs/node/blob/v6.x/lib/console.js#L42)),其中`this._stdout`默认是`process.stdout`: ```javascript // As of v8 5.0.71.32, the combination of rest param, template string From 99de2564da20f0aaac1e8a61a940323daaf16a42 Mon Sep 17 00:00:00 2001 From: "soda.wang" Date: Sun, 24 Dec 2017 19:52:25 +0800 Subject: [PATCH 86/98] section: io, supplement of StringDecoder --- sections/zh-cn/io.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sections/zh-cn/io.md b/sections/zh-cn/io.md index 74240f2..2f4ae0a 100644 --- a/sections/zh-cn/io.md +++ b/sections/zh-cn/io.md @@ -65,7 +65,7 @@ const euro = Buffer.from([0xE2, 0x82, 0xAC]); console.log(decoder.write(euro)); // € ``` -当然也可以断断续续的处理. +stringDecoder.write 会确保返回的字符串不包含 Buffer 末尾残缺的多字节字符,残缺的多字节字符会被保存在一个内部的 buffer 中用于下次调用 stringDecoder.write() 或 stringDecoder.end()。 ```javascript const StringDecoder = require('string_decoder').StringDecoder; From 902ed73a7563fe92523bb36ceb09d9e5766c86f3 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Fri, 19 Jan 2018 14:18:58 +0800 Subject: [PATCH 87/98] section: event-async, fix typo: https://github.com/ElemeFE/node-interview/issues/61 --- sections/zh-cn/event-async.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sections/zh-cn/event-async.md b/sections/zh-cn/event-async.md index cbc511e..08562f4 100644 --- a/sections/zh-cn/event-async.md +++ b/sections/zh-cn/event-async.md @@ -222,6 +222,6 @@ function sleep(ms) { 并行 (Parallel) = 2 队列对应 2 咖啡机. -Node.js 通过事件循环来挨个抽取事件队列中的一个个 Task 执行, 从而避免了传统的多线程情况下 `2个队列对应 1个咖啡机` 的时候上线文切换以及资源争抢/同步的问题, 所以获得了高并发的成就. +Node.js 通过事件循环来挨个抽取事件队列中的一个个 Task 执行, 从而避免了传统的多线程情况下 `2个队列对应 1个咖啡机` 的时候上下文切换以及资源争抢/同步的问题, 所以获得了高并发的成就. 至于在 node 中并行, 你可以通过 cluster 来再添加一个咖啡机. From 758e3aef39541f93cdd31287731a83a684bafb5f Mon Sep 17 00:00:00 2001 From: David Zhang Date: Fri, 23 Feb 2018 13:14:58 +0800 Subject: [PATCH 88/98] section: common, Translate Memory release & es6 features (#58) * Translate memory release & es6 features * Fix typos --- sections/en-us/common.md | 67 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/sections/en-us/common.md b/sections/en-us/common.md index 08c5269..8c0425c 100644 --- a/sections/en-us/common.md +++ b/sections/en-us/common.md @@ -3,8 +3,8 @@ * [`[Common]` Type judgment](/sections/en-us/common.md#Type-judgement) * [`[Common]` Scope](/sections/en-us/common.md#Scope) * [`[Common]` Reference](/sections/en-us/common.md#Reference) -* `[Common]` Memory release -* `[Common]` ES6+ features +* [`[Common]` Memory release](/sections/en-us/common.md#Memory-release) +* [`[Common]` ES6+ features](/sections/en-us/common.md#ES6-features) ## Summary @@ -46,3 +46,66 @@ Sometimes, we ask about the difference between `==` and `===`. And then, `true` Note 1: For senior candidates, you are expected to question directly on the question. e.g. There is no pass by reference in JavaScript. There is call by sharing. Read about [Is JavaScript a pass-by-reference or pass-by-value language?](http://stackoverflow.com/questions/518000/is-javascript-a-pass-by-reference-or-pass-by-value-language). Though it is advanced, it is common for senior developer with more than 3 years experiences. If C++ is mentioned in resume, it is certain to ask `what is the difference between pointer and reference`. + +## Memory release + +> When will each types and each scope of variables be released in JavaScript? + +If reference was no longer referenced, it would be collected by the GC of V8. If a value variable was inside a closure, it wouldn't be release until the closure was no longer referenced. In non-closure scope, it will be collected when V8 is switched to new space. + +In contrast to frontend JavaScript, a Node.js developer with more than 2 years experience should care about memory. Though you may not understand in depth, you had better have a basic concept of memory release and start to pay attention to memory leaks. + +You need to know which operations lead to memory leaks, or even crash the memory. For example, will the code segment given below fill up with all of V8's memory? + +```javaScript +let arr = []; +while(true) + arr.push(1); +``` + +Then, what's the difference between this one and the above? + +```javaScript +let arr = []; +while(true) + arr.push(); +``` + +If a `Buffer` was pushed, what would happen? + +```javaScript +let arr = []; +while(true) + arr.push(new Buffer(1000)); +``` + +After thinking about the aboves, try to figure out what else can fill up with V8's memory. And then let's talk about memory leaks. + +```javaScript +function out() { + const bigData = new Buffer(100); + inner = function () { + void bigData; + } +} +``` + +Closure references variable from its parent. If it is not released, a memory leak happens. The example above shows `inner` is under the root, which causes a memory leak (`bigData` is not released). + +For senior candidates, you need to know the mechanism of GC in V8 and know how memory snapshot (which will be discussed in chapter of `Debug/Optimization`) works. e.g. Where do V8 store different types of data? What are the specific optimizing strategies for different areas when doing memory release? + +## ES6 features + +We recommend a [ECMAScript 6 Tutorial](http://es6.ruanyifeng.com/) book from @ruanyifeng (in Chinese). + +The basic questions can be the differences between `let` and `var`, and between `arrow function` and `function`. + +To go deeper, there are lots of details in es6, such as `reference` together with `const`. Talk about `Set` and `Map` in context of usage and disadvantages of `{}`. Or it can be about the privatization and `symbol`. + +However, it is unnecessary to ask `what is a closure?`. Instead, we'd like to ask about the application of closures. e.g. If interviewer usually uses closure to make data private, then we may ask can new features (e.g. `class` and `symbol`) be private? If true, then why we need closure here? When will data in a closure be released? And so on. + +For `...`, how to implement deletion of duplicated for an array (Bonus point for using Set). + +> Is it possible for an element in a const Array be modified? If possible, what's the effect of const? + +The elements can be modified. And it protects the reference, which cannot be modified (e.g. [Map](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map) is sensitive to reference and it need const. Besides, it is also suitable for immutable). From 144a8a060c2a33f600b696837ea0f89544910c10 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Tue, 3 Apr 2018 02:07:00 +0800 Subject: [PATCH 89/98] fix: typos -> https://github.com/ElemeFE/node-interview/issues/66 --- sections/zh-cn/network.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sections/zh-cn/network.md b/sections/zh-cn/network.md index 17d6ff8..1142ffc 100644 --- a/sections/zh-cn/network.md +++ b/sections/zh-cn/network.md @@ -33,7 +33,7 @@ ***方案1*** -只需要等上一段时间再进行下一次 send 就好, 适用于交互频率特别低的场景. 缺点也很明显, 对于比较频繁的场景而言传输效率实在太低. 不过几乎用做什么处理. +只需要等上一段时间再进行下一次 send 就好, 适用于交互频率特别低的场景. 缺点也很明显, 对于比较频繁的场景而言传输效率实在太低. 不过几乎不用做什么处理. ***方案2*** From 9b48d89b51c2980c06af772e0c132a1f81366b35 Mon Sep 17 00:00:00 2001 From: sinchang Date: Thu, 12 Apr 2018 19:02:09 +0800 Subject: [PATCH 90/98] docs: fix background image (#68) closes #67 --- sections/en-us/README.md | 2 +- sections/zh-cn/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sections/en-us/README.md b/sections/en-us/README.md index efb06f8..5a704f2 100644 --- a/sections/en-us/README.md +++ b/sections/en-us/README.md @@ -1,4 +1,4 @@ -![ElemeFE-background](/assets/ElemeFE-background.png) +![ElemeFE-background](../../assets/ElemeFE-background.png) ## Guide diff --git a/sections/zh-cn/README.md b/sections/zh-cn/README.md index 7aad507..a564c04 100644 --- a/sections/zh-cn/README.md +++ b/sections/zh-cn/README.md @@ -1,4 +1,4 @@ -![ElemeFE-background](/assets/ElemeFE-background.png) +![ElemeFE-background](../../assets/ElemeFE-background.png) # 如何通过饿了么 Node.js 面试 From 0be68fdc91c4ab170e707aa0e07989fb7d81fc1e Mon Sep 17 00:00:00 2001 From: "Jinke.Li" <1359518268@qq.com> Date: Wed, 9 May 2018 13:57:10 +0800 Subject: [PATCH 91/98] section: event-async, fix typo (#70) --- sections/zh-cn/event-async.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sections/zh-cn/event-async.md b/sections/zh-cn/event-async.md index 08562f4..4a5b272 100644 --- a/sections/zh-cn/event-async.md +++ b/sections/zh-cn/event-async.md @@ -35,7 +35,7 @@ doSth.then(() => { }); ``` -毫无疑问的可以得到一下输出结果: +毫无疑问的可以得到以下输出结果: ``` hello From 0c2577f38a8efa7ac423bb63dbdaeea876f17c64 Mon Sep 17 00:00:00 2001 From: AlbertLee Date: Fri, 11 May 2018 16:03:25 +0800 Subject: [PATCH 92/98] section: security, fix typo: SLL => SSL (#71) --- sections/zh-cn/security.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sections/zh-cn/security.md b/sections/zh-cn/security.md index d9267ce..798f809 100644 --- a/sections/zh-cn/security.md +++ b/sections/zh-cn/security.md @@ -45,7 +45,7 @@ Node.js 的加密貌似有点问题, 某些算法算出来跟别的语言 (比 由 RA 统筹、审核用户的证书申请, 将证书申请送至 CA 处理后发出证书, 并将证书公告至 DS 中. 在使用证书的过程中, 除了对证书的信任关系与证书本身的正确性做检查外, 并透过产生和发布证书废止列表 (Certificate Revocation List, CRL) 对证书的状态做确认检查, 了解证书是否因某种原因而遭废弃. 证书就像是个人的身分证, 其内容包括证书序号、用户名称、公开金钥 (Public Key) 、证书有效期限等. -在 TLS/SLL 中你可以使用 OpenSSL 来生成 TLS/SSL 传输时用来认证的 public/private key. 不过这个 public/private key 是自己生成的, 而通过 PKI 基础设施可以获得权威的第三方证书 (key) 从而加密 HTTP 传输安全. 目前博客圈子里比较流行的是 [Let's Encrypt 签发免费的 HTTPS 证书](https://imququ.com/post/letsencrypt-certificate.html). +在 TLS/SSL 中你可以使用 OpenSSL 来生成 TLS/SSL 传输时用来认证的 public/private key. 不过这个 public/private key 是自己生成的, 而通过 PKI 基础设施可以获得权威的第三方证书 (key) 从而加密 HTTP 传输安全. 目前博客圈子里比较流行的是 [Let's Encrypt 签发免费的 HTTPS 证书](https://imququ.com/post/letsencrypt-certificate.html). 需要注意的是, 如果 PKI 受到攻击, 那么 HTTPS 也一样不安全. 可以参见 [HTTPS 劫持 - 知乎讨论](https://www.zhihu.com/question/22795329) 中的情况, 证书由 CA 机构签发, 一般浏览器遇到非权威的 CA 机构是会告警的 (参见 [12306](https://kyfw.12306.cn/otn/)), 但是如果你在某些特殊的情况下信任了某个未知机构/证书, 那么也可能被劫持. From b15f23c023fa5b2e4d938d62f0dd465a10b60f38 Mon Sep 17 00:00:00 2001 From: Roman Morozov <577222+sublimeye@users.noreply.github.com> Date: Wed, 30 May 2018 22:38:24 -0700 Subject: [PATCH 93/98] section: process, fix typo --- sections/en-us/process.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sections/en-us/process.md b/sections/en-us/process.md index 29a54da..7dfadc1 100644 --- a/sections/en-us/process.md +++ b/sections/en-us/process.md @@ -27,7 +27,7 @@ For more details about the process and the operating system, you can read the AP ## Process -Here we will discuss the `project` object in Node.js. It can be printed out by using `console.log (process)` in the code. You can see the process object exposed a lot of useful properties and methods. For more details you can refer [Official document](https://nodejs.org/dist/latest-v6.x/docs/api/process.html), which has been very detailed, +Here we will discuss the `process` object in Node.js. It can be printed out by using `console.log (process)` in the code. You can see the process object exposed a lot of useful properties and methods. For more details you can refer [Official document](https://nodejs.org/dist/latest-v6.x/docs/api/process.html), which has been very detailed, including but not limited to: * The basic information of the process From f48365b601dd938131ea82d3bf9c929ac30d863a Mon Sep 17 00:00:00 2001 From: abhishek gupta Date: Mon, 16 Jul 2018 08:17:05 +0530 Subject: [PATCH 94/98] section: common, fix typo (#73) --- sections/en-us/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sections/en-us/README.md b/sections/en-us/README.md index 5a704f2..75241bd 100644 --- a/sections/en-us/README.md +++ b/sections/en-us/README.md @@ -15,7 +15,7 @@ **Common Problem** -[View more](/sections/en-us/js-basic.md) +[View more](/sections/en-us/common.md) ## [Module](/sections/en-us/module.md) From 719dd6041b302fb1fda150f7ce54e1c747c9eb0b Mon Sep 17 00:00:00 2001 From: KUI CHU Date: Fri, 10 Aug 2018 15:06:01 +0800 Subject: [PATCH 95/98] section: util.md, fix typo (#74) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 转移=》转义 --- sections/zh-cn/util.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sections/zh-cn/util.md b/sections/zh-cn/util.md index 70ef812..7be404e 100644 --- a/sections/zh-cn/util.md +++ b/sections/zh-cn/util.md @@ -24,7 +24,7 @@ ### 转义字符 -常见的需要转移的字符列表: +常见的需要转义的字符列表: |字符|encodeURI| |---|---| From 07853a751b518dcbfa994bce4a4aad43efa6baf5 Mon Sep 17 00:00:00 2001 From: kailunyao Date: Sun, 26 Aug 2018 20:57:00 -0500 Subject: [PATCH 96/98] section: network.md, fix typo (#75) --- sections/zh-cn/network.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sections/zh-cn/network.md b/sections/zh-cn/network.md index 1142ffc..efe1d15 100644 --- a/sections/zh-cn/network.md +++ b/sections/zh-cn/network.md @@ -16,7 +16,7 @@ 默认情况下, TCP 连接会启用延迟传送算法 (Nagle 算法), 在数据发送之前缓存他们. 如果短时间有多个数据发送, 会缓冲到一起作一次发送 (缓冲大小见 `socket.bufferSize`), 这样可以减少 IO 消耗提高性能. -如果是传输文件的话, 那么根本不用处理粘包的问题, 来一个包拼一个包就好了. 但是如果是多条消息, 或者是别的用途的数据那么久需要处理粘包. +如果是传输文件的话, 那么根本不用处理粘包的问题, 来一个包拼一个包就好了. 但是如果是多条消息, 或者是别的用途的数据那么就需要处理粘包. 可以参见网上流传比较广的一个例子, 连续调用两次 send 分别发送两段数据 data1 和 data2, 在接收端有以下几种常见的情况: From 9852022b0a70c41450ef17e054fdecc5d8d47f1b Mon Sep 17 00:00:00 2001 From: Oleg Stotsky Date: Tue, 9 Oct 2018 12:27:14 +1000 Subject: [PATCH 97/98] section: Error handle & Debug, fix typo (#77) --- sections/en-us/error.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sections/en-us/error.md b/sections/en-us/error.md index c36bde0..1c69be4 100644 --- a/sections/en-us/error.md +++ b/sections/en-us/error.md @@ -231,7 +231,7 @@ You can see the latest news about this module at: [deprecate domains](https://gi Command line debug tool like gdb (Build-in debugger in the image above), it also supports remote debug (like [node-inspector](https://github.com/node-inspector/node-inspector), but still in trial). Of course, many developers feel that [vscode](https://code.visualstudio.com/) has maken a better integration to the debug tools. -We recommend reading [official ducoment](https://nodejs.org/dist/latest-v6.x/docs/api/debugger.html) to learn how to use this build-in debugger. If you want to dig deeper, see: [Modify the value of a variable in the NodeJS program dynamically](http://code.oneapm.com/nodejs/2015/06/27/intereference/) +We recommend reading [official document](https://nodejs.org/dist/latest-v6.x/docs/api/debugger.html) to learn how to use this build-in debugger. If you want to dig deeper, see: [Modify the value of a variable in the NodeJS program dynamically](http://code.oneapm.com/nodejs/2015/06/27/intereference/) ## C/C++ Addon From 59de80b63ddd359e9d85fb25b162bef62c250389 Mon Sep 17 00:00:00 2001 From: ZYSzys Date: Mon, 26 Nov 2018 12:00:31 +0800 Subject: [PATCH 98/98] section: correct module require cycle (#79) --- sections/zh-cn/module.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sections/zh-cn/module.md b/sections/zh-cn/module.md index d7d69b8..5db7128 100644 --- a/sections/zh-cn/module.md +++ b/sections/zh-cn/module.md @@ -64,7 +64,7 @@ function require(...) { > a.js 和 b.js 两个文件互相 require 是否会死循环? 双方是否能导出变量? 如何从设计上避免这种问题? -② 不会, 先执行的导出空对象, 通过导出工厂函数让对方从函数去拿比较好避免. 模块在导出的只是 `var module = { exports: {} };` 中的 exports, 以从 a.js 启动为例, a.js 还没执行完 exports 就是 `{}` 在 b.js 的开头拿到的就是 `{}` 而已. +② 不会, 先执行的导出其 **未完成的副本**, 通过导出工厂函数让对方从函数去拿比较好避免. 模块在导出的只是 `var module = { exports: {...} };` 中的 exports, 以从 a.js 启动为例, a.js 还没执行完会返回一个 a.js 的 exports 对象的 **未完成的副本** 给 b.js 模块。 然后 b.js 完成加载,并将 exports 对象提供给 a.js 模块。 另外还有非常基础和常见的问题, 比如 module.exports 和 exports 的区别这里也能一并解决了 exports 只是 module.exports 的一个引用. 没看懂可以在细看我以前发的[帖子](https://cnodejs.org/topic/5734017ac3e4ef7657ab1215).