Skip to content

Commit 6af89be

Browse files
committed
update
1 parent bc3d8ca commit 6af89be

File tree

5 files changed

+205
-3
lines changed

5 files changed

+205
-3
lines changed

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ nodejs 是一种将 JS 作为语言的 server 端开发技术 —— 说起来
3838
- 异步 IO
3939
- 异步编程的问题
4040
- [玩转进程](./docs/06-进程.md)
41-
- 为何要启用多进程(利用 CPU 、避免 v8 内存限制)
41+
- 线程 vs 进程
42+
- 为何要启用多进程
4243
- child_process
43-
- cluster 和 pm2
44+
- cluster
4445
- [其他](./docs/07-其他.md)
4546
- 关于数据存储(如 mysql redis 等)
4647

code/process/demo1.js

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
const cluster = require('cluster')
2+
const os = require('os')
3+
const http = require('http')
4+
5+
if (cluster.isMaster) {
6+
console.log('是主进程')
7+
const cpus = os.cpus() // cpu 信息
8+
const cpusLength = cpus.length // cpu 核数
9+
for (let i = 0; i < cpusLength; i++) {
10+
// fork() 方法用于新建一个 worker 进程,上下文都复制主进程。只有主进程才能调用这个方法
11+
// 该方法返回一个 worker 对象。
12+
cluster.fork()
13+
}
14+
} else {
15+
console.log('不是主进程')
16+
// 运行该 demo 之后,可以运行 top 命令看下 node 的进程数量
17+
// 如果电脑是 4 核 CPU ,会生成 4 个子进程,另外还有 1 个主进程,一共 5 个 node 进程
18+
// 其中, 4 个子进程受理 http-server
19+
http.createServer((req, res) => {
20+
res.writeHead(200)
21+
res.end('hello world')
22+
}).listen(8000) // 注意,这里就不会有端口冲突的问题了!!!
23+
}

code/process/demo2.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
if (cluster.isMaster) {
2+
const num = os.cpus().length
3+
console.log('Master cluster setting up ' + num + ' workers...')
4+
for (let i = 0; i < num; i++) {
5+
// 按照 CPU 核数,创建 N 个子进程
6+
cluster.fork()
7+
}
8+
cluster.on('online', worker => {
9+
// 监听 workder 进程上线(启动)
10+
console.log('worker ' + worker.process.pid + ' is online')
11+
})
12+
cluster.on('exit', (worker, code, signal) => {
13+
// 兼容 workder 进程退出
14+
console.log('worker ' + worker.process.pid + ' exited with code: ' + code + ' and signal: ' + signal)
15+
// 退出一个,即可立即重启一个
16+
console.log('starting a new workder')
17+
cluster.fork()
18+
})
19+
}

docs/06-进程.md

+147
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,151 @@
11
# 进程
22

3+
JS 是单线程执行的,但是我们可以启动多个进程来执行,nodejs 中子进程管理以及进程守候是非常重要的知识点。
34

5+
-----
46

