Node.js Web 介绍

nodejs

Node.js

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。 Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型。1

Node.js作者认为 Web服务器的几个要点:

  • 事件驱动
  • 非阻塞I/O

特性

JavaScript

对比其它语言,选择JavaScript的原因:

  • 对比语言
    • C的开发门槛高,业务开发困难
    • Haskell太过复杂
    • Lua本身有很多阻塞IO库
    • Ruby虚拟机性能不好
  • JavaScript的优势
    • 开发门槛低
    • 没有阻塞IO库的历史包袱
    • JavaScript在浏览器广泛使用事件驱动
    • V8的性能优势

Chrome浏览器和Node的组件结构

异步I/O

IO密集型 编程模式的发展:

进程 ——> 线程 ——> 异步 ——> 协程

异步I/O的调用示意图

异步I/O的优势:

  • 用户体验
  • 资源分配
阻塞与非阻塞
  • 阻塞IO的一个特点是 调用之后一定要等待 系统内核层面完成操作后,调用才结束
  • 非阻塞IO 调用后会立即返回

非阻塞的问题: 如何确定I/O操作是否完成 —— 轮询 8

  1. 循环read: 通过重复调用来 查询I/O状态。最原始,性能最低
  2. select

    • I/O多路复用,通过把多个I/O的阻塞复用到同一个select的阻塞上
    • 通过对文件描述符上的事件状态来进行判断
    • 存储状态采用1024长度的数组,限制了同时检查的数量
  3. poll

    • 对select的改进
    • 采用链表的方式避免数组长度的限制
  4. epoll

    • 基于事件驱动的I/O方式
    • 将用户关心的文件描述符的事件存放到内核的一个事件表
    • 用户线程调用后进入睡眠状态
    • 数据准备好后内核唤醒,然后只要遍历Ready队列的描述符集合
    • 红黑树存储
  5. AIO(异步I/O方式)

    • 通过信号或回调传递数据
    • 有系统实现上的缺陷,无法利用系统缓存
  6. 线程池模拟异步I/O

    • 第三方实现
      • glibc
      • libeio: 采用线程池与阻塞I/O模拟异步I/O (node v0.9.3之前采用方式)
    • node 自实现了线程池异步I/O
    • Windows的IOCP: 异步方法,等待I/O完成后通知,执行回调,用户无需轮询,线程池由系统内核管理
    • libuv: 封装了Windows 和 *nix 差异,提供抽象层

基于libuv的框架示意图

事件驱动

Node 自身的执行模型 —— 事件循环(Event Loop)
事件驱动的实质:通过主循环(Event Loop)加事件触发的方式来运行程序

Event Loop

node进程启动时,会创建一个类似于 while(true) 的循环,每执行一次循环体的过程 称为Tick.
每个Tick的过程就是查看是否有事件待处理,如果有,就取出事件的回调函数 执行;然后进入下次循环。

Node.js 的运行机制如下:

  • V8 引擎解析 JavaScript 脚本。
  • 解析后的代码,调用 Node API。
  • libuv 库负责 Node API 的执行。它将不同的任务分配给不同的线程,形成一个 Event Loop(事件循环),以异步的方式将任务的执行结果返回给 V8 引擎。
  • V8 引擎再将结果返回给用户。
libuv引擎中的事件循环

分为 6 个阶段:

Node 的 Event Loop 分为 6 个阶段,它们会按照顺序反复运行。每当进入某一个阶段的时候,都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量到达系统设定的阈值,就会进入下一阶段。

node event loop

  • timer
    timers 阶段会执行 setTimeoutsetInterval 回调,并且是由 poll 阶段控制的。
    同样,在 Node 中定时器指定的时间也不是准确时间,只能是尽快执行。

  • I/O
    I/O 阶段会处理一些上一轮循环中的少数未执行的 I/O 回调

  • idle, prepare
    idle, prepare 阶段内部实现

  • poll
    poll 是一个至关重要的阶段,这一阶段中,系统会做两件事情

  1. 回到 timer 阶段执行回调
  2. 执行 I/O 回调
    并且在进入该阶段时如果没有设定了 timer 的话,会发生以下两件事情

    • 如果 poll 队列不为空,会遍历回调队列并同步执行,直到队列为空或者达到系统限制
    • 如果 poll 队列为空时,会有两件事发生
      • 如果有 setImmediate 回调需要执行,poll 阶段会停止并且进入到 check 阶段执行回调
      • 如果没有 setImmediate 回调需要执行,会等待回调被加入到队列中并立即执行回调,这里同样会有个超时时间设置防止一直等待下去

