[Node.js] 06 - Multi-thread and process module

课前阅读:关于Node.js后端架构的一点后知后觉javascript

书推荐:<Node.js design patterns>html

衍生问题: 微服务的必要性,Flux架构java

 

 

容错性和拓展性


1、立体拓展

假设如今须要你用NodeJS搭建一个http服务,我猜想你会借助express框架用不到10行的代码完成这项工做。node

容错性拓展性才是正常运行的基本保障,至少保证了你的服务是可用的,永远是可用的。
算法

    • X轴方向:纯粹的对服务实例进行拓展,例如为了响应更多的请求  
    • y轴方向:为服务添加新的功能,功能性拓展
    • z轴方向:按照业务数据对服务进行拓展(这里没搞懂,不知道这么说是否准确)

 

增长服务实例也分为两类,横向拓展(horizontal scaling)和纵向拓展(vertical scaling),数据库

    • 横向,表示利用更多的机器,      // ----> (B)
    • 纵向,表示在同一台机器上挖掘它的潜力。// ----> (A)

 

 

2、服务实例拓展

  • (A) 纵向拓展

单进程express

NodeJS程序是以单进程形式运行,32位机器上最多也只有1GB内存的实用权限(在64位机器上最大的内存权限扩大到1.7GB)。编程

而目前绝大部分线上服务器的CPU都是多核而且至少16GB起,如此以来Node程序便没法充分发挥机器的潜力。json

多进程c#

同时NodeJS本身也意识到了这一点,因此它容许程序建立多个子进程用于运行多个实例。

具体技术细节涉及到Cluster模块,详情能够查看NodeJS相关文档: https://nodejs.org/api/cluster.html

    • 主进程master

master主进程并不实际的处理业务逻辑,但除了业务逻辑之外事情它都作:它是manager,

负责启动子进程,

管理子进程(若是子进程挂了要及时重启),

它也扮演router,也就是对该程序的访问请求首先到达主进程,再由主进程分配请求给子进程worker。

    • 子进程

子进程才负责处理业务逻辑。

 

在这个机制下有两条细节须要咱们定夺如何处理。

    • 负载均衡的难点

如何把外界的请求平均的分配给不一样的worker处理?这里的平均不是指数量上的平均(由于单条请求处理的工做量可能不一样),而是既不能让某个子进程太闲,也不能让某个子进程太忙,保证它们始终处于工做的状态便可。这也是咱们常说的负载均衡(load-balancing)

默认状况下Cluster模块采用的是 round robin 负载均衡算法,说白了就是依次按顺序把请求派给列表上的子进程,派到结尾以后又重头开始。

这个算法只能保证每一个子进程收到的请求个数是平均的,和随机算法相似。但若是某个子进程遇到问题,处理变得迟缓了,然后续的请求又源源不断的分配过来,那么这个子进程的压力就大了,这就略显不公了。除此以外咱们还要考虑到超时,重作等机制的创建。因此主进程master做为路由时不只仅是转发请求,还要能智能的分配请求。

    • 状态共享的难点

另外一个问题是状态共享问题,假如某个用户第一次访问该服务时是分配给了线程A上的实例A处理,而且用户在这个实例上进行了登录,而没有过几秒钟以后当用户第二次访问时分配给了线程B上的实例B处理,若是此时用户在A上的登录状态没有共享给其余实例的话,那么用户不得不从新登录一次,这样的用户体验是没法接受的。

 

  • (B) 横向拓展

主进程-子进程的模式思路不只适用于纵向拓展,还适用于横向拓展。

当单台机器已经没法知足你需求的时候,你能够把单实例子进程的概念拓展为单台机器:咱们将在多台机器上部署多个进行实例,用户的访问请求也并不是直接到达它们,而是先到达前方的代理机器,它也是负责负载均衡的机器,负责将请求转发给部署了应用实例的机器。

这样的模式咱们也一般称为反向代理模式

 

 

3、功能拓展

既然咱们没法保证功能不会出错,那咱们有没有办法保证当一个功能出错以后不会影响整个程序的正常运行?这也是咱们所说的容错性

道理都懂,咱们都明白程序须要容错,因此try/catch是从编码上解决这个问题。

咱们容许程序出错,可是要及时把错误隔离,而且再也不影响程序的运行。这个就要从架构上解决这个问题。例如使用微服务(Microservices)架构

 

  • 为何要用微服务
    • 单体(monolithic)架构介绍

在介绍微服务架构以前,咱们要了解其它架构为何无法知足咱们的要求。例如咱们经常使用的单体(monolithic)架构。单体架构这个词你可能不熟悉,但几乎咱们天天都在和它打交道,大部分的后端服务都归属于单体架构,对它的解释我翻译Martin Fowler的描述:

企业级应用一般分为三个部分:

  1. 用户界面(包含运行在用户浏览器上的html页面和javascript脚本),
  2. 数据库(一般是包含许多表的关系数据库),
  3. 服务端应用。

服务端应用将会处理http请求,执行业务逻辑,从数据库中取得数据,生成html视图返回给浏览器。

这样的服务端应用就被称为单体(monolith)——单个具备逻辑性的执行过程。任何针对系统的修改都会致使从新构建和部署一个新版本的服务端应用。

(注:以上这段描述摘自Martin Fowler的文章Microservices,我认为这是对微架构描述最全面的文章,若是想对这一小节作更深刻的了解能够把这篇文章细读。 这也是我读到的Martin Fowler所写的文章中最通俗的文章。我的认为Martin Fowler的文章读起来比较晦涩,John Resig紧随其后)

    • 单体(monolithic)架构de问题

