Node.js高级程序员晋升系列之-Node进程模型解析和服务器端多进程部署

Node.js的运行模式

让咱们经过一小段代码来看Node.js的运行时javascript

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等多线程模型的语言中,这是一个经典的用来解释锁的必要性的例子java

咱们使用一些压测工具或者本身写代码在极短的时间内请求/inc这个接口1000次redis

而后调用 /currentValue获取times的值数据库

咱们会发现,使用java语言写出来的相同代码times值并不等于1000(通常会小于1000),而使用咱们这段Node.js写出来的值则恰好等于1000,这是为何呢?express

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实战中会遇到的各类问题

相关文章
相关标签/搜索