7+
## 目录
8+
9+
- 线程 vs 进程
10+
- 为何要启用多进程
11+
- child_process
12+
- cluster
13+
14+
----
15+
16+
## 线程 vs 进程
17+
18+
**进程(Process)** 是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。 **线程(Thread)** 是进程的一个实体,是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己不拥有系统资源,它与同属一个进程的其他的线程共享进程所拥有的全部资源。
19+
20+
在 mac os 或者 linux 系统中运行`top`命令,可以看到如下列表,这些就是进程。windows 系统中的任务管理器,可以看到各个启动的软件的列表,也是进程。通过这些列表,我们都能看到每个进程 CPU 使用率,内存占用,符合上文对于进程的描述(独立分配、调度资源)
21+
22+
```
23+
PID COMMAND %CPU TIME #TH #WQ #PORT MEM PURG CMPRS PGRP PPID STATE BOOSTS
24+
12080 top 9.1 00:00.62 1/1 0 23 3672K 0B 0B 12080 12059 running *0[1]
25+
12059 zsh 0.0 00:00.16 1 0 19 3368K 0B 0B 12059 12058 sleeping *0[1]
26+
12058 login 0.0 00:00.04 2 1 30 1904K 0B 0B 12058 12057 sleeping *0[9]
27+
12057 iTerm2 0.0 00:00.03 2 1 30 2364K 0B 0B 12057 945 sleeping *0[1]
28+
12055 lsof 0.0 00:00.00 1 0 8 232K 0B 0B 12054 12054 sleeping *0[1]
29+
12054 lsof 0.0 00:00.84 1 0 19 6004K 0B 0B 12054 70 sleeping *0[1]
30+
12040 QuickLookSat 0.0 00:00.40 5 1 92 10M 232K 0B 12040 1 sleeping 0[12]
31+
12012 quicklookd 0.0 00:00.61 4 1 90 4716K 216K 0B 12012 1 sleeping 0[14]
32+
```
33+
34+
线程是进程中更小的单位,我们无法通过工具直观的看到。一个进程至少启动一个线程,或者启动若干个线程(多线程)。JS 是单线程运行的,我们无法通过 JS 代码新启动一个线程(java 就可以),但是可以新启动一个进程。
35+
36+
注意,新启动一个进程是比较耗费资源的,不应频繁启动。如果遇到需要频繁启动新进程的需求,应该考虑其他的解决方案(我曾经就遇到过,差点入坑)。
37+
38+
----
39+
40+
## 为何要启用多进程
41+
42+
第一,现在的服务器都是多核 CPU ,**启动多进程可以有效提高 CPU 利用率**,否则 CPU 资源就白白浪费了。一般会根据 CPU 的核数,启动数量相同的进程数。
43+
44+
> PS:和开发客户端程序不同,开发 server 端程序时,要时刻注意“节省”和“压榨”,通俗一点就是“抠门”。“节省”就是尽量减少计算次数(时间复杂度)、内存使用(空间复杂度);“压榨”就是尽量多的合理利用起现有的资源,CPU、内存和硬盘等。有时你在开发和测试时候,对于“节省”和“压榨”看不出效果,但是一旦上线访问量增大,效果将会越来越明显。
45+
46+
第二,受到 v8 引擎的垃圾回收算法的限制,**nodejs 能使用的系统内存是受限制的**(64 位最多使用 1.4GB ,32 位最多使用 0.7GB)。**如何突破这种限制呢?—— 多进程**。因为每个进程都是一个新的 v8 实例,都有权利重新分配、调度资源。
47+
48+
----
49+
50+
## child_process
51+
52+
[child_process](http://nodejs.cn/api/child_process.html) 提供了创建子进程的方法
53+
54+
- `spawn`
55+
- `exec`
56+
- `execFile`
57+
- `fork`
58+
59+
```js
60+
var cp = require('child_process')
61+
cp.spawn('node', ['worker.js'])
62+
cp.exec('node worker.js', function (err, stdout, stderr) {
63+
// todo
64+
})
65+
cp.execFile('worker.js', function (err, stdout, stderr) {
66+
// todo
67+
})
68+
cp.fork('./worker.js')
69+
```
70+
71+
进程之间的通讯,代码如下。跟前端`WebWorker`类似,使用`on`监听(此前讲过的自定义事件),使用`send`发送。
72+
73+
```js
74+
// parent.js
75+
var cp = require('child_process')
76+
var n = cp.for('./sub.js')
77+
n.on('message', function (m) {
78+
console.log('PARENT got message: ' + m)
79+
})
80+
n.send({hello: 'workd'})
81+
82+
// sub.js
83+
process.on('message', function (m) {
84+
console.log('CHILD got message: ' + m)
85+
})
86+
process.send({foo: 'bar'})
87+
```
88+
89+
---
90+
91+
## cluster
92+
93+
cluster 模块允许设立一个主进程和若干个 worker 进程,由主进程监控和协调 worker 进程的运行。worker 之间采用进程间通信交换消息,**cluster模块内置一个负载均衡器,采用 Round-robin 算法协调各个 worker 进程之间的负载**。运行时,所有新建立的链接都由主进程完成,然后主进程再把 TCP 连接分配给指定的 worker 进程。
94+
95+
```js
96+
const cluster = require('cluster')
97+
const os = require('os')
98+
const http = require('http')
99+
100+
if (cluster.isMaster) {
101+
console.log('是主进程')
102+
const cpus = os.cpus() // cpu 信息
103+
const cpusLength = cpus.length // cpu 核数
104+
for (let i = 0; i < cpusLength; i++) {
105+
// fork() 方法用于新建一个 worker 进程,上下文都复制主进程。只有主进程才能调用这个方法
106+
// 该方法返回一个 worker 对象。
107+
cluster.fork()
108+
}
109+
} else {
110+
console.log('不是主进程')
111+
// 运行该 demo 之后,可以运行 top 命令看下 node 的进程数量
112+
// 如果电脑是 4 核 CPU ,会生成 4 个子进程,另外还有 1 个主进程,一共 5 个 node 进程
113+
// 其中, 4 个子进程受理 http-server
114+
http.createServer((req, res) => {
115+
res.writeHead(200)
116+
res.end('hello world')
117+
}).listen(8000) // 注意,这里就不会有端口冲突的问题了!!!
118+
}
119+
```
120+
121+
维护进程健壮性,**通过 Cluster 能监听到进程退出,然后自动重启,即自动容错**,这就是进程守候。
122+
123+
```js
124+
if (cluster.isMaster) {
125+
const num = os.cpus().length
126+
console.log('Master cluster setting up ' + num + ' workers...')
127+
for (let i = 0; i < num; i++) {
128+
// 按照 CPU 核数,创建 N 个子进程
129+
cluster.fork()
130+
}
131+
cluster.on('online', worker => {
132+
// 监听 workder 进程上线(启动)
133+
console.log('worker ' + worker.process.pid + ' is online')
134+
})
135+
cluster.on('exit', (worker, code, signal) => {
136+
// 兼容 workder 进程退出
137+
console.log('worker ' + worker.process.pid + ' exited with code: ' + code + ' and signal: ' + signal)
138+
// 退出一个,即可立即重启一个
139+
console.log('starting a new workder')
140+
cluster.fork()
141+
})
142+
}
143+
```
144+
145+
示例看似简单,但是实际应用还是尽量使用成熟的工具,例如 [pm2](https://www.npmjs.com/package/pm2),可以自己去看文档使用。
146+
147+
---
148+
149+
## 总结
150+
151+
总要明白线程和进程的区别、联系,以及为何使用多进程,后面的 API 用法相对比较简单。

docs/07-其他.md

+13-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,13 @@
1-
# 其他
1+
# 其他
2+
3+
## 目录
4+
5+
- 关于数据存储(如 mysql redis 等)
6+
7+
-----
8+
9+
## 关于数据存储(如 mysql redis 等)
10+
11+
npm 中也早就有了 nodejs 操作 mysql redis 等的库,可以直接拿来使用。不过考虑目前 nodejs 主要的应用场景,直接操作 mysql redis 的机会不多。
12+
13+
nodejs 目前属于 web server 的“表层”,即最接近前端的那部分,前后端代码同构就是这部分。nodejs 从一些线程的 server 中得到数据,然后直接渲染出 html 直接返回。至于如何从 mysql redis 中获取数据,一般不会由 nodejs 操作,而是其他 server 操作。现在比较经典的结构是`前端 -> nodejs -> 数据 server(如 PHP java)`

0 commit comments

Comments
 (0)