当然设定了 timer 的话且 poll 队列为空,则会判断是否有 timer 超时,如果有的话会回到 timer 阶段执行回调。

  • check

check 阶段执行 setImmediate

  • close callbacks
    close callbacks 阶段执行 close 事件
对比一下: 浏览器中的 Event Loop

任务源会被分配到不同的 Task 队列中,任务源可以分为 微任务(microtask) 和 宏任务(macrotask)。在 ES6 规范中,microtask 称为 jobs,macrotask 称为 task。

  • 微任务 包括 process.nextTick ,promise ,MutationObserver。

  • 宏任务 包括 script , setTimeout ,setInterval ,setImmediate ,I/O ,UI rendering。

event loop

Event Loop 执行顺序如下所示:

  • 首先执行同步代码,这属于宏任务
  • 当执行完所有同步代码后,执行栈为空,查询是否有异步代码需要执行
  • 执行所有微任务
  • 当执行完所有微任务后,如有必要会渲染页面
  • 然后开始下一轮 Event Loop,执行宏任务中的异步代码,也就是 setTimeout 中的回调函数

单线程

Node在在V8引擎上构建,所以它的模式和浏览器类似,JavaScript运行在单个进程的单个线程上。

优点:

程序状态 单一,没有多线程情况中的 , 线程同步 等问题,减少操作系统在调度时的上下文切换

缺点:

  • 单线程 不能充分利用多核CPU – (性能)
  • 一旦单线程上抛出的异常没有被捕获,会引起整个进程的奔溃 – (健壮性/稳定性)
解决的方法:集群

主从模式 :

cluster

  • 主进程:负责调度或管理工作进程,不负责具体的业务处理
  • 工作进程:负责具体的业务处理

启动多个进程只是为了充分将CPU资源利用起来,而不是为了解决并发问题;并发是靠事件驱动的方式。

集群稳定需要考虑:

  • 性能问题
  • 多个工作进程的存活状态管理
  • 工作进程的平滑重启
  • 配置或静态数据的动态重新载入
  • 其它细节

Node Web

简单的实现

node.js 内置了 http package, 可以很容易的实现 web server的功能。

1
2
3
4
5
6
7
8
9
10
11
var http = require('http')

const PORT = 4337
const HOST = '127.0.0.1'

http.createServer(function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello World\n');
}).listen(PORT, HOST);

console.log(`服务运行: http://${HOST}:${PORT}/`)

封装中间件的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
const http = require('http')
const url = require('url')
const { pathRegexp } = require('./util')
const { resJSON, querystring } = require('./middleware')
const bodyParser = require('body-parser')

const routes = { all: [] };
const app = {};

app.use = function(path) {
let handle;
if (typeof path === 'string') {
handle = {
path: pathRegexp(path),
stack: Array.prototype.slice.call(arguments, 1)
};
} else {
handle = {
path: pathRegexp(),
stack: Array.prototype.slice.call(arguments, 0)
}
}
routes.all.push(handle)
}

const methods = ['get', 'put', 'delete', 'post']
methods.forEach(method => {
routes[method] = [];
app[method] = (path, action) => {
const handle = {
path: pathRegexp(path),
stack: action
};
routes[method].push(handle);
};
});

const handle = (req, res, stack) => {
const next = (err) => {
if (err) {
return handle500(err, req, res, stack)
}
const middleware = stack.shift();
if (middleware) {
// 传入next()函数自s身,使中间件能够执行结束后递归
try {
middleware(req, res, next)
} catch (error) {
next(error)
}
}
}

next();
}

app.use(resJSON);
app.use(bodyParser.json())
app.use(querystring);

const requestListener = (req, res) => {
const match = (pathname, routes) => {
let stacks = [];
for (let i = 0; i < routes.length; i++) {
const route = routes[i];
const reg = route.path.regexp;
const keys = route.path.keys;
const matched = reg.exec(pathname);
if (matched) {
const params = {};
for (let i = 0, l = keys.length; i < l; i++) {
const value = matched[i + 1];
if (value) {
params[keys[i]] = value;
}
}
req.params = params;
stacks = stacks.concat(route.stack)
}
}
return stacks;
}
const pathname = url.parse(req.url).pathname;
const method = req.method.toLowerCase();
let stacks = match(pathname, routes.all);
if (Object.hasOwnProperty.call(routes, method)) {
stacks = stacks.concat(match(pathname, routes[method]));
}

if (stacks.length) {
handle(req, res, stacks);
} else {
handle404(req. res);
}
}

