探索了一下nodejs的cluster化工具PM2

PM2全称是Process Manager2。谢谢你,真方便。

nodejs程序想要多个process共享一个socket端口侦听,似乎只能用cluster库了,这个库不知道什么时候状态变成Stable了,可以放心用了。

他地原理主要是在SocketServer.listen的内部动作做了手脚,不是直接listen,而是让一个控制进程侦听,把得到的新的connection的fd传过来用,这样就实现了connection级别的均衡分配。

至于http request内容级别的均衡分配,那还是得要nginx之类的东西,nodejs做到这个地步已经是很给力了。

想要把一个程序改成cluster化的,标准的做法,就是原本的代码上下加上一个套子。头部加这个:

const cluster = require('cluster');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
  for (var i = 0; i < numCPUs; i++) {
      cluster.fork();
  }
} else {
尾部加这个:
}

实际上不止这么简单,一旦分成住副本,那如何管理就需要花些精力了,需要全体退出,自动重启副本,... ,监测内部信息,log的管理... ...。而PM2就是做这个用的,他甚至可以跨机器发布。

话说,理论上,Nodejs真是可以做一个公用的服务程序,专门为了侦听socket的连接做分配,所有其它的nodejs程序里的socket侦听都内部从这个服务里取一个而已。

而最终我发现PM2就含有这个意思,当然他本身还包含了很多其它功能,例如forever的一些功能,还有热更新模块。


虽然用个hello world例子也行,但是我还是找了个express做的http server的例子:

bin/www    (这其实是个nodejs脚本,这个例子里是从这个开始启动的)
app.js
...

普通启动,那就是执行bin/www 或者node bin/www。

如果换成pm2启动,那就可以指定cluster化的副本个数,例如

$ pm2 start --name myweb --instances 4 --node-args=--debug bin/www  #4换成0可以表示CPU总数
[PM2] Starting bin/www in cluster_mode (4 instances)
[PM2] Done.
┌──────────┬────┬─────────┬───────┬────────┬─────────┬────────┬─────────────┬──────────┐
│ App name │ id │ mode    │ pid   │ status │ restart │ uptime │ memory      │ watching │
├──────────┼────┼─────────┼───────┼────────┼─────────┼────────┼─────────────┼──────────┤
│ myweb    │ 0  │ cluster │ 53061 │ online │ 0       │ 0s     │ 40.484 MB   │ disabled │
│ myweb    │ 1  │ cluster │ 53062 │ online │ 0       │ 0s     │ 40.813 MB   │ disabled │
│ myweb    │ 2  │ cluster │ 53063 │ online │ 0       │ 0s     │ 33.941 MB   │ disabled │
│ myweb    │ 3  │ cluster │ 53064 │ online │ 0       │ 0s     │ 24.742 MB   │ disabled │
└──────────┴────┴─────────┴───────┴────────┴─────────┴────────┴─────────────┴──────────┘
 Use `pm2 show ` to get more details about an app

看起来不错的样子。

想要停止就用

pm2 stop myweb

这些不是重点,重点在于,我看了系统的进程信息里显示,这几个process的命令行参数都是

$ ps -e | grep -E '53061|53062|53063|53064' 
53061 ??         0:00.88 node /Users/q/tmp/nodejs_express_test/bin/www  
53062 ??         0:00.88 node /Users/q/tmp/nodejs_express_test/bin/www  
53063 ??         0:00.94 node /Users/q/tmp/nodejs_express_test/bin/www  
53064 ??         0:00.93 node /Users/q/tmp/nodejs_express_test/bin/www  
53448 ttys001    0:00.00 grep -E 53061|53062|53063|53064

这就奇怪了,他如何控制这个process里我的socket端口侦听的动作呢?如何建立和控制进程的通信通道呢,应该是通过环境变量罗?

NODE_CHANNEL_FD   #副本进程和控制进程之间通信通道fd
NODE_UNIQUE_ID    #副本进程的序号

奇怪了,没找到任何环境变量,别的process都能看到,就这几个没有!

