Nodejs集群模块架构问题

时间:2021-04-16 12:32:30

标签: javascript node.js socket.io cluster-computing scaling

背景

我有一个带有轮盘赌风格游戏的网络应用程序。游戏是一个模块,通过使用socket io接受数据,并将数据和游戏状态存储在内存(js对象)中。 一旦 2 个唯一用户与游戏互动,就会触发一个事件并触发倒计时。 一旦倒计时到 0,websocket 事件就会发送到客户端(用于动画和 UI 更新的目的),将状态写入 MongoDB 数据库,然后为下一轮游戏清除状态。

问题

我希望使用集群模块来优化性能和可扩展性,但是我将如何在这个游戏中使用它,就好像我有多个应用程序实例在运行,我将有 N 个游戏以自己的状态运行,在不同时间向客户端发送 websocket 事件,并导致各种视觉和后端问题。

预期结果

理想情况下,我可以找到一种解决方案,让我可以运行应用程序的多个实例,以利用生产服务器上的所有核心,处理负载,并使游戏状态在实例之间保持单一和集中。

也不确定这将如何与 socket.io 一起使用。

Index.js

const app = express()
const port = process.env.port;

// .. Other bootstrapping stuff irrelevant to the problem

// import routes
require("./routes.js")(app);
const rouletteGame = require("./games/roulette")(app);

app.listen(port, () => {console.log("running :D")});

Handler.js

const Jackpot = require('../../models/Jackpot');
const User = require('../../models/User');
const generateJackpot = require('./tasks/generate-jackpot');
const removeIdleJackpots = require('./tasks/remove-idle-jackpots');
const {decodeToken, tokenContents} = require("./helpers/jwt_validate");
const middleware = require("../middlewareService");
const config = require("../../config");
const deposit = require("./tasks/deposit");
const moment = require("moment");

let liveJackpot;

const set_jackpot = async jackpot =>{
    liveJackpot = jackpot
}

// Refund any users who were involved in games that didn't finish 
// + Generate new game
const init = async () => {
    // find any active jackpots and set to inactive
    const jackpots = await Jackpot.find({ active: true });

    // loop through all jackpots and set them to inactive
    jackpots.forEach(async (jackpot) => {
        
        // refund all users who placed bets 
        let refundObject = {};
        for (deposit of jackpot.deposits) {
            refundObject[deposit.user] == undefined
                ? (refundObject[deposit.user] = deposit.amount)
                : (refundObject[deposit.user] += deposit.amount);
        }
        Object.keys(refundObject).forEach(async (key) => {
            let user = await User.findOne({ username: key });
            console.log(user.balance, "before");
            user.balance += refundObject[key];
            console.log(user.username, "refunded", user.balance)
            await user.save();
        });

        jackpot.active = false;
        await jackpot.save();
    });

    // remove idle
    await removeIdleJackpots();
    // generate a new jackpot
    liveJackpot = await generateJackpot();

};


const main = async app => {
    await init();
    const io = app.get("socketio");
    
    /**
     * Method emits event to client to update the UI
     * @param {string} type: type of updateView 
     * @param {object} data: data payload
     */
    const updateView = async (type, data={}) => {
        io.emit("updateView", {
            type, 
            data, 
        })
    }

    /**
     * Method fires when a user makes a deposit
     */
    async function deposit_event () {
        // check if jackpot has 2 deposits
        if (liveJackpot.deposits.length == 2) {
            // check if both deposits are from the same user
            if (liveJackpot.deposits[0].user == liveJackpot.deposits[1].user) {
                updateView("jackpot", liveJackpot);
                return;
            }

            // if both deposits are unique start timer and emit to client
            liveJackpot.startedAt = Date.now();
            updateView("jackpot", liveJackpot);
            start_jackpot();
        }

        /* 
            check if number of jackpot deposits is greater than 2. start jackpot if true 
            Reason for doing this, is that we do not want the jackpot start event to be fired each time
            the total number of bets to start is exceeded and new bets come in, triggering the event again.
            Hence the !liveJackpot.startedAt check
        */
        if (liveJackpot.deposits.length > 2 && !liveJackpot.startedAt) {
            liveJackpot.startedAt = Date.now();
            updateView("jackpot", liveJackpot);
            start_jackpot();
        }

        updateView("jackpot", liveJackpot);
    }

    /**
     * Method changes the jackpots state to closed 
     * and triggers the next steps.
     */
    async function closedPot () {
        liveJackpot.open = false;
        liveJackpot.closedAt = Date.now();
        
        liveJackpot.winningPercentage = await genWinner(liveJackpot);
        if (isNaN(liveJackpot.winningPercentage)) {
            return killPot();
        }


        setTimeout(function () {
            winner();
        }, 4000)
    }


    /**
     * Handle Socket Events
     */

    io.on("connection", socket => {
        try {
            // retrieve jackpot
            socket.on('jp-init', () => {
                socket.emit('updateView', {
                    type: 'jackpot',
                    data: liveJackpot,
                });
            });

            
            // handle payload event
            socket.on("payload", async payload => {

                console.log("received jackpot", moment().format("hh:mm:ss:SS"));
            

                if (await decodeToken(payload.data.token, config.secret) == false) {
                    return // TODO: Show error to client
                } 

                // ensure token isn't stolen (extra security)
                if (await middleware.verify_token_socket(socket, payload) !== true) {
                    return //TODO: Show error to client
                }

                // jackpot deposit event
                if (payload.type == "jackpot_deposit") {
                    const token_contents = await tokenContents(payload.data.token, config.secret);
                    
                    // * does username of payload match name in token
                    if (token_contents.data.username !== payload.data.deposit.user) {
                        return // TODO: Show error to client
                    }

                    await depositChecks(
                        token_contents.data.user_id,
                        payload, 
                        liveJackpot, 
                        socket 
                    );
                }
                
            })
        } 
        
        catch (error) {
            console.log(error);
        }
    })
}


module.exports = {
    main, 
    set_jackpot,
}

0 个答案:

没有答案
相关问题