MySQL
Обзор
Расширение MySQL — это мост, позволяющий нескольким серверам TypeWriter делить одни и те же данные. Когда вы его включаете, факты (прогресс игроков) и артефакты (файлы данных, создаваемые другими расширениями) больше не хранятся в файлах под plugins/Typewriter/. Они хранятся в базе данных MySQL или PostgreSQL, которую читает и в которую пишет каждый сервер вашей сети.
Оно также управляет необязательным подключением к Redis (или Valkey). Redis передаёт мгновенные уведомления об изменениях, реестр «на каком сервере находится этот игрок» и сообщения, используемые социальными расширениями (Friend, Party, Guild) и расширением Ghost. Ряд других расширений BTC читают из этого расширения свои настройки, игровой режим и имя сервера.
У этого расширения нет записей: вы настраиваете его файлом и, по желанию, из веб-панели BTC.
Возможности
- Хранение в базе данных — факты и артефакты в MySQL 8 или PostgreSQL с автоматическим созданием таблиц и пулом соединений.
- Безопасно по умолчанию — если запрошено общее хранилище, а оно недоступно, сервер останавливается, а не продолжает молча работать на локальных файлах.
- Игровые режимы — отдельные данные для каждого игрового режима (выживание, скайблок, мини-игры) в одной базе данных.
- Синхронизация между серверами — изменения подхватываются другими серверами в течение секунд, а с Redis — мгновенно.
- Redis и Valkey — обмен сообщениями pub/sub, присутствие игроков во всей сети и переносы игроков через прокси.
- Правила хранения для каждого расширения — выбирайте, какие расширения используют базу данных и хранятся ли их данные по профилям и общие ли они для игровых режимов.
- Миграция при первом запуске — существующие факты и файлы ресурсов копируются в базу данных, когда она пуста.
- Реестр серверов — каждый сервер сообщает своё состояние (TPS, игроки, память, расширения) каждые 30 секунд.
- Секреты из окружения — любую настройку можно читать из переменной окружения.
- Поддержка панели администрирования — состояние серверов, игровые режимы, экспорт данных и просмотр таблиц для веб-панели BTC.
Установка
- Создайте пустую базу данных и пользователя для неё на MySQL 8 или новее либо на PostgreSQL. Пользователю нужно право создавать и изменять таблицы.
- По желанию, для нескольких серверов: запустите сервер Redis или Valkey, доступный каждому бэкенду.
- Положите JAR-файл MySQL в
plugins/Typewriter/extensions/и один раз перезапустите. Будет создан файлplugins/Typewriter/mysql/config.yml. - Отредактируйте файл, как описано в разделе Быстрый старт, затем перезапустите. Драйверы базы данных, пул соединений и клиент Redis поставляются вместе с расширением.
Быстрый старт
Переведём сервер на общую базу данных:
- Остановите сервер и откройте
plugins/Typewriter/mysql/config.yml. - В разделе
mysqlзаполнитеhost,port,database,usernameиpassword. Для PostgreSQL также добавьтеdatabase-type: "POSTGRESQL"и используйте порт5432. - Задайте
mysql.game-mode.currentравным игровому режиму этого сервера, напримерsurvival. Серверам, которые должны делить факты, нужно одинаковое значение. - Задайте
mysql.enabled: true. - Для сети включите Redis:
redis.enabled: true, сhost,portиpassword, уникальнымredis.server-nameдля каждого сервера и одинаковымredis.group-idна всех серверах, которые относятся друг к другу. - Запустите сервер. В логе появится «Database storage initialized». Существующие факты и файлы копируются в базу данных, пока она ещё пуста (см. Миграция).
- Повторите на остальных серверах с их собственными
server-name.
enabled осознанноПри mysql.enabled: false сервер хранит всё в собственных файлах. Это нормально для одного постоянного сервера, но если его каталог пересоздаётся из шаблона при запуске, данные записываются в папку, которой в следующий раз не будет, и ничто вас не предупредит. При mysql.enabled: true сервер, не имеющий доступа к базе данных, завершает работу, а не обслуживает игроков на локальных файлах.
Как это работает
При запуске расширение заменяет файловое хранилище TypeWriter хранилищем в базе данных до того, как любое другое расширение прочитает свои данные. TypeWriter держит факты в памяти и записывает изменения в базу данных небольшими пакетами. Другие серверы считывают изменения, которых ещё не видели, опрашивая базу данных, а Redis сообщает им сразу, когда что-то изменилось. Когда игрок выходит или его исключают, его ожидающие факты записываются немедленно.
В базе данных хранится только то, что изменилось. Удалённый факт хранится как строка со значением 0 (надгробие), чтобы другие серверы узнали об удалении, а не сохранили старое значение заново. Надгробия старше fact-tombstone-retention-days удаляются при запуске.
Конфигурация
Файл — plugins/Typewriter/mysql/config.yml. Большинство разделов необязательны: любой отсутствующий ключ принимает значение по умолчанию.
База данных
| Ключ | По умолчанию | Значение |
|---|---|---|
mysql.enabled | false | Использовать базу данных. При true сервер останавливается, если база данных недоступна или настройка неполна. |
mysql.database-type | MYSQL | MYSQL или POSTGRESQL. В сгенерированном файле отсутствует, добавьте при необходимости. |
mysql.host, mysql.port | localhost, 3306 | Адрес базы данных. Для PostgreSQL используйте 5432. |
mysql.database | typewriter | Имя базы данных. |
mysql.username, mysql.password | typewriter, пусто | Учётные данные. |
mysql.use-ssl | false | Использовать SSL (require для PostgreSQL). |
mysql.allow-public-key-retrieval | true | Только для MySQL. |
mysql.table-prefix | btc_ | Префикс таблиц, создаваемых этим расширением. Символы, кроме букв, цифр и _, становятся _, а префикс приводится к нижнему регистру. |
mysql.migrate-from-files | true | Копировать существующие файлы ресурсов в базу данных, когда таблица ресурсов пуста. |
mysql.fact-tombstone-retention-days | 30 | Дней до удаления строк-надгробий. Должно быть больше времени, которое любой сервер остаётся выключенным. |
mysql.sync-interval-seconds | 30 | Читается, но в этой версии не используется: интервал опроса задаёт сам TypeWriter. |
mysql.pool.* | см. ниже | Пул соединений. |
mysql.game-mode.current | default | Игровой режим этого сервера. |
Ключи пула: maximum-pool-size (10), minimum-idle (2), connection-timeout (30000 мс), max-lifetime (1800000 мс), validation-timeout (5000 мс) и keepalive-time (0, отключено).
Переменные окружения
Любое значение, в точности равное ${env:NAME}, при загрузке файла читается из переменной окружения NAME:
mysql:
password: ${env:BTC_TYPEWRITER_DB_PASSWORD}
Числовое или булево значение (например порт) читается с правильным типом. Незаданная или пустая переменная останавливает сервер с сообщением, называющим переменную. Поддерживаются только целые значения, а не текст, который лишь содержит ${env:...}.
Redis и Valkey
| Ключ | По умолчанию | Значение |
|---|---|---|
redis.enabled | false | Подключаться к Redis или Valkey. Работает, даже когда mysql.enabled равно false. |
redis.type | redis | redis или valkey. Оба используют один протокол. |
redis.host, redis.port | localhost, 6379 | Адрес сервера. |
redis.username, redis.password | пусто | Учётные данные. Имя пользователя default или пустое считается отсутствием имени. |
redis.channel | typewriter:events | Канал pub/sub, общий для всех серверов. |
redis.server-name | survival в коде, server1 в сгенерированном файле | Уникальное имя этого сервера. Оно же является именем сервера на прокси, используемым для переносов. |
redis.group-id | пусто | Группа сети этого сервера. Буквы, цифры, ., _, -, до 64 символов. Пустое значение означает изоляцию. |
group-id ограничивает каждую функцию, взаимодействующую между серверами: социальные сообщения, призраки и списки сети передаются только между серверами с одинаковым непустым id группы. Серверы с другой группой не видят друг друга.
Хранение для каждого расширения
В mysql.extensions.<name> у каждой записи есть три ключа:
enabled— хранит ли расширение собственные данные в базе данных.profile-mode—shared(один набор данных для всех профилей) илиper-profile.network-mode—shared(одни и те же данные во всех игровых режимах) илиisolated(свой набор для каждого игрового режима).
В сгенерированном файле перечислены значения по умолчанию для profiles, cosmetics, party, battlepass, translate и других. Эти настройки читают расширения, которые их поддерживают, например Profiles. Они не влияют на собственные факты и артефакты TypeWriter, которые всегда идут в базу данных, когда mysql.enabled равно true.
Настройки разрешаются в таком порядке: запись в config.yml, затем строка таблицы <prefix>game_mode_extensions для текущего игрового режима. При запуске каждое загруженное расширение получает там строку в отключённом состоянии, ожидающую, пока администратор её включит. Названия ключей нормализуются: TypeWriter-ProfilesExtension и profiles — одно и то же, а friends — псевдоним party.
Раздел profiles
Раздел profiles того же файла настраивает расширение Profiles: enabled, max-profiles, default-profile-name, save-statistics, save-inventory, save-ender-chest, require-permission, auto-create-default, force-profile-selection, switch-cooldown-seconds, coop-supported и creation-menu-entry-id. Их значение описано на той странице.
Что хранится
| Таблица | Содержимое |
|---|---|
<prefix>facts | Факты каждого игрока и группы: id записи, id группы, игровой режим, значение, время последнего обновления. |
<prefix>assets | Артефакты и другие ресурсы (текстовые или двоичные) по пути и игровому режиму. |
<prefix>game_modes | Игровые режимы, известные сети. |
<prefix>game_mode_extensions | Правила хранения для каждого игрового режима и расширения. |
<prefix>server_extensions, <prefix>extension_entry_filters | Настройки синхронизации для каждого сервера и каждой записи. |
servers | Реестр работающих серверов. У его имени нет префикса. |
Другие расширения BTC создают собственные таблицы в той же базе данных, например btc_guild_bank для банка Guild и typewriter_profiles для Profiles. Названия таблиц и столбцов создаются автоматически, а недостающие столбцы добавляются при запуске.
Каждая строка факта и ресурса несёт игровой режим. Сервер читает и записывает только строки своего mysql.game-mode.current (и строки с пометкой global), поэтому два игровых режима никогда не делят факты или артефакты, даже в одной базе данных.
Синхронизация между серверами
Данные. Без Redis серверы всё равно сходятся: каждый опрашивает базу данных на предмет изменений, сделанных другими. С Redis сервер, записывающий факты, публикует короткое уведомление, и остальные синхронизируются немедленно. Запускайте все серверы в одном часовом поясе, потому что метки времени фактов хранятся в местном времени каждого сервера.
Социальные сообщения. Friend, Party и Guild публикуют свои изменения в виде сообщений JSON в канале Redis. Сервер игнорирует собственные сообщения. Они отправляются, только когда Redis подключён и redis.group-id не пуст.
Присутствие. Каждый сервер записывает в Redis presence:<player name> для каждого игрока в сети со сроком действия 120 секунд, обновляемым каждые 50 секунд и удаляемым, когда игрок выходит. Функции вроде «телепортироваться к другу» читают его, чтобы найти сервер игрока.
Переносы. Чтобы перенести игрока на другой сервер, расширение обращается к прокси через стандартный канал плагина BungeeCord, а для переносов по всему кластеру — через канал btc:bridge, понятный прокси BTC. redis.server-name каждого сервера должен совпадать с именем сервера, известным прокси.
Переподключение. Если Redis отключается, публикация пытается переподключиться, а подписчик повторяет попытки до 10 раз с растущей задержкой (от 5 секунд до 5 минут).
Миграция
При первом запуске игрового режима с mysql.enabled: true:
- Если в таблице фактов нет фактов для этого игрового режима, факты, находящиеся сейчас в памяти (из локальных файлов), копируются в базу данных. В противном случае факты загружаются из базы данных, а локальные игнорируются.
- Если
mysql.migrate-from-filesравноtrueи таблица ресурсов для этого игрового режима пуста, в неё копируются файлыplugins/Typewriter/assets/.
Сделайте резервную копию plugins/Typewriter/ перед первым запуском. Возврат к файлам означает установку mysql.enabled: false: содержимое базы данных обратно не копируется.
Панель администрирования
Расширение отвечает на запросы управления из веб-панели BTC: список серверов и их состояние (TPS, игроки, память, установленные расширения, задержка Redis, статистика пула), создание и удаление игровых режимов, включение расширений для каждого игрового режима, экспорт, импорт и миграция данных игрового режима, просмотр и редактирование таблиц хранилища и чтение последних журналов базы данных. Каждый запрос проверяет право пользователя панели и ограничивается по частоте.
Права
Эти права проверяются для пользователя веб-панели. Они регистрируются без значения по умолчанию, поэтому выдавайте их администраторам.
| Право | Даёт доступ к |
|---|---|
typewriter.admin.server.list | Просмотру серверов, игровых режимов, хранилища, таблиц и обнаруженных расширений. |
typewriter.admin.server.health | Просмотру проверок состояния и журналов базы данных. |
typewriter.admin.server.deregister | Удалению сервера из реестра. |
typewriter.admin.gamemode.create | Созданию и удалению игровых режимов. |
typewriter.admin.gamemode.configure | Изменению настроек расширений, фильтров записей и переопределений хранилища, импорту, экспорту и миграции данных, редактированию строк таблиц. |
Эксплуатация
- Перезагрузка. Публикация
config_updateв канале Redis заставляет каждый сервер перезагрузитьmysql/config.ymlи зарегистрировать новые расширения. В обычной работе файл читается один раз при запуске. - Реестр серверов. Таблица
serversобновляется каждые 30 секунд. Записи старше 60 секунд считаются отключёнными и удаляются при запуске, а сервер удаляет себя при остановке JVM. - Взаимоблокировки. Пакеты фактов повторяются до 3 раз, когда MySQL сообщает о взаимоблокировке.
- Резервные копии. Делайте резервные копии таблиц
<prefix>factsи<prefix>assets. Поскольку удаление — это строка со значением0, восстановленная резервная копия, старше срока хранения надгробий, может вернуть удалённые факты. - Обновления. Обновляйте JAR на всех серверах в одно окно перезапуска. Схема обновляется на месте при запуске.
Устранение неполадок
Сервер останавливается при запуске с сообщением «Refusing to serve players on local file storage.» mysql.enabled равно true, а база данных недоступна или значение неполно. Прочитайте строку перед ним: сообщение SEVERE называет user@host:port/database и ошибку. Исправьте учётные данные, сеть или базу данных либо задайте mysql.enabled: false, если этот сервер должен использовать файлы.
«Configuration incomplete: environment variable(s) X are referenced ... but are not set.» У значения ${env:X} нет соответствующей непустой переменной окружения в процессе сервера. Задайте её в определении службы, а не только в вашей оболочке.
Факты не делятся между двумя серверами. Проверьте, что у обоих mysql.enabled: true, одна и та же база данных и один и тот же mysql.game-mode.current. Разные игровые режимы хранят отдельные факты. Без Redis изменение может появляться через интервал опроса.
Друзья, отряды, гильдии или призраки не переходят между серверами. Redis должен быть включён и подключён, redis.group-id должен быть задан одним и тем же непустым значением на каждом сервере, а redis.server-name должен быть уникален для каждого сервера. При пустом id группы социальные сообщения не отправляются.
В логе написано «Failed to connect to Redis». Проверьте хост, порт, имя пользователя и пароль. Для пароля Redis requirepass нужен redis.password. Расширение продолжает работать без Redis, используя только опрос.
Расширение не хранит свои данные в базе данных. Расширения, обнаруженные при запуске, регистрируются отключёнными. Включите расширение в mysql.extensions в config.yml или в панели администрирования, затем перезапустите.
Факты удалённого игрока или удалённый факт возвращаются. Сервер был выключен дольше, чем fact-tombstone-retention-days, и заново сохранил старые данные. Увеличьте срок хранения и не восстанавливайте старые копии сервера.
«HikariPool ... connection is not available» или тайм-ауты. Пул исчерпан. Увеличьте mysql.pool.maximum-pool-size (и собственный лимит соединений базы данных) и проверьте медленные запросы на сервере базы данных.
Метки времени выглядят неправильно или изменения пропускаются. Серверы, делящие базу данных, работают в разных часовых поясах. Выровняйте их.
Связанные расширения
- Profiles — использует раздел
profiles, а также правила хранения профилей и игровых режимов. - Friend, Party, Guild — социальные функции, синхронизируемые через Redis.
- Ghost — показывает игроков других серверов, используя подключение Redis.
- Translate, Rewards, BattlePass — расширения, зависящие от этого.