@soulflystation

Node.js, socket.io, cluster. Как организовать архитектуру multiplayer-игры?

Добрый день!

Есть игровой сервер на node.js + socket.io. На сервере крутятся игровые комнаты. Каждая комната на 2х-4х игроков. Комната - это по сути экземпляр игрового движка с текущим состоянием игры + игроки, которые к этой игровой комнате подключены (с точки зрения кода, комната - это js-объект, а игроки различаются по socket id).

Клиенты взаимодействуют с сервером с использованием socket.io и выполняют только ввод игровых ходов, получают от сервера обновленное состояние игры.

На сервере осуществляется роутинг игровых событий - от какого игрока пришел ход, в какую игровую комнату этот ход следует отдать.

Сейчас встала задача улучшить приложение под более высокую нагрузку. Целей для оптимизации две - 1) большее число одновременных соединений и 2) параллельный обсчет игровых ходов. Рассматривается вариант по использованию модуля cluster. Хотелось бы вынести затратные операции в worker'ы. Но есть следующее затруднение. Массив игровых комнат с их текущим состоянием должен храниться централизованно. И эти данные (достаточно сложные js-объекты) нельзя поместить в БД. Вроде как напрашивается хранение этих данных на master'е. А из worker'ов требуется обеспечить к ним доступ. Притом требуется еще какой-то механизм блокировок, чтобы два worker'а не пытались одновременно с одним куском памяти работать.

Сталкивался ли кто-то с подобной проблемой? Как реализовали взаимодействие между worker'ами и master'ом?

Рассматриваются также любые другие варианты - может стоит вообще делать как-то иначе? Поделитесь опытом, наверняка уже сталкивались с подобными задачами.
  • Вопрос задан
  • 10170 просмотров
Решения вопроса 1
@kazmiruk
Вообще Вы сами ответили на свой вопрос: используем кластер, мастер и несколько воркеров (обычно по количеству ядер). Данные, которые необходимы всем храним или на мастере, или в носкл, как указали выше. Воркеры спрашивают разрешение на обновление данных и если текущая запись сейчас заблокирована кем-то, то ожидают. Если не заблокирована, то блокируют и обновляют, а затем освобождают. По крайней мере сам делал именно так.
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 4
fornit1917
@fornit1917
И эти данные (достаточно сложные js-объекты) нельзя поместить в БД

noSQL же. MongoDB например так и хранит все в виде JS-объектов.
Можете попробовать так же in-memory решение, например Redis.
БД в вашем случае потом при необходимости тоже можно будет расшардить для улучшения производительности.
Ответ написан
Комментировать
@ISINK
1.Раскидывали через cluster http сервер (свой long-polling, socket.io не использовали) на несколько потоков по количеству ядер в системе . Также была необходимость хранить данные в головном потоке и там их обрабатывать при необходимости. Cluster предоставляет возможность осуществлять связь между дочерними потоками и головным, делается эта следующим образом:
В головном потоке (master) устанавливаем событие message - которое ждет сообщения от дочерних потоков
всю логику разбиваем на 2 файла server.js (Master) и worker.js

var config = {
    numWorkers: require('os').cpus().length,
    refreshTime: 1000, // Milliseconds between data refreshes.
    waitTime: 90, //Время в секундах когда пользователь считается оффлайн
    worker: {
        port: XXXX,
        setNoDelay: true,
        mongoUrl: 'mongodb://XXX:XXXXXXXX@/tmp/mongodb-270XX.sock'
    }
};

тут храним натсройки, так удобней чтобы потом не рыться в коде елси нужно поправить

cluster.setupMaster({
    exec: "worker.js"      
});

говорим кластеру в каких файлах исполняемый код воркеров

for (var i = 0; i < config.numWorkers; i++)
    (function(worker) {
        worker.on('message', function(data) {
			switch(data.routeType){   // мы  в сообщениях преедавали  массив , routeType  -  переменная масисива которую мы ввели и в нашем случае она  обозначала какоето действие
                case 'act1':
                    //  Что то делаем, выполняем какойто код   массив  data  может  содержать еще какието элементы котоыре вы будете обрабатывать
                    break;
                case 'act1':
						// Второе действие
                    break;
				default:
					break;
			}
        });

        worker.on('exit', function(code, signal) {
            console.log('Worker died: ', worker.process.pid, code, signal);  //  Сообщаем в консоль что у нас подох один из потоков
        });
    })(cluster.fork({WORKER_CONFIG: JSON.stringify(config.worker)}));  //  Форкаем  потоки




worker.js -- код в потоках
var config = JSON.parse(process.env.WORKER_CONFIG || "{}"); -- таким образом можно получить в дочернем потоке какието насторйки из голвного

process.on('message', function(data){   // получаем сообщение от  головного потока 
		switch(data.routeType){
			case 'myMess1': 
				// Выполняем свой код
            break;
            case 'myMess2':
						// Второе действие
            break;
			default:
			break;
			}
}

отправляем сообщение в головной
var procMess = {
	routeType: 'myMess',
//..........  любые ваши данные которые нужно передать
};

process.send(procMess);	    // Отправка данных  в головной поток

Советую собрать у себя этот пример habrahabr.ru/post/123154 чтобы наглядно посмотреть как передаются сообщения из головного потока в воркеры и обратно.
Мы первую версию своего движка собрали по похожем принципу и тестировали под высокими нагрузками - проблем не было.
Ответ написан
@ISINK
2.После нового года решили опробовать socket.io -- понравилось то что он сразу в себе реализовывает и вебсокеты и long-polling, в один поток собрали решение без проблем.
Началось интересное когда пытались распараллеливать
пока решение не нашли, натыкались на людей с такими же проблемами , они использовали Reddis -- пока не очень хочется ставить его.
adamnengland.wordpress.com/2013/01/30/node-js-clus...
Ответ написан
Комментировать
Anubis
@Anubis
Люблю корейскую кухню и веб-разработку
Александр, доброго времени суток. Полагаю, на свой вопрос ответ уже нашли, теперь хотел бы обратиться к вам за консультацией. Сам проектирую браузерку — клиент на Unity, сервер на node.js, бд на redis (orm для неё на jugglingdb, очень похожа на mongoose), связь на tcp. Хочу сразу попробовать кластерную архитектуру, дабы потом не пришлось в спешке переписывать логику под неё в случае неожиданного роста онлайна.

Как и у вас, игрушка будет пошаговой (боёвка — в стиле jrpg). Единственное, что в бою завязано на таймер — выдача тому или иному игроку права хода на определённый лимит времени (таймаут хода) и централизованное управление просмотра анимаций ударов и заклинаний («анимация удара файрболлом 5 секунд, после передача хода следующему игроку»).

Вне боёв персонажам нужен временной луп для постепенного восстановления параметров.

Какого рода задачи логично распределить в воркеры? Допустимо ли использовать redis pub-sub для обмена очень большим количеством сообщений между потоками? Буду очень признателен вашим советам.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы