Node.js的运行模式 让我们通过一小段代码来看Node.js的运行时 const express = require(‘express’) const app = express()
let times = 0
app.get(’/inc’, (req, res) => { times += 1 res.end(‘success’) })
app.get(’/currentValue’, (req, res) => { res.end(times) })
app.listen(3000)
复制代码如果在java等多线程模型的语言中,这是一个经典的用来解释锁的必要性的例子 我们使用一些压测工具或者自己写代码在极短的时间内请求/inc这个接口1000次 然后调用 /currentValue获取times的值 我们会发现,使用java语言写出来的相同代码times值并不等于1000(一般会小于1000),而使用我们这段Node.js写出来的值则刚好等于1000,这是为什么呢? Node和Java的运行时模型区别
Java服务器默认运行是单进程多线程模型 Node服务器默认运行是单进程单线程模型
体现到服务器收到网络请求的时候,java每次都会开一个新的线程来处理这个请求,不同线程是同步运行的,所以java服务器在运行上面这个例子时如果不加锁就会遇到经典的多线程同时修改某一个变量导致最终数据错误的问题。 Node在设计之初抛弃了多线程,除了libuv中有一个系统内部的线程池之外,我们 自己写的代码都在主线程中运行,所以也不会涉及到多线程竞争问题。 既然Node默认是单进程单线程,为什么还需要多进程部署?
原因很简单,如果不做多进程部署的话,那我们买的4核CPU服务器岂不是要被浪费掉3个核心?或者用户请求量上去之后,同样也需要分布式集群部署
多进程部署最简单的方式 通过pm2的cluster方式运行(文档)
pm2 start index.js -i max --name my-app
这样就直接开了和系统CPU核心数量一样的进程跑了起来,pm2会自动将请求均分到不同的进程里 多进程部署之后,会出现哪些问题? 最明显的一个问题是,现在进程空间不再共享,每个进程都有着自己的进程空间,回到我们文章最初的那个例子,假设我们现在在一台2核CPU上多进程运行了这个demo,然后访问 /inc 这个接口1000次,然后访问 /currentValue 会出现什么结果呢?
我们会发现返回结果会不断变化,存在2个不同的值,但他们的和加起来等于1000,很明显,是因为存在两个不同的times对象
那么不同的进程之间通过什么方式来共享数据呢?比如我在做微信开发的时候,会有一个微信的access_token,每次获取了新的token之后,老的token就会被微信自动作废,这个token是通过一个http接口获取,示例代码 class WeixinTokenService { private token = null
async getToken() {
if (!this.token) {
this.token = await someHttpResquest()
}
return this.token
}
} 复制代码这是一个很简单的例子,在单进程运行的时候基本上是正确的(但其实也有一个小概率bug,不知道有没有人能发现😊)但在多进程模型下,第一个进程获取了之后,第二个进程的token依然是空,那么他会重新发起一个请求导致第一个进程获取到的token作废。 那么如何解决这个问题呢?答案也很简单,有2种比较通用的方式
每次获取了token之后,将token存入Redis或者数据库中,大家一起共享 将token获取这块单独作为一个microservice,以单进程的方式部署(因为这块代码不同于业务代码,是没有高并发压力的)
通过Redis来解决这个问题的代码如下: class WeixinTokenService { private redisClient = initRedisClient()
async getToken() {
// line 1
let token = await this.redisClient.get('wx-token')
if (!token) {
// line 2
token = await someHttpResquest()
// line 3
await this.redisClient.set('wx-token', token)
}
return token
}
} 复制代码此时这个问题基本得到了解决,但新的问题出现了: 我们想象一个这样的场景,有2个用户请求同时到达,他们一起执行了line 1,都没有拿到token,然后一起执行了line 2,再同时执行了line 3,这时候有一个进程拿到的token是废弃的,而具体被存到redis中的token是哪一个,也是我们无语预料的。
思考:其实单进程模型下也会遇到同样的问题(还记得我说有一个小概率bug吗?)
但单进程下的解决方法相对简单,可以通过变量标记法(设置一个变量用来标注当前是否正在请求token,防止发出多个http请求) 而多进程下如何解决这个问题呢?我们下一篇文章再聊。 欢迎加入Node开发高级进阶群,群主有时间就会给大家解决一些Node实战中会遇到的各种问题
作者:曹酌中 链接:https://juejin.im/post/5f12c58b5188252e603af9a4 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
pm2会自动将请求均分到不同的进程里? pm2做了负载均衡的工作?
@muyoucun557 我out了,看了下文档确实是这样,好奇是怎么实现的,继续学习一下
跟 PM2 没啥关系,就是 Node Cluster 做的。