iT邦幫忙

2

[closed] Nginx 反向代理 + NodeJS + PM2 保存 Session 的好方法

想做一套架構能承載盡可能多的長連線(WebSocket)
Nginx 透過 upstream 來分配 request
NodeJS 透過 PM2 來做 叢集,以利用所有CPU核心
畫成圖大概是這樣:
https://ithelp.ithome.com.tw/upload/images/20180903/20109426kE1dClRCqx.png

upstreamip_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)呢
我有查到利用RedisSession的方法,是能確保使用者不需要重新登入了,但好像跟我想要做的事情不太一樣,有點不得要領。

(抱歉了,因為我對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'
});

差不多就是這樣吧。
不過這應該是效能較差的做法,實際上應該結合Redissubpub來做會更有彈性,要跨伺服器主機的話用IPC顯然是不夠的。
這又是另一個課題了,這題就先到此打住吧~

看更多先前的討論...收起先前的討論...
froce iT邦高手 1 級 ‧ 2018-09-04 12:01:11 檢舉
弄一台SSO server不要跟PM2混在一起?
wingkawa iT邦新手 4 級 ‧ 2018-09-04 17:17:15 檢舉
謝謝你的提議。
今天有一些進展,所以我補充、更新了問題。
froce iT邦高手 1 級 ‧ 2018-09-05 06:59:48 檢舉
http://docs.jinkan.org/docs/celery/getting-started/first-steps-with-celery.html
感覺這個對你會有幫助
wingkawa iT邦新手 4 級 ‧ 2018-09-07 10:50:37 檢舉
有參考了一下,您提供的celery可能在未來會有幫助,謝謝。
我又研究了一些東西並已更新到內文中,這個問題就暫時告一個段落了,感謝~

尚未有邦友回答

立即登入回答