想做一套架構能承載盡可能多的長連線(WebSocket
)Nginx
透過 upstream
來分配 request
NodeJS
透過 PM2
來做 叢集
,以利用所有CPU核心
畫成圖大概是這樣:
upstream
有ip_hash
可以使用,但考量到使用者的對外ip極有可能會是相同的,用ip來分配請求給那4個process應該是不太妥當的做法。
目前Nginx中的相關設定:
worker_processes 4;
upstream io_nodes {
ip_hash;
server 127.0.0.1:3131;
server 127.0.0.1:3132;
server 127.0.0.1:3133;
server 127.0.0.1:3134;
}
location / {
# 禁用緩存
proxy_buffering off;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; # 加入 X-Real-IP Header
proxy_http_version 1.1;
# 反向代理的地址
proxy_pass http://io_nodes;
}
想請問的是,有什麼好方法可以確保每一個使用者連線(e.g. 連到了 Node1),在斷線重連後,可以連回原本的process(e.g. Node1)呢?
我有查到利用Redis
存Session
的方法,是能確保使用者不需要重新登入了,但好像跟我想要做的事情不太一樣,有點不得要領。
(抱歉了,因為我對Server架構很不熟,所以圖、甚至是我的問題都可能錯很大,還請多指正,感謝!)
2018/9/4 補充:
我補充一下,主要並不是要解決多個系統的登入問題,而是像聊天室那樣
process1 (cpu1): Room A 有 User1、User2、User3
process2 (cpu2): Room B 有 User4、User5
process3 (cpu3): Room C 有 User6
process4 (cpu4): Room D 有 User7
User1斷線重連後,必須要連回Room A這樣
今天 survey 到 nginx 的第三方套件nginx-sticky-module-ng,測試了一下可以利用cookie
來達到我的需求,在cookie
過期之前都能讓使用者分配到同一個process內。
使用nginx-sticky-module-ng
的相關配置設定如下:
upstream io_nodes {
sticky expires=8h;
server 127.0.0.1:3131;
server 127.0.0.1:3232;
server 127.0.0.1:3133;
server 127.0.0.1:3134;
}
看起來是解決了原本的問題,不過回過頭來想想,以這個聊天室的情境來看……
似乎User在哪個process並不是那麼重要,重要的是能透過IPC
讓process間可以互相溝通才是正確的路線?
2018/9/7 更新:
翻了翻PM2
新舊版的文件和範例,發現其實有可以在process間傳遞訊息的方法,總之它的API文件似乎不是非常完整。
可以寫一支.js
來啟動PM2
,控制核心數、名稱等等,像這樣:
// pm2-call.js
// 呼叫 pm2 api
var pm2 = require('pm2');
var appList = [];
pm2.connect(function(err) {
if (err) {
console.log('pm2-call.js', err);
process.exit(2);
}
// 啟動 pm2
pm2.start({
script: 'index.js', // Script to be run
exec_mode: 'cluster', // Allows your app to be clustered
instances: 4, // Optional: Scales your app by 4
watch: true // the application will be restarted on change of the script file
}, function(err, apps) {
if (err) {
throw err;
}
console.log('pm2-call.js: create process');
appList = apps;
});
});
pm2.launchBus(function(err, bus) {
// process 收到訊息觸發
bus.on('process:msg', function(packet) {
var pm_id = packet.process.pm_id;
var message = packet.data.message;
// 廣播給所有 process
appList.forEach( function(appProcess, index) {
pm2.sendDataToProcessId(appProcess.pm_id, {
type: 'process:msg',
data: {
message: message,
},
topic: 'broadcast message'
}, function(err, res) {
if (err) {
throw err;
}
});
});
});
});
process要傳訊息pm2
廣播的話就這樣用:
// index.js
// 檢查 process
// if (process['send']) {
// console.log('have');
// }
// process 收到 pm2 的廣播訊息
process.on('message', function(packet) {
// do something
});
// 由 process 傳遞訊息
process.send({
type: 'process:msg',
data: {
message: 'messages',
},
topic: 'broadcast received message'
});
差不多就是這樣吧。
不過這應該是效能較差的做法,實際上應該結合Redis
的sub
、pub
來做會更有彈性,要跨伺服器主機的話用IPC
顯然是不夠的。
這又是另一個課題了,這題就先到此打住吧~