Node 初识

什么是 Node

  • 是JS运行时环境
  • 构建在V8引擎之上
  • 通过libuv实现异步I/O
  • 目标是让并发编程更简单,主要应用在以网络编程为主的I/O密集型应用中,不适合CPU密集型应用。JAVA在基础平台建设及大数据等领域有着非常深厚的基础,直接使用即可。

image.png

  • V8 解释并执行JS
  • libuv 由事件循环和线程池组成,负责所有I/O任务的分发与执行。

Node 的特点

  • 构建传统网站
  • 构建移动端 API:在后端的 API 接口开发上封装一层专门供前端使用的 API Proxy 是非常有必要的。后端API接口开发用于数据库访问,将返回的数据进行包裹,以 HTTP 形式返回;API Proxy 针对前端使用的API进行优化,使前端开发更人性化。
  • 构建 RPC 服务:如 JAVA 的 Dubbo,Node 实现的 DNode、微服务工具集Seneca、跨语言的gRPC客户端。将数据库访问返回的数据以 TCP 形式传输给调用方。组装多个RPC服务,完成某项服务。将RPC服务包装成HTTP API。
  • 前后端分离:前端页面静态化;前端页面服务化(PAAS);服务端渲染(SSR); 渐进式Web应用(PWA)
  • 适用于Severless:无须关心运维(自带运维功能,一般集成Kubernetes),无须关心流量(自带服务网格功能,比如Istio,根据流量可以快速实例化容器),无须关心高并发(API层有缓存)。

Node 的应用场景

  • 跨平台开发:PC web、移动端、HTML5、React Native,Weex 以及 硬件 Ruff.io
  • 后端开发:面向网站、API、RPC 服务等
  • API: 绝大部分是I/O密集型应用,是node最擅长的
  • RPC:主要是对OLTP(联机事务处理过程)数据库进行操作,node可以支持,但如果是复杂计算,使用Go或JAVA会更好。
  • 前端开发:三大框架辅助开发及工程化演进过程(使用Gulp、Webpack构建Web开发工具)
  • 工具开发:主要是开发npm上的各种工具模块,包括各种前端预编译工具、构建工具Gulp、脚手架、命令行工具等
  • 数据挖掘
  • AI

Node 安装与入门

3m安装法

  • nvm: 解决多版本共存、切换、测试等问题
  • npm: 解决Node模块安装问题,其本身也是一个Node模块,每次安装都会内置某个版本的npm
  • nrm: 解决npm镜像访问慢的问题,提供测速、切换下载器(registry)功能

    1. curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash
    2. cat ~/.bashrc_nvm | grep nvm # 我自己的Mac电脑是把nvm命令的执行路径放到该文件下的。有可能你的在~/.bashrc
    3. source ~/.bashrc_nvm # 使系统环境变量生效
    4. vi ~/.zshrc # 手动将环境变量放到~/.zshrc里,将~/.bashrc_nvm的内容copy一份到这个文件中
    5. source ~/.zshrc # 使系统环境变量生效
    6. nvm ls-remote # 查看可安装的node版本
    7. nvm install 10.15.3
    8. which node
    9. nvm alias default node # 手动指定一个default别名
    10. node -v
    11. nvm ls
    12. nvm use stable # 切换版本
    13. nvm reinstall-packages stable # 一键安装全局模块

    ⚠️若经常切换版本,最痛苦的是全局模块需要重新安装。通过nvm reinstall-packages来解决

  • npm v2: 典型的树形依赖,层次深,依赖重叠,安装时间长,但相对更清晰

  • npm v3: 扁平化依赖,将依赖移到了顶层,同一模块多版本依赖时采用npm v2 模式,由于Node项目模块依赖非常多,因此在查找依赖时特别麻烦,虽没有npm v2用起来体验好,但下载速度确实快了不少。
  • npm v5: 自动记录依赖树,下载使用强校验,重写缓存系统等功能得到了升级和改造。代表性特征是新增了package.json文件,在操作依赖时默认生成,用于记录和锁定依赖树的信息,与Yarn类似。 | 命令 | 简写 | 说明 | | —- | —- | —- | | 无 | 无 | 将模块安装到本地 node_modules目录下,但不保存到package.json里 | | —save-prod | -P | 将模块安装到本地 node_modules目录下,同时保存到package.json里的dependencies,这在安装模块时是必须安装的 | | —save-dev | -D | 将模块安装到本地 node_modules目录下,同时保存到package.json里的devDependencies仅供开发时使用,一般测试模块等辅助开发工具都会这样安装 | | —global | -g | 安装的模块为全局模块,如果是命令行模块,会直接链接到环境变量里 |

