mirror of
https://github.com/ElemeFE/node-interview.git
synced 2026-01-18 16:12:44 +00:00
Add network.md
This commit is contained in:
parent
8f71b1d257
commit
5ce9caf052
34
README.md
34
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 程序员需要了解的部分.
|
||||
|
||||
如果你觉得大多不了解, 就不用投简历了 <del>(这样两边都节约了时间)</del>, 如果你觉得大都有了解或者***光看大纲都都觉得很简单那么欢迎投递简历至 ElemeFe (fe.job@ele.me)***.
|
||||
如果你觉得大多不了解, 就不用投简历了 <del>(这样两边都节约了时间)</del>, 如果你觉得大都有了解或者**光看大纲都都觉得很简单那么欢迎投递简历至 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 程序员需要了解的部分。
|
||||
|
||||
BIN
assets/socket-backlog.png
Normal file
BIN
assets/socket-backlog.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 210 KiB |
BIN
assets/tcpfsm.png
Normal file
BIN
assets/tcpfsm.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
@ -17,12 +17,12 @@
|
||||
|
||||
## 类型判断
|
||||
|
||||
Javascript 的类型判断其实是个挺折磨人的话题, 不然也不会有 Typescript 出现了.
|
||||
|
||||
能清晰的说出变量类型以及各种 `typeof` 情况的,要么是好好看过书的,要么就是平常代码写得不少的.
|
||||
Javascript 的类型判断其实是个挺折磨人的话题, 不然也不会有 Typescript 出现了. 在类型判断的问题上, 基础上 推荐阅读 [lodash](https://github.com/lodash/lodash) 的源代码.
|
||||
|
||||
这类问题一般只是简单的开场, 不会因为说你不知道 `undefined == null` 的结果是 `true` 就一票否决一个人. 只是根据个人经验看来,这个问题答不清楚的有不小的概率属于基础较差. 如果你对这种问题没有任何概念, 也许要反思一下是不是该找本书过一下 Js 的基础了.
|
||||
|
||||
另外在这个问题上, 对使用 TypeScript 以及 flow 同学会有一定的加分.
|
||||
|
||||
## 作用域
|
||||
|
||||
在面试时, 作用域并不是一个很好问的知识点, 一般会问的是 `es6 中 let 与 var 的区别`, 或者列举代码, 然后通过对代码的解读来看你对作用域的掌握比较方便.
|
||||
|
||||
@ -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#上下文)
|
||||
|
||||
## 常见问题
|
||||
|
||||
|
||||
> <a name="q-hot"></a> 如何在不重启 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 校验/差异更新等复杂问题; 对服务端而言, 热更新意味着服务不用重启, 这样可用性较高<del>同时也优雅和有逼格</del>. 问的过程中可以一定程度的暴露应聘程序员的水平.
|
||||
|
||||
因为热更新意味着服务不用重启, 对于服务来说这属于高可用性的特性了. 从 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), 不过聊起来确实是可以暴露水平.
|
||||
|
||||
|
||||
224
sections/network.md
Normal file
224
sections/network.md
Normal file
@ -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
|
||||
|
||||

|
||||
|
||||
关于该 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)). 经过<del>我也不懂的</del>计算 backlog 的长度默认是 511.
|
||||
|
||||
另外提一句, 这个 backlog 是通过系统指定时是通过 `somaxconn` 参数来指定 accept queue 的. 而 `tcp_max_syn_backlog` 参数指定的是 SYN queue 的长度.
|
||||
|
||||
### 状态机
|
||||
|
||||

|
||||
|
||||
关于网络连接的建立以及断开, 存在着一个复杂的状态转换机制, 完整的状态表参见 [《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 一次
|
||||
|
||||
> <a name="q-time-wait"></a> `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
|
||||
|
||||
> <a name="q-tcp-udp"></a> TCP/UDP 的区别? UDP 有粘包吗?
|
||||
|
||||
协议|连接性|双工性|可靠性|有序性|有界性|拥塞控制|传输速度|量级|头部大小
|
||||
---|---|---|---|---|---|---|---|---|---
|
||||
TCP|面向连接<br>(Connection oriented)|全双工(1:1)|可靠<br>(重传机制)|有序<br>(通过SYN排序)|无, 有[粘包情况](#粘包)|有|慢|低|20~60字节
|
||||
UDP|无连接<br>(Connection less)|n:m|不可靠<br>(丢包后数据丢失)|无序|有消息边界, **无粘包**|无|快|高|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 的多播特性 (不过仅在支持多播的系统上才有).
|
||||
|
||||
|
||||
### 常见的应用场景
|
||||
|
||||
<table>
|
||||
<tr><th>传输层协议</th><th>应用</th><th>应用层协议</th></tr>
|
||||
<tr><td rowspan="5">TCP</td><td>电子邮件</td><td>SMTP</td></tr>
|
||||
<tr><td>终端连接</td><td>TELNET</td></tr>
|
||||
<tr><td>终端连接</td><td>SSH</td></tr>
|
||||
<tr><td>万维网</td><td>HTTP</td></tr>
|
||||
<tr><td>文件传输</td><td>FTP</td></tr>
|
||||
<tr><td rowspan="8">UDP</td><td>域名解析</td><td>DNS</td></tr>
|
||||
<tr><td>简单文件传输</td><td>TFTP</td></tr>
|
||||
<tr><td>网络时间校对</td><td>NTP</td></tr>
|
||||
<tr><td>网络文件系统</td><td>NFS</td></tr>
|
||||
<tr><td>路由选择</td><td>RIP</td></tr>
|
||||
<tr><td>IP电话</td><td>-</td></tr>
|
||||
<tr><td>流式多媒体通信</td><td>-</td></tr>
|
||||
</table>
|
||||
|
||||
简单的说, 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 提倡的语义角度来讲<del>比较符合当代程序员的逼格</del>比较合理.
|
||||
|
||||
> <a name="q-post-put"></a> 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)
|
||||
|
||||
<del>有点不想写...</del>整理中
|
||||
|
||||
### 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)<del>虽然我还没看完</del>)
|
||||
|
||||
常见的 RPC 几大代表:
|
||||
|
||||
* [thrift](http://thrift.apache.org/)
|
||||
* HTTP
|
||||
* MQ
|
||||
|
||||
整理中
|
||||
|
||||
|
||||
@ -173,7 +173,6 @@ cluster 模块提供了两种分发连接的方式.
|
||||
|
||||
第二种方式是由主进程创建 socket 监听端口后, 将 socket 的句柄直接分发给相应的 workder, 然后当连接进来时, 就直接由相应的 worker 来 accept 连接并处理.
|
||||
|
||||
使用第二种方式时, 多个 worker 之间会存在竞争关系, 产生一个老生常谈的 "惊群效应" 从而导致效率变低的问题. 该问题常见于 Apache. 并且各自竞争的情况下无法控制一个新的连接由哪个进程来处理, 从而导致各 worker 进程之间的负载不均衡, 比如 70% 的连接终止于八个进程中的两个.
|
||||
|
||||
## 进程间通信
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user