Игрок бьёт монстра, монстр в ту же секунду решает, куда шагнуть, а сосед по карте обязан увидеть и то, и другое. Всё это — одно состояние мира, в которое лезут десятки процессов сразу. Вопрос не в том, где его хранить, а в том, выдержит ли хранилище такой поток.
Что такое Redis в двух словах
Redis — хранилище пар «ключ — значение» в оперативной памяти: простые структуры, числа, текст, JSON. Сброс на диск есть, но игровому серверу он не подходит: операция слишком медленная, а состояние мира меняется непрерывно.
В обычных веб-приложениях Redis выгодно держать кешем поверх SQL: собрать данные при старте, обновлять по ходу, раз в интервал сбрасывать обратно. Работа с памятью на порядки быстрее, чем с дисковой базой.
Две его особенности пригодились отдельно. Первая — гео-координаты: «найти всех игроков в радиусе от точки» — типовой игровой запрос. Вторая — Pub/Sub: один процесс кладёт сообщение в канал, другой слушает, и стоять они могут на разных машинах.
На схеме ниже Source — клиенты игры, BUS — сервер, Channel — канал Redis, Listener — процессы, где лежит логика.

Каналов может быть несколько, и это даёт межпроцессное взаимодействие без ожидания. Отправив команду, не нужно стоять и ждать ответа, как в привычной схеме «запрос — ответ»: процесс занимается своими делами, а обработчик сам пришлёт результат либо передаст его дальше.
Вторая схема — то же самое другими словами: Source — клиент, Pipe — канал Redis, фильтр — процесс с логикой, Sink — получатель результата.

Поэтому выбор и пал на Redis. Оставался вопрос, сколько игроков он вытянет.
Где потолок
По данным разработчиков Redis держит от 10 000 до 80 000 запросов в секунду — зависит от процессора. Для нагруженного сайта это прекрасный показатель: там и сто миллисекунд ответа никто не заметит, страницы грузятся дольше секунды.

В играх планка другая. Хороший пинг — 60 миллисекунд, идеальный — 30, на сотне уже заметны рывки.
Что такое пинг, я разобрал простым языком в видео:
Способы ускориться есть, но потолок никуда не девается: 80 000 запросов в секунду — при том что одна команда игрока разворачивается в три и больше обращений к хранилищу. Пропускная способность самого WebSocket-сервера на workerman лежит в куда более широком диапазоне.

И читают Redis не только игроки. NPC живут на сервере отдельными процессами: сервер сам считает, куда им идти и кого атаковать, и данные об игроках нужны им актуальные. Держать копию в памяти каждого процесса нельзя — она сразу разойдётся с реальностью. Подписать NPC на Pub/Sub тоже не выход: им прилетало бы всё подряд, включая события на другом конце карты.
Способы разделять память между процессами быстрее существуют. Часть из них плохо масштабируется, зато вытягивает 700 000 запросов в секунду — о них в следующих частях.
Почему не сложить всё в один процесс
Такая модель имеет право на жизнь, и серверы онлайн-игр часто устроены именно так: монолит, где логика собрана в одном файле из ветвлений, либо, что аккуратнее, набор подгружаемых библиотек.

Обычно такие серверы не масштабируются: есть копия мира со своими игроками, которые с чужими не пересекаются. Данные грузятся один раз при старте и раз в интервал сбрасываются на сохранение.
Проблема в другом. Когда обсчёт NPC, команды игроков и сохранение идут в одном процессе, приложение становится синхронным: пока цикл дойдёт до конца, проходит заметное время, и растёт оно пропорционально наполнению мира. А идти такие циклы должны непрерывно.
Отсюда и потолок на размер мира: оперативная память — дорогой ресурс, и в одном процессе её не растянуть.
Как это устроено у других
Ниже — несколько онлайн-игр, чью серверную архитектуру я разбирал. Это обзор подходов, не рейтинг.
Fallout Online: Requiem на движке fonline. Коду пятнадцать лет: C++, 32-битная архитектура, то есть четыре гигабайта памяти потолком, несколько процессов, каждый обсчитывает свою часть мира. Пакеты игрокам уходят потоком каждые 2–10 миллисекунд, хотя за это время на карте часто ничего не меняется, — канал забивается впустую. Без единого игрока сервер съедает 40% процессора на несколько тысяч NPC и объектов, после двухсот онлайн — 99%.

BrowserQuest — браузерная игра: клиент на JS, сервер на PHP. Написал её автор workerman, того самого фреймворка, и WebSocket-соединения там держит он же.

Документации нет, но код читается. Архитектура зеркальна предыдущей: всё выполняется в одном процессе, экземпляры классов создаются по запросу клиента, логика отрабатывает, ответ уходит обратно.
Каждые 20 миллисекунд таймер запускает в том же процессе функцию, которая обсчитывает жизнь всех NPC и предметов и рассылает результат игрокам. В PHP это делается через тики: обработчик получает управление между операциями. Новый процесс не создаётся — текущий приостанавливается до конца обработчика.
Несколько «миров» игра создаёт, и это уже отдельные процессы, но по сути копии игры со своими игроками: конфигурация обещает тысячу на мир. При таком же количестве NPC, которые обсчитываются там же, хорошего пинга ждать не стоит.
Отдельно неудобны числовые команды вместо строковых: 4,47,170 значит «идти в точку 47×170», 8,"1122" — «атаковать объект». Без продуманной защиты так можно ударить цель с другого конца карты; команды вида move_down или atack_right в этом смысле честнее. На скриншоте виден и рассинхрон: игрок ударил, отошёл, и только после этого пришло сообщение об уроне.
Третья — моя демонстрационная игра на Unity и PHP, собранная на решениях из этой серии.

Что было дальше
Главной задачей с самого начала была не игровая механика, а архитектура: механику воскрешения игрока пишешь за день, а над распределением нагрузки думаешь неделями.
Redis остался в основе, но единственным хранилищем быть перестал: часть состояния уехала в более быстрые механизмы разделяемой памяти, а мир перестал помещаться в один процесс. Сейчас на этой архитектуре работает платформа с открытым бесшовным миром, клиентом на Unity и редактором, где механика описывается текстом. Демонстрация той, ранней версии сервера:
Если вы держали состояние мира на другой схеме — расскажите, во что она упёрлась первой.
История:
Redis
Выбор технологий, протокола и архитектурный шаблон Entity Component System
FPS, Ping, паузы между командами, интерполяция и экстраполяция
Event-driven паттерн, JSON-RPC и почему не сервисная (SOA) архитектура
Сетевая карта и задержка кадра (Latency frame) по RFC 2544 (1242)
Конвейер контента для MMO: импорт анимации, экипировка и обновление игры без патчей