如果用nvm安装的安装包放在用户目录的nvm版本对应的bin目录~/.nvm/versions/node/v13.3.0下,否则在/usr/local下。

  1. npm install --global nrm
  2. nrm test # 测速
  3. nrm ls
  4. nrm use cnpm
  5. nrm add yourcompany http://registry.npm.yourcompony.com/

hello node

  1. touch hello_node.js
  1. const http = require('http')
  2. http.createServer((req, res) => {
  3. let status = 200
  4. res.writeHead(status, { 'Content-Type': 'text-plain' })
  5. res.end('Hello Node')
  6. }).listen(3000, '127.0.0.1')
  7. console.log('Server running at http://127.0.0.1:3000/')

编程与调试

编程三等境界:

  • 打日志:最简单、便捷、略显低级
  • 断点调试:可以很直观地跟踪代码执行逻辑、调用栈、变量等
  • 测试驱动开发:在写代码之前先写测试,在保证代码质量、重构等方面有帮助

VSCODE

安装code命令

打开Command Palette(command shift P),并键入 shell command:Install 'code' command in PATH
image.png

调试

https://code.visualstudio.com/docs/editor/debugging

编号 名称 描述
1 Launch Program 直接执行
2 Launch via NPM 通过npm来启动
3 Attach to Port 附加端口,根据端口来查找进程
4 Attach to Process 附加到进程,即跨进程调试
5 Nodemon Setup 代码变动自动重启的 node monitor,
6 Mocha Tests 简单、强大的测试框架
7 Yeoman generator 使用最多的脚手架生成器
8 Gulp task 最流行的流式构建系统,

request 有两个选项,对应着两种调试模块:

  • launch:启动程序,相当于直接在编辑器里启动node.js,即本地调试
  • attach: 附加到进程,在编辑器外通过node —debug 命令启动,然后附加到 debug 进程中,即远程调试
    1. {
    2. "type": "node",
    3. "request": "attach",
    4. "name": "启动程序",
    5. "address": "localhost",
    6. "port": 5858
    7. },
    1. {
    2. "type": "chrome",
    3. "request": "launch",
    4. "name": "Chrome",
    5. "url": "http://127.0.0.1:8092/",
    6. "webRoot": "${workspaceRoot}"
    7. },
    1. {
    2. "type": "node",
    3. "request": "launch",
    4. "name": "Mocha JS",
    5. "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
    6. "args": [
    7. "-u",
    8. "tdd",
    9. "--timeout",
    10. "999999",
    11. "--compilers",
    12. "js:babel-core/register",
    13. "--colors",
    14. "${workspaceRoot}/test/js/**/*.spec.js"
    15. ],
    16. "internalConsoleOptions": "openOnSessionStart"
    17. },
    1. {
    2. "type": "node",
    3. "request": "launch",
    4. "name": "Mocha TS",
    5. "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
    6. "args": [
    7. "-u",
    8. "tdd",
    9. "--timeout",
    10. "999999",
    11. "-r",
    12. "ts-node/register",
    13. "--colors",
    14. "${workspaceRoot}/test/ts/**/**/*.spec.ts"
    15. ],
    16. "protocol": "auto",
    17. "internalConsoleOptions": "openOnSessionStart"
    18. }
    1. {
    2. "type": "node",
    3. "request": "attach",
    4. "name": "Attach to Remote",
    5. "address": "TCP/IP address of process to be debugged", // IP 地址或域名
    6. "port": 5858, // 端口
    7. "localRoot": "${workspaceRoot}", // 本地的代码根目录
    8. "remoteRoot": "Absolute path to the remote directory containing the program" // 远程代码的相对目录
    9. },

    更了不起的node

    架构变迁

    | | 单体式架构 | 页面即服务 | | —- | —- | —- | | 开发难度 | 越大越复杂 | 单个页面不会特别复杂,更容易把控 | | 可用性 | 容易雪崩,影响极大 | 单个页面崩溃对整体来说影响极小 | | 运维 | 容易 | 麻烦(必须配合DevOps) |

