MySQL
Visão geral
A extensão MySQL é a ponte que permite que vários servidores TypeWriter compartilhem os mesmos dados. Quando você a ativa, os facts (o progresso dos jogadores) e os artifacts (arquivos de dados criados por outras extensões) deixam de ser mantidos em arquivos em plugins/Typewriter/. Eles são armazenados em um banco de dados MySQL ou PostgreSQL que todos os servidores da sua rede leem e gravam.
Ela também é dona da conexão opcional com o Redis (ou Valkey). O Redis transporta notificações instantâneas de alterações, o registro de "em qual servidor este jogador está" e as mensagens usadas pelas extensões sociais (Friend, Party, Guild) e pelo Ghost. Várias outras extensões da BTC leem suas configurações, o modo de jogo e o nome do servidor a partir desta extensão.
Esta extensão não tem entries: você a configura com um arquivo e, opcionalmente, pelo painel web da BTC.
Recursos
- Armazenamento em banco de dados — facts e artifacts em MySQL 8 ou PostgreSQL, com criação automática de tabelas e um pool de conexões.
- Seguro por padrão — quando o armazenamento compartilhado é solicitado e não pode ser alcançado, o servidor para em vez de continuar silenciosamente em arquivos locais.
- Modos de jogo — dados separados por modo de jogo (sobrevivência, skyblock, minigames) no mesmo banco de dados.
- Sincronização entre servidores — as alterações são captadas pelos outros servidores em segundos, instantaneamente com o Redis.
- Redis e Valkey — mensageria pub/sub, presença de jogadores em toda a rede e transferências de jogadores pelo proxy.
- Regras de armazenamento por extensão — escolha quais extensões usam o banco de dados e se seus dados são por perfil e compartilhados entre modos de jogo.
- Migração na primeira inicialização — os facts e arquivos de assets existentes são copiados para o banco de dados quando ele está vazio.
- Registro de servidores — cada servidor informa seu status (TPS, jogadores, memória, extensões) a cada 30 segundos.
- Segredos a partir do ambiente — qualquer configuração pode ser lida de uma variável de ambiente.
- Suporte ao painel de administração — saúde dos servidores, modos de jogo, exportação de dados e navegação pelas tabelas para o painel web da BTC.
Instalação
- Crie um banco de dados vazio e um usuário para ele no MySQL 8 ou posterior, ou no PostgreSQL. O usuário precisa do direito de criar e alterar tabelas.
- Opcional, para vários servidores: execute um servidor Redis ou Valkey que todos os backends consigam alcançar.
- Coloque o JAR do MySQL em
plugins/Typewriter/extensions/e reinicie uma vez. O arquivoplugins/Typewriter/mysql/config.ymlé criado. - Edite o arquivo conforme descrito em Primeiros passos e reinicie. Os drivers de banco de dados, o pool de conexões e o cliente Redis acompanham a extensão.
Primeiros passos
Mova um servidor para um banco de dados compartilhado:
- Pare o servidor e abra
plugins/Typewriter/mysql/config.yml. - Em
mysql, preenchahost,port,database,usernameepassword. Para o PostgreSQL, adicione tambémdatabase-type: "POSTGRESQL"e use a porta5432. - Defina
mysql.game-mode.currentcom o modo de jogo deste servidor, por exemplosurvival. Os servidores que devem compartilhar facts precisam do mesmo valor. - Defina
mysql.enabled: true. - Para uma rede, ative o Redis:
redis.enabled: true, comhost,portepassword, umredis.server-nameúnico por servidor e o mesmoredis.group-idem todos os servidores que pertencem ao mesmo grupo. - Inicie o servidor. O log mostra "Database storage initialized". Os facts e arquivos existentes são copiados para o banco de dados quando ele ainda está vazio (veja Migração).
- Repita nos outros servidores com seu próprio
server-name.
enabled de propósitoCom mysql.enabled: false, um servidor armazena tudo em seus próprios arquivos. Isso é aceitável para um único servidor permanente, mas, se o diretório dele for recriado a partir de um template ao iniciar, os dados são gravados em uma pasta que não existirá da próxima vez, e nada avisa você. Com mysql.enabled: true, um servidor que não consegue alcançar o banco de dados é desligado em vez de atender os jogadores com arquivos locais.
Como funciona
Quando a extensão inicia, ela substitui o armazenamento em arquivos do TypeWriter pelo armazenamento em banco de dados, antes que qualquer outra extensão leia seus dados. O TypeWriter mantém os facts na memória e grava as alterações no banco de dados em pequenos lotes. Os outros servidores leem as alterações que ainda não viram consultando o banco de dados periodicamente, e o Redis os avisa imediatamente quando algo muda. Quando um jogador sai ou é expulso, seus facts pendentes são gravados na hora.
O banco de dados armazena apenas o que mudou. Um fact excluído é armazenado como uma linha com valor 0 (um tombstone), para que os outros servidores saibam da exclusão em vez de salvar novamente o valor antigo. Os tombstones mais antigos que fact-tombstone-retention-days são removidos na inicialização.
Configuração
O arquivo é plugins/Typewriter/mysql/config.yml. A maioria das seções é opcional: qualquer chave ausente assume seu padrão.
Banco de dados
| Chave | Padrão | Significado |
|---|---|---|
mysql.enabled | false | Usar o banco de dados. Quando true, o servidor para se o banco de dados estiver inacessível ou uma configuração estiver incompleta. |
mysql.database-type | MYSQL | MYSQL ou POSTGRESQL. Não está no arquivo gerado; adicione-o se necessário. |
mysql.host, mysql.port | localhost, 3306 | Endereço do banco de dados. Use 5432 para o PostgreSQL. |
mysql.database | typewriter | Nome do banco de dados. |
mysql.username, mysql.password | typewriter, vazio | Credenciais. |
mysql.use-ssl | false | Usar SSL (require para o PostgreSQL). |
mysql.allow-public-key-retrieval | true | Somente MySQL. |
mysql.table-prefix | btc_ | Prefixo das tabelas criadas por esta extensão. Os caracteres que não sejam letras, dígitos e _ viram _, e o prefixo é convertido para minúsculas. |
mysql.migrate-from-files | true | Copiar os arquivos de assets existentes para o banco de dados quando a tabela de assets está vazia. |
mysql.fact-tombstone-retention-days | 30 | Dias antes de as linhas de exclusão serem removidas. Deve ser maior que o tempo que qualquer servidor fica offline. |
mysql.sync-interval-seconds | 30 | Lido, mas não usado nesta versão: o intervalo de consulta é definido pelo próprio TypeWriter. |
mysql.pool.* | veja abaixo | Pool de conexões. |
mysql.game-mode.current | default | Modo de jogo deste servidor. |
As chaves do pool são maximum-pool-size (10), minimum-idle (2), connection-timeout (30000 ms), max-lifetime (1800000 ms), validation-timeout (5000 ms) e keepalive-time (0, desativado).
Variáveis de ambiente
Qualquer valor que seja exatamente ${env:NAME} é lido da variável de ambiente NAME quando o arquivo é carregado:
mysql:
password: ${env:BTC_TYPEWRITER_DB_PASSWORD}
Um valor numérico ou booleano (por exemplo, uma porta) é lido com o tipo correto. Uma variável não definida ou vazia interrompe o servidor com uma mensagem que indica o nome da variável. Apenas valores inteiros são suportados, não textos que apenas contenham ${env:...}.
Redis e Valkey
| Chave | Padrão | Significado |
|---|---|---|
redis.enabled | false | Conectar ao Redis ou Valkey. Funciona mesmo quando mysql.enabled é false. |
redis.type | redis | redis ou valkey. Ambos usam o mesmo protocolo. |
redis.host, redis.port | localhost, 6379 | Endereço do servidor. |
redis.username, redis.password | vazio | Credenciais. O usuário default ou um vazio é tratado como sem usuário. |
redis.channel | typewriter:events | Canal pub/sub compartilhado por todos os servidores. |
redis.server-name | survival no código, server1 no arquivo gerado | Nome único deste servidor. Também é o nome do servidor no proxy usado nas transferências. |
redis.group-id | vazio | Grupo de rede deste servidor. Letras, dígitos, ., _, -, até 64 caracteres. Vazio significa isolado. |
O group-id delimita todo recurso que conversa entre servidores: as mensagens sociais, os ghosts e as listas da rede só trafegam entre servidores com o mesmo id de grupo não vazio. Os servidores de grupos diferentes não se enxergam.
Armazenamento por extensão
Em mysql.extensions.<name>, cada entrada tem três chaves:
enabled— se a extensão armazena seus próprios dados no banco de dados.profile-mode—shared(um único conjunto de dados para todos os perfis) ouper-profile.network-mode—shared(os mesmos dados em todos os modos de jogo) ouisolated(um conjunto por modo de jogo).
O arquivo gerado lista padrões para profiles, cosmetics, party, battlepass, translate e outros. Essas configurações são lidas pelas extensões que as suportam, por exemplo o Profiles. Elas não afetam os facts e artifacts do próprio TypeWriter, que sempre vão para o banco de dados quando mysql.enabled é true.
As configurações são resolvidas nesta ordem: a entrada no config.yml e depois a linha da tabela <prefix>game_mode_extensions para o modo de jogo atual. Na inicialização, toda extensão carregada recebe ali uma linha, desativada, aguardando que um administrador a ative. Os nomes das chaves são normalizados: TypeWriter-ProfilesExtension e profiles são a mesma coisa, e friends é um alias de party.
Seção profiles
A seção profiles do mesmo arquivo configura a extensão 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 e creation-menu-entry-id. Veja a página dela para o significado de cada um.
O que é armazenado
| Tabela | Conteúdo |
|---|---|
<prefix>facts | Facts de cada jogador e grupo: id da entry, id do grupo, modo de jogo, valor, última atualização. |
<prefix>assets | Artifacts e outros assets (texto ou binário), por caminho e modo de jogo. |
<prefix>game_modes | Modos de jogo conhecidos pela rede. |
<prefix>game_mode_extensions | Regras de armazenamento por modo de jogo e extensão. |
<prefix>server_extensions, <prefix>extension_entry_filters | Configurações de sincronização por servidor e por entry. |
servers | Registro dos servidores em execução. Seu nome não tem prefixo. |
Outras extensões da BTC criam suas próprias tabelas no mesmo banco de dados, por exemplo btc_guild_bank para o banco do Guild e typewriter_profiles para o Profiles. Os nomes das tabelas e colunas são criados automaticamente e as colunas ausentes são adicionadas na inicialização.
Toda linha de fact e de asset carrega um modo de jogo. Um servidor só lê e grava as linhas do seu próprio mysql.game-mode.current (e as linhas marcadas como global), então dois modos de jogo nunca compartilham facts ou artifacts, mesmo no mesmo banco de dados.
Sincronização entre servidores
Dados. Sem o Redis, os servidores ainda convergem: cada um consulta periodicamente o banco de dados em busca de alterações feitas pelos outros. Com o Redis, um servidor que grava facts publica um breve aviso, e os outros sincronizam imediatamente. Execute todos os servidores no mesmo fuso horário, pois os timestamps dos facts são armazenados no horário local de cada servidor.
Mensagens sociais. O Friend, o Party e o Guild publicam suas alterações como mensagens JSON no canal do Redis. Um servidor ignora suas próprias mensagens. Elas só são enviadas quando o Redis está conectado e redis.group-id não está vazio.
Presença. Cada servidor grava presence:<player name> no Redis para cada jogador online, com uma expiração de 120 segundos renovada a cada 50 segundos e removida quando o jogador sai. Recursos como "teletransportar para um amigo" a leem para encontrar o servidor do jogador.
Transferências. Para mover um jogador para outro servidor, a extensão pede ao proxy pelo canal de plugin padrão BungeeCord e, para movimentos em todo o cluster, pelo canal btc:bridge compreendido pelo proxy da BTC. O redis.server-name de cada servidor deve corresponder ao nome do servidor conhecido pelo proxy.
Reconexão. Se o Redis cair, a publicação tenta reconectar, e o assinante tenta até 10 vezes com um atraso crescente (de 5 segundos até 5 minutos).
Migração
Na primeira vez que um modo de jogo é iniciado com mysql.enabled: true:
- Se a tabela de facts não contém nenhum fact para este modo de jogo, os facts atualmente na memória (dos arquivos locais) são copiados para o banco de dados. Caso contrário, os facts são carregados do banco de dados e os locais são ignorados.
- Se
mysql.migrate-from-filesfortruee a tabela de assets estiver vazia para este modo de jogo, os arquivos deplugins/Typewriter/assets/são copiados para ela.
Mantenha um backup de plugins/Typewriter/ antes da primeira inicialização. Voltar aos arquivos significa definir mysql.enabled: false: o conteúdo do banco de dados não é copiado de volta.
Painel de administração
A extensão responde a solicitações de gerenciamento do painel web da BTC: listar servidores e sua saúde (TPS, jogadores, memória, extensões instaladas, latência do Redis, estatísticas do pool), criar e excluir modos de jogo, ativar extensões por modo de jogo, exportar, importar e migrar os dados de um modo de jogo, navegar e editar as tabelas de armazenamento e ler os logs recentes do banco de dados. Cada solicitação verifica uma permissão da pessoa que usa o painel e tem limite de taxa.
Permissões
Estas permissões são verificadas para o usuário do painel web. Elas não são registradas com um padrão, então conceda-as aos administradores.
| Permissão | Concede |
|---|---|
typewriter.admin.server.list | Ver servidores, modos de jogo, armazenamento, tabelas e extensões descobertas. |
typewriter.admin.server.health | Ver verificações de saúde e logs do banco de dados. |
typewriter.admin.server.deregister | Remover um servidor do registro. |
typewriter.admin.gamemode.create | Criar e excluir modos de jogo. |
typewriter.admin.gamemode.configure | Alterar configurações de extensões, filtros de entries e substituições de armazenamento, importar, exportar e migrar dados, editar linhas de tabelas. |
Operações
- Recarga. Publicar
config_updateno canal do Redis faz todos os servidores recarregaremmysql/config.ymle registrarem novas extensões. A operação normal lê o arquivo uma única vez na inicialização. - Registro de servidores. A tabela
serversé atualizada a cada 30 segundos. As entradas com mais de 60 segundos são tratadas como offline e removidas na inicialização, e um servidor se remove quando a JVM para. - Deadlocks. Os lotes de facts são repetidos até 3 vezes quando o MySQL relata um deadlock.
- Backups. Faça backup das tabelas
<prefix>factse<prefix>assets. Como uma exclusão é uma linha com valor0, um backup restaurado mais antigo que a retenção de tombstones pode trazer de volta facts excluídos. - Atualizações. Atualize o JAR em todos os servidores na mesma janela de reinicialização. O esquema é atualizado no local na inicialização.
Solução de problemas
O servidor para na inicialização com "Refusing to serve players on local file storage." mysql.enabled é true e o banco de dados está inacessível ou um valor está incompleto. Leia a linha anterior: a mensagem SEVERE indica user@host:port/database e o erro. Corrija as credenciais, a rede ou o banco de dados, ou defina mysql.enabled: false se este servidor deve usar arquivos.
"Configuration incomplete: environment variable(s) X are referenced ... but are not set." Um valor ${env:X} não tem nenhuma variável de ambiente correspondente e não vazia no processo do servidor. Defina-a na definição do serviço, não apenas no seu shell.
Os facts não são compartilhados entre dois servidores. Verifique se ambos têm mysql.enabled: true, o mesmo banco de dados e o mesmo mysql.game-mode.current. Modos de jogo diferentes armazenam facts separados. Sem o Redis, uma alteração pode levar até o intervalo de consulta para aparecer.
Amigos, parties, guildas ou ghosts não atravessam servidores. O Redis deve estar ativado e conectado, redis.group-id deve estar definido com o mesmo valor não vazio em todos os servidores e redis.server-name deve ser único por servidor. Com um id de grupo vazio, as mensagens sociais não são enviadas.
O log mostra "Failed to connect to Redis". Verifique o host, a porta, o usuário e a senha. Uma senha requirepass do Redis precisa de redis.password. A extensão continua funcionando sem o Redis, usando apenas a consulta periódica.
Uma extensão não armazena seus dados no banco de dados. As extensões descobertas na inicialização são registradas como desativadas. Ative a extensão em mysql.extensions no config.yml, ou no painel de administração, e reinicie.
Facts de um jogador excluído ou um fact excluído voltam. Um servidor ficou offline por mais tempo que fact-tombstone-retention-days e salvou novamente dados antigos. Aumente a retenção e evite restaurar cópias antigas de um servidor.
"HikariPool ... connection is not available" ou timeouts. O pool está esgotado. Aumente mysql.pool.maximum-pool-size (e o limite de conexões do próprio banco de dados) e verifique consultas lentas no servidor de banco de dados.
Os timestamps parecem errados ou alterações são perdidas. Servidores que compartilham um banco de dados rodam em fusos horários diferentes. Alinhe-os.