$ ps -AE | grep -E '53061|53062|53063|53064' 
53061 ??         0:01.53 node /Users/q/tmp/nodejs_express_test/bin/www  
53062 ??         0:01.53 node /Users/q/tmp/nodejs_express_test/bin/www  
53063 ??         0:01.59 node /Users/q/tmp/nodejs_express_test/bin/www  
53064 ??         0:01.58 node /Users/q/tmp/nodejs_express_test/bin/www  
53732 ttys001    0:00.00 grep -E 53061|53062|53063|53064 TERM_PROGRAM=Apple_Terminal SHELL=/bin/bash ... ...

头一次碰到这种事儿,我不信邪,于是去调试了这些副本进程,正好练习体会一下node的原始debug功能。

$ pm2 delete myweb   #因为接下来要改变pm2启动的参数,所以先得删除以前记下的配置,不然没有效果。
... ...
$ pm2 start --name myweb --instances 4 --node-args=--debug bin/www
... ...
│ myweb    │ 0  │ cluster │ 53061 │ online │ 0       │ 0s     │ 40.484 MB   │ disabled │
│ myweb    │ 1  │ cluster │ 53062 │ online │ 0       │ 0s     │ 40.813 MB   │ disabled │
│ myweb    │ 2  │ cluster │ 53063 │ online │ 0       │ 0s     │ 33.941 MB   │ disabled │
│ myweb    │ 3  │ cluster │ 53064 │ online │ 0       │ 0s     │ 24.742 MB   │ disabled │
... ...
$ lsof -P -p 53061,53062,53063,53064 | grep LISTEN    #各个副本的调试服务端口是不一样的,所以得查一下
node    53061    q   17u    IPv6 0xcf294233a1ce9d2d       0t0      TCP *:5956 (LISTEN)
node    53062    q   17u    IPv6 0xcf294233a1ceb12d       0t0      TCP *:5957 (LISTEN)
node    53063    q   17u    IPv6 0xcf2942339024882d       0t0      TCP *:5958 (LISTEN)
node    53064    q   17u    IPv6 0xcf294233a1ceac2d       0t0      TCP *:5959 (LISTEN)

