AirJD 焦点
  • 本月热门
AirJD

没有录音文件
00:00/00:00
加收藏

探究 Node.js 的服务端之路

发布者 kernal
发布于 1492609517577  浏览 6863 关键词 Node.JS, 微服务 
分享到

第1页

探究 Node.js 的服务端之路

ElemeFe - 黄鼎恒



第2页

大纲

1. 前端 -> Node.js -> 后端 2. V8 内存的简介 3. Node.js 服务端开发的常见问题



第3页

2016 Developer Occupations



Full-Stack Web Developer Back-End Web Developer

Student Mobile Developer (Android, IOS, etc)

Desktop Developer Front-End Web Developer



12.2% 11.4% 8.4% 6.9% 5.8%



28.0%



第4页

Node.js in Eleme



实时推送



中间件



快速建站



第5页

Front to Back

业务差异 环境差异 思想差异



业务

环境

思想



第6页

展示



状态管理



需求频繁变动 推送



样式



面向DB

面向DOM操作



通信



业务差异



效果



面向View

数据量大多对多



交互



数据一致性



数据量小一对一



第7页

基于操作系统提供的接口



基于浏览器提供的接口/对象



Polyfill



webview 兼容



灾难备份



环境差异



浏览器兼容



响应式



Scalable



分布式



第8页

Frontend 快速开发 快速渲染 视觉效果



思想差异



Backend 稳定 性能 负载



第9页

Backend’s Perspective



性能 负载 稳定性



—— n% 流量丢失 —— m% 流量丢失 —— 100% 流量丢失



第10页

Backend’s Perspective



稳定性因素

• CPU • 内存容量 • 网络 I/O • 存储设备 I/O • 存储容量 • 存储控制器 • 网络控制器 • CPU 互联 • 内存互联 • I/O 互联



I/O 互联



存储设备 I/O



内存容量



网络 I/O



CPU



第11页

V8 内存结构

Node.js - 后端

V8 JavaScript Engine

Chrome 浏览器 - 前端



第12页

#include <stdio.h>



void init() {

int arr[5] = {}; for (int i = 0; i < 5; ++i) {

arr[i] = i; } }

void test() {

int arr[5]; for (int i = 0; i < 5; ++i) {

printf("%d\n", arr[i]); } }



初始化 int arr[5]



输出结果:



主函数



0 1



输出未初始化的 a32rr



调用 init 4 调用 test



int main(int argc, char const *argv[]) {

init(); test(); return 0; }



第13页

#include <stdio.h>



void init() {

int arr[5] = {}; for (int i = 0; i < 5; ++i) {

arr[i] = i; } }



void test() {

int arr[5]; for (int i = 0; i < 5; ++i) {

printf("%d\n", arr[i]); } }



主函数

init test



int main(int argc, char const *argv[]) {

init(); test(); return 0; }



栈顶 栈顶

打印 打印 打印 打印 栈顶 打印





umnkainnow unitnkesniotw unk0now unk1now unk2now unk3now unk4now unknow





第14页

function test() { let number = 123; return number;

}

function init() { let arr = [1,2,3,4,5]; return { get: (i) => arr[i] };

}

let {get} = init(); let n = test();



栈 …



第15页

V8 New Space



1. 准备两个 Stack

2. 函数执行完不释放内存

3. 当正在使用的 Stack 快满, 将不打 算释放的内存到另外一个 Stack

4. 释放整个旧的 stack, 使用新 Stack



Stack Stack 12



第16页

V8 Old Space



New Space



将幸存2次以上 对象挪动到 Old space



Old Space



第17页

内存结构



新生代 老生代



New Space Old Space Large Object Space Map Space Code Space



新数据存储区 老数据存储区 大对象存储区 对象结构信息 机器码存储区



第18页

Garbage Collection

Scavenge

准备两份内存轮番使用, 当某分内存即将用完时切换

Mark-Sweep/Compact

根据引用标记对象后视情况选择方法清理



第19页

Garbage Collection

root

V8 的 GC 的触发由 v8 自身决定(内含默认4个 线程)。如果要需要主动触发 GC,可以在 node 启动时加上 -expose-gc 参数(不推荐)。



