9.3 集群稳定之路(《深入浅出nodejs》 笔记)

父子进程通过send()和on('message',callback)分别进行消息的发送和接收处理实现通信机制

自动重启

  1. 在主进程中通过监听子进程的exit事件来或者获知其推出的信息,在多进程架构中加入一些子进程管理的机制,比如重新启动一个进程来继续服务。
  2. 在极端情况下,所有的工作进程都停止接收新的连接,全处在等待退出的状态。但是等到进程完全退出才重启的过程中,所有的新来请求可能存在没有工作进程为新用户服务的情景,这会丢掉大部分请求。 此时需要为退出的流程中新增一个自杀信号(suicide).工作进程在得知要退出时,向主进程发送一个自杀信号,然后才停止接收新的连接,当所有连接断开后才退出。主进程在接收到自杀信号后,立即创建新的工作进程服务。
  3. 对于http服务的长连接,为已有的连接的断开设置一个超时时间是必要的,在限定时间里里强制退出的设置
  4. 对未能捕获的异常进行日志记录
  5. 为了消除无意义的重启,在满足一定规则的限制下,不应当反复重启。比如在单位时间内规定只能重启多少次,超过限制就出发giveup事件,告知放弃重启工作进程这个重要事件。为了完成限量重启的统计,使用队列做标记,并每次重启工作进程之间进行打点并判断重启是否太过频繁。
    giveup事件比uncaughtException更严重的异常,因为giveup事件表示集群中没有任何进程服务了,十分危险,此时应该添加重要日志,并让监控系统监视到这个严重错误,进而报警。
demo:
master.js(主进程,控制和管理子进程)
let fork = require('child_process').fork
let cpus = require('os').cpus()
let server = require('net').createServer()
server.listen(1337)
//限制重启次数
let limit = 10
//限制时间单位
let during = 60000
let restart = []
let isTooFrequently = function () {
  //记录重启时间
  let time = Date.now()
  let length = restart.push(time)
  if (length > limit) {
    // 取出最后10个记录
    restart = restart.slice(limit * -1)
  }
  // 最后一次重启到前10次重启之间的时间间隔
  return restart.length >= limit && restart[restart.length - 1] - restart[0] < during
}

let workers = {}
let createWorker = function () {
  // 检查是否重启的过于频繁
  if (isTooFrequently()) {
    //触发giveup事件,不再重启
    process.emit('giveup', restart.length, during)
    return
  }
  let worker = fork(__dirname + '/worker.js')
  // 处理自杀信号
  worker.on('message', function(message) {
    if (message.act === 'suicide') {
      createWorker()
    }
  })
  //退出时重新启动新的进程
  worker.on('exit', function () {
    console.log('Worker ' +worker.pid + ' exited.')
    delete workers[worker.pid]
    createWorker()
  })
  worker.send('server', server)
  workers[worker.pid] = worker
  console.log('Create worker.pid ' + worker.pid)
}
for(var i =0;i<cpus.length; i++) {
  createWorker()
}
process.on('exit', function() {
  for(let pid in workers) {
    workers[pid].kill()
  }
})
worker.js(各个工作进程)
var http = require('http')
let logger = require('./logger')
let server = http.createServer(function(req, res) {
  res.writeHead(200, {'Content-Type':'text/plain'})
  res.end('handled by child,pid is ' + 'process.pid' + '\n')
  throw new Error('throw exception ' + JSON.stringify(req))
})
let worker;
process.on('message', function(m, tcp) {
  if(m === 'server') {
    worker = tcp
    worker.on('connection', function(socket) {
      server.emit('connection',socket)
    })
  }
})
process.on('uncaughtException', function (err) {
  logger.error(err)
  process.send({act: 'suicide'}) //新增自杀信号
  // 停止接受新的连接
  worker.close(function () {
    // 所有已有连接断开后,推出进程
    process.exit(1)
  })
  // 处理长连接的退出进程机制
  setTimeout(function() {
    process.exit(1)
  }, 5000)
})

负载均衡

  1. NODE默认采取操作系统的抢占式策略(即综合考虑cpu、I/O繁忙度,决定是否进行处理服务)对于不同业务,可能只需要考虑cpu的繁忙度即可
  2. NODE v0.11提供Round-Robin 轮叫调度,其工作方式是由主进程接受连接,将其依次分发给工作进程。分发的策略是在N个工作进程中,每次选择第i = (i+1)mod n 个进程来发送连接。
    在cluster 模块中启用它的方式如下:
cluster.schedulingPolicy = cluster.SCHED_RR (启用Round-Robin)
cluster.schedulingPolicy = cluster.SCHED_NONE(不启用)

或者在环境 变量里设置NODE_CLUSTER_SCHED_POLICY

export NODE_CLUSTER_SCHED_POLICY = rr
export NODE_CLUSTER_SCHED_POLICY = none

状态共享

node不容许在多个进程之间共享数据,但是一些数据 譬如配置数据需要在多个进程之间是一致的。

1. 采用第三方数据存储

eg:redis,然后去轮讯存储的数据

2. 主动通知

创建一个通知进程,去轮询config配置,然后发送通知和查询状态去通知其他进程。推送机制可以按进程间信号传递,在跨多台服务器时会无效,故可以采用Tcp或者UDP的方案

Cluster模块

cluster模块就是child_process 和net模块的组合应用。但是cluster模块应用中,一个主进程只能管理一组工作进程。
暴露事件:

  1. fork:复制一个工作进程后触发该事件
  2. online: 复制好一个工作进程后,工作进程主动发送一条online消息给主进程,主进程收到消息后,触发该事件
  3. listening: 工作进程中调用listen() (共享了服务器Socket)后,发送一条listening消息到主进程,主进程收到消息后,触发该事件
  4. disconnect: 主进程和工作进程之间IPC通道断开后会触发该事件
  5. exit: 有工作进程退出时触发该事件
  6. setup: cluster.setupMaster() 执行后触发该事件
    此时的master.js 只需要写如下代码,此时,需要在worker.js中删除throw new error
let cluster = require('cluster')

cluster.setupMaster({
  exec: "worker.js"
})
let cpus = require('os').cpus()
for(let i =0;i<cpus.length;i++) {
  cluster.fork()
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,934评论 6 13
  • 第一章 Nginx简介 Nginx是什么 没有听过Nginx?那么一定听过它的“同行”Apache吧!Ngi...
    JokerW阅读 32,650评论 24 1,002
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,637评论 18 139
  • 1. Nginx的模块与工作原理 Nginx由内核和模块组成,其中,内核的设计非常微小和简洁,完成的工作也非常简单...
    rosekissyou阅读 10,206评论 5 124
  • 1 儿子说,今天学校发毕业证 一张小小的初中毕业证虽然说没有什么了不起的 可是,能够顺顺利利拿到这个毕业证也是充满...
    利君理疗阅读 670评论 0 0