$ node debug localhost:5957  #随便连接其中一个端口调试
connecting to localhost:5957 ... ok
debug> pause     #必须得先pause以便让目标进程进入可观察状态。
break in timers.js:56
... ...
>56 function listOnTimeout() {
... ...
debug> repl
Press Ctrl + C to leave debug repl
> process.env
{ TERM_PROGRAM: undefined,  #各种系统环境变量
  ANDROID_HOME: undefined, #各种用户自定义环境变量
  SHELL: undefined,
  TERM: undefined,
  mac_sdk_root: undefined,
  CLICOLOR: undefined,
  gcc_darwin_version: undefined,
  TMPDIR: undefined,
  Apple_PubSub_Socket_Render: undefined,
  TERM_PROGRAM_VERSION: undefined,
  ANDROID_SDK_ROOT: undefined,
  OLDPWD: undefined,
  TERM_SESSION_ID: undefined,
  USER: undefined,
  CCACHE_DIR: undefined,
  ANDROID_NDK_ROOT: undefined,
  build_mac_version: undefined,
  SSH_AUTH_SOCK: undefined,
  __CF_USER_TEXT_ENCODING: undefined,
  LSCOLORS: undefined,
  PATH: undefined,
  PWD: undefined,
  HOMEBREW_GITHUB_API_TOKEN: undefined,
  LANG: undefined,
  PYTHONSTARTUP: undefined,
  NODE_PATH: undefined,
  XPC_FLAGS: undefined,
  XPC_SERVICE_NAME: undefined,
  mac_sdk_version: undefined,
  SHLVL: undefined,
  GREP_OPTIONS: undefined,
  LOGNAME: undefined,
  USE_CCACHE: undefined,
  DISPLAY: undefined,
  NDK_ROOT: undefined,
  SECURITYSESSIONID: undefined,
  _: undefined,
  HOME: undefined,
  SILENT: undefined,
  node_args: undefined,  #这些都是PM2嵌入的环境变量
  name: undefined,
  instances: undefined,
  vizion: undefined,
  pmx: undefined,
  automation: undefined,
  autorestart: undefined,
  treekill: undefined,
  exec_mode: undefined,
  pm_exec_path: undefined,
  env: undefined,
  pm_cwd: undefined,
  exec_interpreter: undefined,
  pm_out_log_path: undefined,
  pm_err_log_path: undefined,
  pm_pid_path: undefined,
  NODE_APP_INSTANCE: undefined,
  vizion_running: undefined,
  status: undefined,
  pm_uptime: undefined,
  axm_actions: undefined,
  axm_monitor: undefined,
  axm_options: undefined,
  axm_dynamic: undefined,
  created_at: undefined,
  pm_id: undefined,
  restart_time: undefined,
  unstable_restarts: undefined,
  started_inside: undefined,
  command: undefined,
  _pm2_version: undefined }

这还了得,都是undefined, 真的不会出问题吗。我又查看了运行中的模块,发现了可疑之处了。

#先Ctrl+C退出repl模式。
debug> scripts
  66: ProcessContainer.js         #这绝对不是我的文件
  68: index.js
  69: index.js
  70: index.js
  71: index.js
  72: index.js
  75: Nodejs
  77: debug.js
  78: index.js
  79: sample-conf.js
  80: conf.js
  81: Utility.js
  82: safeclonedeep.js
  83: async.js
  84: semver.js
  86: index.js
  87: events.js
  88: transport.js
  89: stringify.js
  90: common.js
  91: actions.js
  93: notify.js
  94: configuration.js
  95: autocast.js
  96: transaction.js
  97: proxy.js
  98: simple_http.js
  99: Probe.js
  100: Counter.js
  101: Histogram.js
  102: EDS.js
  103: BinaryHeap.js
  105: Meter.js
  106: EWMA.js
  107: alert.js
  108: sample.js
  109: network.js
  110: monitor.js
  111: profiling.js
  113: pacemaker.js
  117: www
  118: app.js
  120: express.js
  121: index.js
  122: application.js
  123: index.js
  124: Nodejs
  125: debug.js
  126: index.js
  127: index.js
  139: index.js
  140: index.js
  141: index.js
  142: index.js
  143: route.js
  144: array-flatten.js
  145: layer.js
  146: index.js
  148: index.js
  149: index.js
  150: index.js
  151: index.js
  152: init.js
  153: query.js
  155: stringify.js
  156: utils.js
  157: parse.js
  158: view.js
  159: utils.js
  160: index.js
  161: index.js
  162: index.js
  163: index.js
  164: index.js
  166: index.js
  167: index.js
  168: index.js
  169: index.js
  170: mime.js
  171: index.js
  172: index.js
  181: index.js
  182: index.js
  183: ipaddr.js
  187: request.js
  188: index.js
  189: index.js
  190: charset.js
  191: encoding.js
  192: language.js
  193: mediaType.js
  194: index.js
  196: index.js
  197: index.js
  202: response.js
  203: index.js
  204: index.js
  205: index.js
  207: index.js
  208: index.js
  209: index.js
  210: index.js
  211: index.js
  212: index.js
  213: index.js
  214: index.js
  215: index.js
  216: parse.js
  217: index.js
  219: index.js
  220: index.js
  222: crc1.js
  223: define_crc.js
  224: crc8.js
  225: crc8_1wire.js
  226: crc16.js
  227: crc16_ccitt.js
  228: crc16_modbus.js
  229: crc16_xmodem.js
  230: crc16_kermit.js
  231: crc24.js
  232: crc32.js
  233: index.js
  234: index.js
  235: index.js
  236: index.js
  237: session.js
  238: memory.js
  239: store.js
  240: cookie.js
debug> cont   #退出前得让目标继续运行。
debug> quit

至于环境变量消失了的问题,我发现仅仅是debugger现实的问题,他不会自动把子项目的内容显示出来,用JSON.stringify(proess.env)就可以显示出来。但是太长了就变成...了。

所以最终我还是换成node-inspector来从chrome的developer tool里调试观察了。

$ node-inspector --web-host 127.0.0.1 --web-port 8081 --debug-port 5958
Node Inspector v0.12.5
Visit http://127.0.0.1:8081/?ws=127.0.0.1:8081&port=5958 to start debugging.

然后就是打开Chrome,输入上面的URL, 进入Developer tools, 可以看到代码了。完美。

然后按[中断]按钮就可以观察变量了,到旁边的Console里输入要观察的东西。

转了一圈最终确定是 PM2的ProcessContainer.js 里干了这些事儿。

// Rename process
process.title = 'node ' + pm2_env.pm_exec_path;

这句代码太迷惑人了。

转载于:https://my.oschina.net/osexp2003/blog/614485

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值