Node问题解析(问题和部分答案采用与Eleme)
1.a.js和b.js两个文件互相require是否会死循环?双方是否能导出变量?
不会死循环,如果相互require,会复制一个未完成的给另一方
2.Promise中的then的第二个参数与.catch有什么区别?
.then 只就近捕获异常,catch 捕获全局异常
3.Eventemitter 的 emit 是同步还是异步:
同步
4.是否只要有回调函数就是异步?
回调函数不是异步,在方法里做耗时操作才是异步
5.setTimeOut nextTick setImmediate 的区别
process.nextTick()方法可以在当前"执行栈"的尾部-->下一次Event Loop(主线程读取"任务队列")之前-->触发
process指定的回调函数。也就是说,它指定的任务总是发生在所有异步任务之前,当前主线程的末尾。
nextTick虽然也会异步执行,但是不会给其他io事件执行的任何机会)
两者都代表主线程完成后立即执行,但是setTimeOut 比setImmediate 概率更大
setTimeout采用的是类似IO观察者,setImmediate采用的是check观察者,而process.nextTick()采用的是idle观察者
6.如何实现一个sleep
function sleep (ms) { let currentTime = Date.now(); let sleepEnd = currentTime + ms; while(Date.now() < sleepEnd) ; return; }
7.如何实现reduce
Array.prototype.myReduce = function (cb) {
var preValue = undefined;
var value = undefined;
var index = 0;
var result = 0;
for (var i = 0; i < this.length - 1; i++) {
if (index === 0) {
preValue = this[index];
} else {
preValue = result;
}
value = this[index + 1];
result = cb(preValue, value);
index++;
}
console.log('result', result);
return result;
};
var arr = [1, 2, 3, 5, 60];
arr.myReduce(function (a, b) {
return a + b;
});
8.读取定义好的配置变量的库
dotenv, node-config
9.进程的当前目录是什么?有什么作用
当前进程启动的目录, 通过 process.cwd() 获取当前工作目录 (current working directory)
它是命令行启动的时候所在目录,文件操作等使用相对路径的时候会相对当前工作目录来获取文件
10.child.kill 与 child.send
如 child.kill 与 child.send 的区别. 二者一个是基于信号系统, 一个是基于 IPC.
11.父进程或子进程的死亡是否会影响对方? 什么是孤儿进程?
子进程死亡不会影响父进程, 不过子进程死亡时(线程组的最后一个线程,通常是“领头”线程死亡时),会向它的父进程发送死亡信号. 反之父进程死亡, 一般情况下子进程也会随之死亡, 但如果此时子进程处于可运行态、僵死状态等等的话, 子进程将被进程1(init 进程)收养,从而成为孤儿进程
12.Cluster
Cluster 是常见的 Node.js 利用多核的办法. 它是基于 child_process.fork() 实现的, 所以 cluster 产生的进程之间是通过 IPC 来通信的, 并且它也没有拷贝父进程的空间, 而是通过加入 cluster.isMaster 这个标识, 来区分父进程以及子进程, 达到类似 POSIX 的 fork 的效果.
13.如何实现进程间通信(IPC)
类型 | 无连接 | 可靠 | 流控制 | 优先级 |
---|---|---|---|---|
普通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 |
14.在 IPC 通道建立之前, 父进程与子进程是怎么通信的? 如果没有通信, 那 IPC 是怎么建立的?
这个问题也挺简单, 只是个思路的问题. 在通过 child_process 建立子进程的时候, 是可以指定子进程的 env (环境变量) 的. 所以 Node.js 在启动子进程的时候, 主进程先建立 IPC 频道, 然后将 IPC 频道的 fd (文件描述符) 通过环境变量 (NODE_CHANNEL_FD) 的方式传递给子进程, 然后子进程通过 fd 连上 IPC 与父进程建立连接.
15.守护进程
最起码要知道通过 pm2 之类的工具可以将进程以守护进程的方式启动;
守护进程的启动方式:
1. 创建一个进程A。
2. 在进程A中创建进程B,我们可以使用fork方式,或者其他方法。
3. 对进程B执行 setsid 方法。
4. 进程A退出,进程B由init进程接管。此时进程B为守护进程。
setsid 主要完成三件事:
1. 该进程变成一个新会话的会话领导。
2. 该进程变成一个新进程组的组长。
3. 该进程没有控制终端。
然而,Nodejs中并没有对 setsid 方法的直接封装,翻阅文档发现有一个地方是可以调用该方法的。
var spawn = require('child_process').spawn;
var process = require('process');
var p = spawn('node',['b.js'],{
detached : true
});
console.log(process.pid, p.pid);
process.exit(0);
在 spawn 的第三个参数中,可以设置 detached 属性,如果该属性为true,则会调用 setsid 方法。这样就满足我们对守护进程的要求。
16.Buffer一般用于什么数据,其大小是否可以动态变化
一般用于二进制数据,与IO相关的操作(网络/文件等)均基于Buffer,一旦创建大小固定不变,内存在V8堆栈外分配原始内存空间;
new Buffer(),在6.x之后废弃不用
接口 | 用途
--------- | ------------- |
Buffer.from| 根据已有数据生成
Buffer.alloc | 创建一个初始化的
Buffer.allocUnsafe | 创建一个未初始化的
17.String Decode
字符串解码器 (String Decoder) 是一个用于将 Buffer 拿来 decode 到 string 的模块, 是作为 Buffer.toString 的一个补充, 它支持多字节 UTF-8 和 UTF-16 字符. 例如
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)); // €
18.Stream 流
你要拷贝一个 20G 大的文件, 如果你一次性将 20G 的数据读入到内存, 你的内存条可能不够用, 或者严重影响性能. 但是你如果使用一个 1MB 大小的缓存 (buf) 每次读取 1Mb, 然后写入 1Mb, 那么不论这个文件多大都只会占用 1Mb 的内存.
19.Console
console.log 正常情况下是异步的, 除非你使用 new Console(stdout[, stderr]) 指定了一个文件为目的地
实现:
Console.prototype.log = function(...args) {
this._stdout.write(`${util.format.apply(null, args)}\n`);
};
20.Net,粘包
默认情况下, TCP 连接会启用延迟传送算法 (Nagle 算法), 在数据发送之前缓存他们. 如果短时间有多个数据发送, 会缓冲到一起作一次发送 (缓冲大小见 socket.bufferSize), 这样可以减少 IO 消耗提高性能.
然而会出现以下问题:
•先接收到 data1 的部分数据, 然后接收到 data1 余下的部分以及 data2 的全部.
•先接收到了 data1 的全部数据和 data2 的部分数据, 然后接收到了 data2 的余下的数据.
•一次性接收到了 data1 和 data2 的全部数据.
解决方法:
封包/拆包是目前业内常见的解决方案了. 即给每个数据包在发送之前, 于其前/后放一些有特征的数据, 然后收到数据的时候根据特征数据分割出来各个数据包.
21.resultful
(1)每一个URI代表一种资源;
(2)客户端和服务器之间,传递这种资源的某种表现层;
(3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"。
GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。
例如我要获取新闻列表
url如果是 xxx/xxx/getnews这是不合适的
应该是 xxx/xxx/news 用get方法
POST 和 PUT 有什么区别?
POST 是新建 (create) 资源, 非幂等, 同一个请求如果重复 POST 会新建多个资源. PUT 是 Update/Replace, 幂等, 同一个 PUT 请求重复操作会得到同样的结果.
22.cookie 与 session 的区别? 服务端如何清除 cookie?
主要区别在于, session 存在服务端, cookie 存在客户端. session 比 cookie 更安全. 而且 cookie 不一定一直能用 (可能被浏览器关掉). 服务端可以通过设置 cookie 的值为空并设置一个及时的 expires 来清除存在客户端上的 cookie.
23.什么是跨域请求? 如何允许跨域?
出于安全考虑, 默认情况下使用 XMLHttpRequest 和 Fetch 发起 HTTP 请求必须遵守同源策略, 即只能向相同域名请求. 向不同域名的请求被称作跨域请求 (cross-origin HTTP request). 可以通过设置 CORS headers 即 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;
}
24.socket hang up
hang up 有挂断的意思, socket hang up 也可以理解为 socket 被挂断. 在 Node.js 中当你要 response 一个请求的时候, 发现该这个 socket 已经被 "挂断", 就会就会报 socket hang up 错误.
典型的情况是用户使用浏览器, 请求的时间有点长, 然后用户简单的按了一下 F5 刷新页面. 这个操作会让浏览器取消之前的请求, 然后导致服务端 throw 了一个 socket hang up.
25.hosts 文件是什么? 什么叫 DNS 本地解析?
hosts 文件是个没有扩展名的系统文件, 其作用就是将网址域名与其对应的 IP 地址建立一个关联“数据库”, 当用户在浏览器中输入一个需要登录的网址时, 系统会首先自动从 hosts 文件中寻找对应的IP地址.
26.什么是 TTY?
TTY,任何表现的像打字机的设备, 例如终端 (terminal).
你可以通过 w 命令查看当前登录的用户情况, 你会发现每登录了一个窗口就会有一个新的 tty.
27.OS
通过 OS 模块可以获取到当前系统一些基础信息的辅助函数.
28.负载
负载是衡量服务器运行状态的一个重要概念. 通过负载情况, 我们可以知道服务器目前状态是清闲, 良好, 繁忙还是即将 crash.
通常我们要查看的负载是 CPU 负载
命令行上可以通过 uptime, top 命令, Node.js 中可以通过 os.loadavg() 来获取当前系统的负载情况:
PID COMMAND %CPU TIME #TH #WQ #PORT MEM PURG CMPRS PGRP
22112 top 1.8 00:02.37 1/1 0 21 2504K 0B 0B 22112
22100 Google Chrom 1.3 00:06.09 12 0 105+ 56M+ 0B 0B 1479
22099 ocspd 0.0 00:00.02 2 0 30 752K- 0B 544K+ 22099
22009 mdworker 0.0 00:00.03 3 0 40 968K 0B 1908K 22009
21539 netbiosd 0.0 00:00.03 2 1 38 588K 0B 1864K 21539
21444 eapolclient 0.0 00:00.14 3 0 55 1792K 0B 492K 21444
浏览器果然是杀器
对于服务端程序员而言, 完整的服务器 checklist 首推 《性能之巅》 第二章中讲述的 USE 方法
29.怎么处理未预料的出错? 用 try/catch ,domains 还是其它什么?
• callback(err, data) 回调约定
• throw / try / catch
• EventEmitter 的 error 事件
30.内存快照
内存快照常用与解决内存泄漏的问题. 快照工具推荐使用 heapdump 用来保存内存快照, 使用 devtool 来查看内存快照. 使用 heapdump 保存内存快照时, 只会有 Node.js 环境中的对象, 不会受到干扰(如果使用 node-inspector 的话, 快照中会有前端的变量干扰).
31.测试
项目在多人合作的时候, 为了某个功能修改了某个模块的某部分代码, 实际的情况中修改一个地方可能会影响到别人开发的多个功能, 在自己不知情的情况下想要保证自己修改的代码不影响到其他功能, 最简单的办法是通过测试来保证.
黑盒测试 (Black-box Testing), 测试应用程序的功能, 而不是其内部结构或运作.
白盒测试 (White-box Testing) 测试应用程序的内部结构或运作, 而不是测试应用程序的功能
单元测试 (Unit Testing) 是白盒测试的一种, 用于针对程序模块进行正确性检验的测试工作
测试覆盖率 (Test Coverage) 是指代码中各项逻辑被测试覆盖到的比率, 比如 90% 的覆盖率, 是指代码中 90% 的情况都被测试覆盖到了.
覆盖率通常由四个维度贡献:
• 行覆盖率 (line coverage) 是否每一行都执行了?
• 函数覆盖率 (function coverage) 是否每个函数都调用了?
• 分支覆盖率 (branch coverage) 是否每个if代码块都执行了?
• 语句覆盖率 (statement coverage) 是否每个语句都执行了?
常用的测试覆盖率框架 istanbul.
压力测试 (Stress testing), 是保证系统稳定性的一种测试方法. 通过预估系统所需要承载的 QPS, TPS 等指标, 然后通过如 Jmeter 等压测工具模拟相应的请求情况, 来验证当前应能能否达到目标.
对于比较重要, 流量较高或者后期业务量会持续增长的系统, 进行压力测试是保证项目品质的重要环节. 常见的如负载是否均衡, 带宽是否合理, 以及磁盘 IO 网络 IO 等问题都可以通过比较极限的压力测试暴露出来.
断言 (Assert) 是快速判断并对不符合预期的情况进行报错的模块. 是将:
32.HTTP 如何通过 GET 方法 (URL) 传递 let arr = [1,2,3,4] 给服务器
第三方
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' ] }
33.数据库范式
范式是为了消除重复数据减少冗余数据,从而让数据库内的数据更好的组织,让磁盘空间得到更有效利用的一种标准化标准 第一范式(1NF) 如果一个关系模式R的所有属性都是不可分的基本数据项,则R∈1NF。 简单的说,第一范式就是每一个属性都不可再分 比如:address,这个是可再分为城市,街道,号码等 第二范式: 若关系模式R∈1NF,并且每一个非主属性都完全函数依赖于R的码,则R∈2NF 简单的说,是表中的属性必须完全依赖于全部主键,而不是部分主键 例如一个表中有两个主键,当一个字段只依赖于一个字段的时候,就要拆分成两个表 第三范式:数据库属性不能互相依赖
详情请看http://www.cnblogs.com/CareySon/archive/2010/02/16/1668803.html/
34.MySql 主键与索引,索引有什么用,大致原理是什么? 设计索引有什么注意点?
主键
主键ID,可以一列或多列,主键既是约束也是索引且是唯一索引,同时也用于对象缓存的键值。
索引是用空间换时间的一种优化策略
组合或者引用关系的子表(数据量较大的时候),需要在关联主表的列上建立非聚集索引(如订单明细表中的产品ID字段、订单明细表中关联的订单ID字段)
注意点
表中如果建有大量索引将会影响INSERT、UPDATET、DELETE语句的性能
1.选择唯一性索引
2.为经常需要排序、分组和联合操作的字段建立索引
3.为常作为查询条件的字段建立索引
4.限制索引的数目
5.尽量使用数据量少的索引
6.尽量使用前缀来索引
7.删除不再使用或者很少使用的索引
原理:
数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。
在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引
35.Monogdb 连接问题(超时/断开等)有可能是什么问题导致的?
• 网络问题
• 任务跑不完, 超过了 driver 的默认链接超时时间 (如 30s)
• Monogdb 宕机了
• 超过了连接空闲时间 (connection idle time) 被断开
• fd 不够用 (ulimit 设置)
• mongodb 最大连接数不够用 (可能是连接未复用导致)
• etc...
36.脏数据如何产生,数据一致性是什么意思?
• 从 A 帐号中把余额读出来
• 对 A 帐号做减法操作
• 把结果写回 A 帐号中
• 从 B 帐号中把余额读出来
• 对 B 帐号做加法操作
• 把结果写回 B 帐号中
这6件事, 要么都成功做完, 要么都不成功, 而且这个操作的过程中, 对A、B帐号的其它访问必需锁死, 所谓锁死就是要排除其它的读写操作, 否则就会出现脏数据 ---- 即数据一致性的问题.
- XSS 攻击是什么?怎么避免?
跨站脚本 (Cross-Site Scripting, XSS) 是一种代码注入方式, 为了与 CSS 区分所以被称作 XSS. 早期常见于网络论坛, 起因是网站没有对用户的输入进行严格的限制, 使得攻击者可以将脚本上传到帖子让其他人浏览到有恶意脚本的页面
攻击的方法:
等方法
CSP策略:
CSP 的实质就是白名单制度,开发者明确告诉客户端,哪些外部资源可以加载和执行,等同于提供白名单。它的实现和执行全部由浏览器完成,开发者只需提供配置
两种方法可以启用 CSP。一种是通过 HTTP 头信息的Content-Security-Policy的字段。
Content-Security-Policy: script-src 'self'; object-src 'none';
style-src cdn.example.org third-party.org; child-src https:
另一种是通过网页的标签。
上面的代码的意思:
脚本:只信任当前域名
CSRF
跨站请求伪造 (Cross-Site Request Forgery, CSRF, https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet) 是一种伪造跨站请求的攻击方式. 例如利用你在 A 站 (攻击目标) 的 cookie / 权限等, 在 B 站 (恶意/钓鱼网站) 拼装 A 站的请求.
攻击方式,包括但是不限于
(xss), 攻击者的恶意网站, email 邮件, 微博, 微信, 短信等及时消息.
1.同源检查
通过检查来过滤简单的 CSRF 攻击, 主要检查一下两个 header:
• Origin Header
• Referer Header
token
简单来说, 对需要预防的请求, 通过
2.特别的算法生成 token 存在 session 中, 然后将 token 隐藏在正确的界面表单中, 正式请求时带上该 token 在服务端验证, 避免跨站请求
sql注入:
Sql 注入是网站常见的一种注入攻击方式. 其原因主要是由于登录时需要验证用户名/密码, 其执行 sql 类似:
SELECT * FROM users WHERE usernae = 'myName' AND password = 'mySecret';
其中的用户名和密码属于用户输入的部分, 那么在未做检查的情况下, 用户可能拼接恶意的字符串来达到其某种目的, 例如上传密码为 '; DROP TABLE users; -- 使得最终执行的内容为:
SELECT * FROM users WHERE usernae = 'myName' AND password = ''; DROP TABLE users; --';
解决方法:
• 给表名/字段名加前缀 (避免被猜到)
• 报错隐藏表信息 (避免被看到, 12306 早起就出现过的问题)
• 过滤可以拼接 SQL 的关键字符
• 对用户输入进行转义
• 验证用户输入的类型 (避免 limit, order by 等注入)