单体架构是一种很天然的搭建应用的方式,它符合咱们对业务处理流程的认知。但单体应用也存在问题:

任何一处,不管大小的修改都会致使整个应用被从新构建和从新部署。随着应用规模和复杂性的不断增大,参与维护的人数增多,每一轮迭代修改的模块增多,对上线来讲是极大的考验,对于内部单个模块的拓展也是极为不利的。例如当图片压缩请求剧增时,须要新增图片压缩模块的实例,但实际上不得不扩展整个单体应用的实例。

    • 微服务是方案

微服务架构解决的就是这一系列问题。顾名思义,微服务架构下软件是由多个独立的服务组成。这些服务相互独立互不干预。以拆分上面所说的单体应用为例,咱们能够把处理HTTP请求的模块和负责数据库读写的模块分离出来成为独立的服务,这两个模块从功能上看是没有任何交集。这样的好处就是,咱们能够独立的部署,拓展,修改这些服务。例如应用须要添加新的接口时,咱们只须要修改处理HTTP请求的服务,只公开这部分代码给修改者,只上线这部分服务,拓展时也只须要新添这部分服务的实例。

微服务和咱们一般编写的模块(以文件为单位,以命名空间为单位)相比更加独立,更像是一个五脏俱全的“小应用”,若是你读完了我以前推荐的Martin Fowler关于微服务的文章的话,你会对这点更深有感触:微服务除了在运维上独立之外,它还能够拥有独立的数据库,还应该配备独立的团队维护。它甚至能够容许使用其余的语言进行开发,只要对外接口正常便可。

    • 微服务de不足

固然微服务也存在不足,例如

    1. 如何将诸多的微服务在大型架构中组织起来,
    2. 如何提升不一样服务之间的通讯效率都是须要在实际工做中解决的问题。

 

微服务说到底仍是解耦思想的实践。从这个意义上来讲,React下的Flux架构某种意义上也属于微服务。

若是你了解Flux的起源的话,Flux架构 其实来源于后端的CQRS,即Command Query Responsibility Segregation,命令与查询职责分离,也就是将数据的读操做和写操做分离开。

这么设计的理由有不少,举例说一点:在许多业务场景中,数据的读和写的次数是不平衡,可能上千次的读操做才对应一次写操做,好比机票余票信息的查询和更新。因此把读和写操做分开可以有针对性的分别优化它们。例如提升程序的scalability,scalability意味着咱们可以在部署程序时,给读操做和写操做部署不一样数量的线上实例来知足实际的需求。

 

  • 类比unity设计模式 - 自治

若是你也有Unity编程经验的话会对解耦更有感触,在Unity中咱们已经不能称之为解耦,而是自治,这是Unity的设计模式

举个例子,屏幕上少则可能有十几个游戏元素,例如玩家、敌人还有子弹。你必须为它们编写“死亡”的规则,“诞生”的规则,交互的规则。由于你根本没法预料玩家在什么时候何种位置发射出子弹,也没法预料子弹什么时候在什么位置碰撞上什么状态敌人。

因此你只能让它们在规则下自由发挥。这和微服务有殊途同归之妙:独立,隔离,自治。

 

 

4、Node.js 与 Netty Server

  • 良心对比一

Jeff: netty可能有更好的封装,来解决例如“状态共享”这样的难题。

protobuf比Json更好一点:http://www.infoq.com/cn/articles/json-is-5-times-faster-than-protobuf

  • 良心对比二

From: https://my.oschina.net/lifeofpi/blog/120210

如下代码体现了nodejs的简洁性。

var http = require('http');
http.createServer(
function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }).listen(1337, "127.0.0.1");
console.log(
'Server running at http://127.0.0.1:1337/');

 

 

 

多进程/多线程实践


1、JS 角度的多线程

Goto: [JS] ECMAScript 6 - Async : compare with c#

  • Javascript异步编程的4种方法 - Ruan

(1) 回调函数

(2) 事件监听:func.trigger --> func.on

(3) 发布订阅:jQuery.publish --> jQuery.subscribe

(4) Promises对象

 

2、Promise 对象

有三种状态,也就支持了.then这样的链式写法。

Ref: 浅谈ES6的Promise对象【看上去讲得更好】

 

  • 使用 Bluebird 在生产环境

原生promise可能不稳定,为了防止不可预知的bug,在生产项目中最好仍是不要使用原生的或者本身编写的Promise(目前为止并非全部浏览器都能很好的兼容ES6),而是使用已经较为成熟的有大量小伙伴使用的第三方Promise库,下面就为小伙伴推荐一个—— Bluebird

 

  • catch 比 then() 的 rejected 回调好些
promise.then(
    () => { console.log('this is success callback') }
).catch(
    (err) => { console.log(err) }
)

 

/* 须要根据连接进一步学习 */

 

 

3、Generator 函数

状态机、遍历器 ----> yield & next 函数 

 


4、Async 函数

ES2017 标准引入了 async 函数,是个Generator 函数的语法糖。

 

 

5、node.js角度的多线程

Ref: Node.js的process模块

Ref: Node.js 多进程

Ref: nodejs 多核处理模块cluster【基于以上两篇的技术,居然有了负载均衡】

cluster是一个nodejs内置的模块,用于nodejs多核处理。cluster模块,能够帮助咱们简化多进程并行化程序的开发难度,轻松构建一个用于负载均衡的集群。

 

/* implement */

相关文章
相关标签/搜索