app.server = (port, hostname) => {
http.createServer(requestListener).listen(port, hostname);
}

exports.app = app

Express 2

基于 Node.js 平台,快速、开放、极简的 Web 开发框架

Express 应用程序生成器:

1
npx express-generator --view=pug myapp

特性:

  • 保持最小规模的灵活
  • 使用您所选择的各种 HTTP 实用工具和中间件,快速方便地创建强大的 API
  • 提供精简的基本 Web 应用程序功能,而不会隐藏您了解和青睐的 Node.js 功能
  • 框架 / 许多 流行的开发框架 都基于 Express 构建
1
2
3
4
5
6
7
var express = require('express')
var app = express()

// respond with "hello world" when a GET request is made to the homepage
app.get('/', function (req, res) {
res.send('hello world')
})

Koa 3

Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。

koa wiki

🌰 : koa-rest-api-boilerplate

1
2
3
4
5
6
7
8
9
const Koa = require('koa');
const app = new Koa();

// response
app.use(ctx => {
ctx.body = 'Hello Koa';
});

app.listen(3000);

ThinkJs 4

ThinkJS 是一款面向未来开发的 Node.js 框架,整合了大量的项目最佳实践,让企业级开发变得更简单、高效。从 3.0 开始,框架底层基于 Koa 2.x 实现,兼容 Koa 的所有功能。

特性:

  • 基于 Koa 2.x,兼容 middleware
  • 内核小巧,支持 Extend、Adapter 等插件方式
  • 性能优异,单元测试覆盖程度高
  • 内置自动编译、自动更新机制,方便快速开发
  • 使用更优雅的 async/await 处理异步问题,不再支持 */yield
  • 从 3.2 开始支持 TypeScript

模仿 ThinkPHP 的代码模式

代码风格:

1
2
3
4
5
6
7
8
//src/controller/user.js

const Base = require('./base.js');
module.exports = class extends Base {
indexAction(){
this.body = 'hello world!';
}
}

nest 5

用于构建高效且可伸缩的服务端应用程序的渐进式Node.js框架。

特性:

  • 面向AOP编程
  • 支持typeorm
  • Node.js版的spring
  • 构建微服务应用

仿Angular和Spring的风格,注解形式的编码风格

代码风格:

1
2
3
4
5
6
7
8
9
10
import { Controller, Get, Req } from '@nestjs/common';
import { Request } from 'express';

@Controller('cats')
export class CatsController {
@Get()
findAll(@Req() request: Request): string {
return 'This action returns all cats';
}
}

Egg.js 6

Egg.js 为企业级框架和应用而生,我们希望由 Egg.js 孕育出更多上层框架,帮助开发团队和开发人员降低开发和维护成本。

特性:

  • 提供基于 Egg 定制上层框架的能力
  • 高度可扩展的插件机制
  • 内置多进程管理
  • 基于 Koa 开发,性能优异
  • 框架稳定,测试覆盖率高
  • 渐进式开发

egg 比较好的就是它的插件机制,和 统一的代码风格

eggjs 插件

eggjs 🌰

代码风格:

1
2
3
4
5
6
7
8
9
10
// app/controller/home.js
const Controller = require('egg').Controller;

class HomeController extends Controller {
async index() {
this.ctx.body = 'Hello world';
}
}

module.exports = HomeController;

MidwayJS 7

Midway是一个由typescript编写的Node.js Web框架,它利用IoC注入机制来解耦应用程序的业务逻辑,使大型Node.js应用程序的开发变得更简单、更自然。

特性:

  • ✔︎复杂的组中间件体系结构和兼容性
  • ✔︎可扩展的插件功能和生态组插件
  • ✔︎好的应用分层和分离能力
  • ✔︎很好的未来发展经验
  • ✔︎支持egg插件和koa的中间件

midway例子 🌰

TS编程语言,和注解形式的编码风格:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { provide, controller, inject, get } from 'midway';

@provide()
@controller('/user')
export class UserController {

@inject('userService')
service: IUserService;

@get('/:id')
async getUser(ctx): Promise<void> {
const id: number = ctx.params.id;
const user: IUserResult = await this.service.getUser({id});
ctx.body = {success: true, message: 'OK', data: user};
}
}
  1. Node.js 中文网
  2. Express 中文网
  3. Koa 中文网
  4. ThinkJs 中文网
  5. nest 中文网
  6. Egg.js
  7. MidwayJS

Powered by Hexo and Hexo-theme-hiker

Copyright © 2013 - 2021 朝着牛逼的道路一路狂奔 All Rights Reserved.

访客数 : | 访问量 :