Задать вопрос
@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'ом?

Рассматриваются также любые другие варианты - может стоит вообще делать как-то иначе? Поделитесь опытом, наверняка уже сталкивались с подобными задачами.
  • Вопрос задан
  • 10182 просмотра
Подписаться 14 Оценить Комментировать
Решения вопроса 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 для обмена очень большим количеством сообщений между потоками? Буду очень признателен вашим советам.
Ответ написан
Комментировать
Ваш ответ на вопрос

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

Похожие вопросы