第20页

Memory Leak



当变量挂靠在 root 以及 module.exports 等全局变量 上时,对象的内存始终不会释放

异常产生之后没有正确 恢复状态可能导致内存泄漏



全局变 量



闭包



闭包因为写法的问题 容易使得闭包引用的作用域内存泄漏



引用被持有



错异误常处 理



事件监 听



对同一个事件重复监听 或者忘记移除

容易导致内存泄漏



第21页

Memory Leak

Node.js 的事件监听也可能出现的内存泄漏。例如对同一个事件重复监听,忘记移除( removeListener),将造成内存泄漏。这种情况很容易在复用对象上添加事件时出现, 所以事件重复监听可能收到如下警告:

(node:2752) Warning: Possible EventEmitter memory leak detected。 11 myTest listeners added。Use emitter。setMaxListeners() to increase limit

例如,Node.js 中 Http.Agent可能造成的内存泄漏。当 Agent keepAlive 为 true 或者短时间有大量情况的时候,都会复用之前使用过的 socket,如果此时在 socket 上添加事件监听,忘记清除的话,因为 socket 的复用,将导致事件重复监听 从而产生内存泄漏。



第22页

Common Problem

异步



部署



弱计算



Dev



内存泄漏



Ops 分布式 维护



第23页

异步

1. 流程处理曲折 2. 闭包持有引用的释放问题 3. 异步队列的问题 4. 不适合处理复杂的状态机



第24页

异步

1. 使用 Promise, Async/await 等现代方式 2. 学习 V8 内存结构了解闭包释放问题 3. 遵循 immutable 等思想减少操作副作用 4. 使用队列结构进行可控的异步并发



第25页

异步队列



第26页

Common Problem

异步



部署



弱计算



Dev



内存泄漏



Ops 分布式 维护



第27页

弱计算

Node.js 擅长 IO 密集型应用 不擅长 CPU 密集型



第28页

弱计算

什么是 IO 密集型? 什么是 CPU 密集型?



第29页

弱计算

输入设备



存储器



输出设备



运算器



控制器

CPU



数据流



指令流



控制流



第30页

弱计算



1. 确认业务类型



{ CPU密集型 IO密集型



2.了解 CPU 在干什么



{ 死循环 序列化对象



{3.常规监控



利用率



饱和度



GC

Crypt...



第31页

弱计算

CPU Profilling



火焰图



webstorm 工具 https://www.jetbrains.com/help/webstorm/2016.2/v8-cpu-and-memory-profiling.html



第32页

Common Problem

异步



部署



弱计算



Dev



内存泄漏



Ops 分布式 维护



第33页

内存泄漏

• 1. 引用问题: 不论是闭包编写,还是事件监听没注意释放。你需要知道什么情况下, 你的引

用是被持有的,什么情况下又不是。所有引用问题归结到最后都是 v8 内存释放原理了解程度的问题 。

• 2. 队列问题: 一个流程调用的过程中可能经过了非常多个过程,包括通信层、业务层、数据

层等,其中每一个层级对于事务的处理都存在的队列的问题,你需要避免整个流程中某个环节的负载 超过其能处理的上限。

• 3. CPU问题: 引用的释放(GC)、队列设置的不合理(超过负载)、业务逻辑的编写问题(

死循环)都可能导致 CPU 资源紧张,而 CPU 资源紧张同样会导致内存泄漏(没有足够的 CPU 执 行 GC 操作,释放速度赶不上生产速度)。

• 4. 错误处理: 错误出现之后,如果没有对当前流程进行正确的状态恢复,可能导致错误状态

下的内存始终得不到释放从而导致内存泄漏。



第34页

Common Problem

异步



部署



弱计算



Dev



内存泄漏



分布式 Ops

维护



第35页

部署

Node.js 的 child_process.fork 与

POSIX 的 fork



第36页

部署

• 1. exec: 启动一个子进程来执行命令,调用 bash 来解释命令, 所以如果有命令有外部参数,

则需要注意被注入的情况。

• 2. spawn: 更安全的启动一个子进程来执行命令,使用 option 传入各种参数来设置子进程的

stdin、stdout 等。通过内置的命名管道来与子进程建立 IPC 通信。

• 3. fork: spawn 的特殊情况,专门用来产生 worker 或者 worker 池。 返回值是

ChildProcess 对象可以方便的与子进程交互。

Node.js 的 child_process.fork() 不像 POSIX fork(2) 系统调用, 不会克隆当前父进程的空间,也不需要 手动的等待子进程结束回收。



第37页

部署



1. IPC 通信问题



{ RPC通过 Node.js 的 fork 创建的进程使用的是其内置的 IPC (进程间通信)功能,该功能基于管道实现。 架构调整目前在实践使用过程中发现其自带的 IPC 通信功能较弱,在传 输较大数据(1MB以上)是存在性能问题,所以不推荐使用。



2. swap 内存异常



{ master 健康检查 在线上部署的过程中出现机器的 swap 内存爆满情况,排查

发现多进程模式中存在 master 死亡后没有通知到

worke节r 终点止进注程册,使(得 zwoorkoekr e成e为p孤e儿r进程被等系)统

init 领养,在长时间无请求的情况下将 worker 的内存

折叠进入 swap 内存。



第38页

Common Problem

异步



部署



弱计算



Dev



内存泄漏



Ops 分布式 维护



第39页

分布式



1. cluster 的负载均衡



句柄共享 (win)



由主进程创建 socket 监听端口后,将 socket 句 柄直接分发给相应的 worker,然后当连接进来时, 就直接由相应的 worker 来接收连接并处理。 多个 worker 之间会存在竞争关系,产生“惊群效应”



round-robin (*nix)



通过时间片轮转法(round-robin)分发连接. 主进程 监听端口, 接收到新连接之后, 通过时间片轮转法来决 定将接收到的客户端的 socket 句柄传递给指定的 worker 处理. 至于每个连接由哪个 worker 来处理, 完全由内置的循环算法决定.



第40页

分布式



2. 数据一致性



Mongodb 弱项



MonogoDB 的事务能力分常弱,如果你的 业务对数据一致性有要求,请更换事务能力 更强的数据库,比如 mysql 的 innodb



异步的先天弱势



异步的写法以及异步的流程,使得失 败操作的回滚存在先天的困难。



第41页

Common Problem

异步



部署



弱计算



Dev



内存泄漏



Ops 分布式 维护



第42页

维护

• 1.日志思想: 与传统语言不同,Node.js 操作系统资源的形式几乎都是异步,不像 PHP 等传

统的情况可以使用 Dtrace 等动态分析工具。并且异步的情况下,错误栈存在曲折、截断的情况。

• 2.异常处理: 上文中有提到过,异常出现之后,如果没有进行正确的恢复操作可能导致内存泄

漏。其根本原因在于一些异常产生之后 Node.js 的行为是未定义的。官方有明确表示 process 的 uncaughtException 事件是为了让你准备之后再 exit 进程。

• 3.依赖管理: 2016年的 left-pad 事件让大家意识到了使用第三方依赖存在的安全隐患,同时

如果版本未做明确限制的话依赖的模块如果出现 breaking change 可能导致项目构建失败。

维护并不是一个孤立的问题,包括前面提到的异步、CPU、内存、部署、分布式,某一个环节如果架构 的不好,必然导致后期维护困难。



第43页

Common Problem

异步



部署



弱计算



Dev



内存泄漏



Ops 分布式 维护



第44页

大型应用的短板



第45页

microservice arch微i服te务cture



第46页

Step by Step

Node.js 基金会成立



ECMA 2017



第47页

JavaScript 称霸 Github

官方 Security 平台

npms.io nodesecurity.io



NPM 数目破 40万



第48页

Node.js v7.6 发 布



加入 Linux 基金会





第49页

*.js pattern



第50页

Node.js 面试github.com/ElemeFE/node-interview

微博@Lellansin



支持文件格式:*.pdf
上传最后阶段需要进行在线转换,可能需要1~2分钟,请耐心等待。