单体式架构(Monolithic Architecture)

  • 表现层:处理HTTP请求,直接返回HTML渲染,或者返回API结果。对于一个复杂的应用系统,表现层通常是代码中比较重要的部分。
  • 业务逻辑层:完成具体的业务逻辑,是应用的核心组成部分
    • service,通常组合多个DAO对象进行某项业务处理
    • controller里组装了多个service对象,可实现具体的功能
  • 数据访问层:访问基础数据,例如数据库、缓存和消息队列等

    • 定义model,数据库操作一般采用ORM来简化操作,模型会和数据库里的表进行关联映射
    • DAO(Data Access Object): 即CRUD,主要对单个模型进行操作

      页面即服务(page as service)

      :::info 请求 => nginx => node => 前端 :::
  • node层

    • 提供请求转发、反向代理等功能
    • 提供web服务,所有后端语言能做到的,node都能做到
    • 圈定范围,便于维护,只要修改功能包含在当前node项目范围内即可维护
    • 充当HTTP客户端,访问后端接口,但一般不直接访问数据库
  • nginx层
    • 负载均衡
    • 反向代理
    • 缓存
    • 限流限速
    • nginx需要繁杂的配置文件,且不能动态控制与实时生效,而openResty 只需要少量的lua代码即可

Node模块

分类 举例
压缩 UglifyJS
CSS预处理 Less
依赖管理 npm
模块系统 AMD | CMD | CMJ | ESM
模块加载 system.js
模块打包器 webpack + npm scripts
构建工具 gulp
模版引擎 jade | nunjucks
JS超集 TS(动态类型一时爽,代码重构火葬场)
跨平台打包 Electron(客户端) | cordova(移动端) | Ruff.io(物联网)
生成器 Yeoman | vue-cli | create-react-app
其他 imagemin | DataURL
后端 express | koa | egg
测试 AVA(支持新特性,基于Babel安装) | Mocha
数据库 Mongoose(MongoDB)

框架选型原则:

  • 考虑业务场景、特点,不必为了用而用,避免本末倒置
  • 考虑自身团队能力、喜好,有时候技术选型决定团队氛围,需要平衡激进与稳定
  • 保证在出现问题的时候有人能够做到源码级定制
  • 考虑安全、生态及历史遗留问题的处理

BFF

  • API Proxy
  • 服务组装
  • API 网关
    • 请求转发
    • 跨域JSONP支持

更好的node

编程范式

面向过程

如Express

面向对象

TS 无缝兼容ES6,且提供静态类型、接口、反射、范型等特性,在代码质量、抽象程度等方面都表现的比较好。

函数式编程

  • 一等公民是函数
  • 函数柯里化(curry): 传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数
  • 通过“函数组合”来解决函数嵌套问题
  • 函数式编程的思想如果是在团队中应用,还是要谨慎选择,持观望态度,因为其一学习成本高,其二维护成本高。

Metetor: 著名的同构 Web 框架
ThinkJS: 典型的结合 npm scripts 和 Babel 一起使用的框架

单线程会死

Node是单线程的,只要代码出错肯定是要崩溃的。

  • uncaughtException
  • try/catch 捕获异常
  • 自动重启:forever模块 | pm2
  • 通过cluster模块充分利用服务器多核优势,启动多个实例

即本质上单线程会死是个伪命题:

  • 单个应用实例可以适当捕获异常,减少崩溃几率
  • 单个应用实例崩溃之后,采用forever或pm2自动重启,可以继续服务
  • 利用多核集群同时在一台服务器上启动多个实例,崩溃的几率极低
  • 应用线上部署就只部署一台服务器吗?这种几率其实也很小,多台服务器也要做集群。
  • 如果所有集群中的服务器都崩溃了该怎么办?这其实是运维的问题,和Node无关

异步解决方案

解决方案 描述 推荐指数
callback Node API 天生就是这样的,异步必须要约定错误优先风格的问题 3
thunk 编译器的“传名调用”实现,通常做法是将参数放到一个临时的函数之中,再将这个临时函数传入函数体。 1
promise 最开始是社区定义的Promise/A+规范,大家习惯称之为Promise,随后成了ES6语言标准 4
generator Es6中的生成器,用于计算,但TJ想将其用作流程控制 1
co Generator用起来非常麻烦,故而TJ编写了co这个Generator生成器,用法简单 2
async NodeV7以上的版本都可以使用 5

Node 是如何执行的

node v8 为基础剖析核心原理:

  • V8 和 libuv 的作用
  • JS 是如何与C/C++通信的
  • 掌握模块机制
    1. ./configure --debug # 开启debug会进行两个CMake任务,且在源码根目录分别连接node.js和node_g,编译完成后,生成的文件将位于out/Release和out/Debug目录下
    2. make -j4
    3. sudo make install

    模块与核心

工具资源

vscode debugger:https://code.visualstudio.com/docs/editor/debugging
node 各版本对ES6的支持情况:https://node.green/