Iaeeeeeee cambada, tudo na paz? bora falar mais um cadim do database do golfinho?
⚠️ CONTÉM TEXTO MELHORADO POR AI – E TA TUDO BEM (SE SOUBER USAR 🤭)⚠️
Neste artigo aqui eu vou falar do setup do InnoDB Cluster (mostrar como faz também) e vou dar alguns detalhes de como administrar, monitorar etc, nada de outro mundo. E não, não é parecido com o Oracle RAC mas é muito legal quanto.
O que é o MySQL InnoDB Cluster?
O InnoDB Cluster é uma arquitetura de alta disponibilidade nativa do MySQL que combina replicação automagica, failover integrado e escalabilidade da parada toda. Ele utiliza de Group Replication para sincronizar instâncias de banco de dados, garantindo consistência entre os nós e permitindo a distribuição de cargas de trabalho. De novo, não existem nada como o Oracle RAC mesmo 😆 (e ponto final).
Bora para a arquitetura:
A arquitetura de um cluster de alta disponibilidade (High Availability Cluster) do MySQL com Group Replication:

Componentes:
- Client App:
- Representa as aplicações dos usuários que se conectam ao banco de dados.
- Utiliza o MySQL Connector para comunicação com o cluster via MySQL Router.
- MySQL Router:
- Um middleware que atua como intermediário entre os aplicativos e os servidores MySQL.
- Direciona as solicitações de leitura/escrita para o nó primário (Primary Instance R/W) e distribui as leituras entre os nós secundários (Secondary Instances R/O), dependendo da configuração.
- MySQL Shell (Cluster Admin):
- Uma interface de administração utilizada para gerenciar o cluster.
- Faz uso da MySQL Admin API para configurar e monitorar o cluster, incluindo a inicialização do Group Replication.
- MySQL Servers:
- Primary Instance R/W:
- É o nó principal que processa as operações de leitura e escrita.
- Participa do Group Replication, garantindo que as alterações sejam propagadas para os nós secundários.
- Secondary Instances R/O:
- São nós secundários configurados para replicação em tempo real.
- São usados principalmente para operações de leitura, otimizando o desempenho do cluster.
- Primary Instance R/W:
Funcionamento:
- Group Replication:
- Um protocolo de replicação nativa do MySQL usado para sincronizar os dados entre o nó primário e os nós secundários.
- Assegura que todas as instâncias estejam atualizadas com as alterações feitas no nó primário.
- Alta Disponibilidade:
- Se o nó primário falhar, um dos nós secundários pode ser promovido automaticamente a nó primário para garantir a continuidade do serviço.
- O MySQL Router ajusta automaticamente os encaminhamentos para refletir essa mudança.
Setup do InnoDB Cluster
Agora, vou mostrar o passo a passo para realizar o setup do cluster.
Topicos do role:
- Topologia
- Pre-Requisitos
- Instalação do MySQL e seus componentes (MySQL Shell e escambal)
- Configuração do my.cnf
- Setup do Cluster via MySQL Shell
- Setup do MySQL Router
- Teste de disponibilidade
Topologia da parada
- myorcl1
- 192.168.10.101
- myorcl2
- 192.168.10.102
- myorcl3
- 192.168.10.103
- myrouter
- 192.168.10.100
Pre-Requisitos (Do meu ambiente kkk)
- Ubuntu 20
- RAM 8GB
- HD 40GB
- MySQL Server 8
- MySQL Shell 8
- MySQL Router 8
- “DNS”
- Config do /etc/hosts
127.0.0.1 localhost
# Configuração dos nós do cluster
192.168.61.101 myorcl1
192.168.61.102 myorcl2
192.168.61.103 myorcl3
# Configuração do MySQL Router
192.168.61.100 myrouter
Instalação do MySQL e seus componentes (MySQL Shell e escambal)
Passo 1: Instalação do MySQL e MySQL Shell no Ubuntu
Instalar o MySQL Server
Execute os comandos abaixo em cada servidor do cluster (myorcl1
, myorcl2
, myorcl3
):
Atualize os pacotes do sistema
sudo apt update && sudo apt upgrade -y
Adicione o repositório do MySQL
Baixe e adicione o repositório oficial do MySQL:
wget https://dev.mysql.com/get/mysql-apt-config_0.8.26-1_all.deb
sudo dpkg -i mysql-apt-config_0.8.26-1_all.deb
sudo apt update
Instale o MySQL Server
Instale a versão desejada do MySQL Server (por exemplo, 8.0):
sudo apt install -y mysql-server
Verifique se o serviço está ativo
Inicie e habilite o MySQL para iniciar no boot:
sudo systemctl start mysql
sudo systemctl enable mysql
Synchronizing state of mysql.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install enable mysql
sudo systemctl status mysql
● mysql.service - MySQL Community Server
Loaded: loaded (/lib/systemd/system/mysql.service; enabled; vendor preset: enabled)
Active: active (running) since Fri 2024-12-29 10:15:32 UTC; 5min ago
Main PID: 12345 (mysqld)
Tasks: 37 (limit: 4915)
Memory: 148.4M
CGroup: /system.slice/mysql.service
└─12345 /usr/sbin/mysqld
Dec 29 10:15:32 ubuntu-server systemd[1]: Started MySQL Community Server.
Realize a configuração inicial do MySQL
Utilize o utilitário de configuração inicial do MySQL para definir senha e outras configs, so seguir o que tem na tela e ser feliz:
sudo mysql_secure_installation
Instalar o MySQL Shell
Instale o MySQL Shell
O MySQL Shell pode ser instalado com o seguinte comando:
sudo apt install -y mysql-shell
Verifique a instalação do MySQL Shell
Confirme que o MySQL Shell está instalado:
mysqlsh --version
Configurar o MySQL para aceitar conexões externas
Pelo root:
UPDATE mysql.user SET host = '%' WHERE user = 'root' AND host = '127.0.0.1';
FLUSH PRIVILEGES;
Pelo bind-adress:
sudo vi /etc/mysql/mysql.conf.d/mysqld.cnf
bind-address = 0.0.0.0
Portas do firewall utilizadas no MySQL InnoDB Cluster
Porta | Protocolo | Finalidade |
---|---|---|
3306 | TCP | Conexões ao banco de dados MySQL. |
33061 | TCP | Comunicação interna do Group Replication. |
6446 | TCP | MySQL Router (opcional, ajuste conforme necessário). |
22 | TCP | SSH (opcional, para administração remota). |
Configuração do my.cnf
vi no menimo: /etc/my.cnf
MYorcl1
[mysqld]
server-id=1
log_bin=mysql-bin
binlog_checksum=NONE
gtid_mode=ON
enforce_gtid_consistency=ON
master_info_repository=TABLE
relay_log_info_repository=TABLE
transaction_write_set_extraction=XXHASH64
loose-group_replication_group_name="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
loose-group_replication_start_on_boot=OFF
loose-group_replication_local_address="192.168.10.101:33061"
loose-group_replication_group_seeds="192.168.10.101:33061,192.168.10.102:33061,192.168.10.103:33061"
loose-group_replication_bootstrap_group=OFF
bind-address=192.168.10.101
report_host=192.168.610.101
port=3306
myorcl2
[mysqld]
server-id=2
log_bin=mysql-bin
binlog_checksum=NONE
gtid_mode=ON
enforce_gtid_consistency=ON
master_info_repository=TABLE
relay_log_info_repository=TABLE
transaction_write_set_extraction=XXHASH64
loose-group_replication_group_name="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
loose-group_replication_start_on_boot=OFF
loose-group_replication_local_address="192.168.10.102:33061"
loose-group_replication_group_seeds="192.168.10.101:33061,192.168.10.102:33061,192.168.10.103:33061"
loose-group_replication_bootstrap_group=OFF
bind-address=192.168.10.102
report_host=192.168.610.102
port=3307
myorcl3
[mysqld]
server-id=3
log_bin=mysql-bin
binlog_checksum=NONE
gtid_mode=ON
enforce_gtid_consistency=ON
master_info_repository=TABLE
relay_log_info_repository=TABLE
transaction_write_set_extraction=XXHASH64
loose-group_replication_group_name="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
loose-group_replication_start_on_boot=OFF
loose-group_replication_local_address="192.168.10.103:33061"
loose-group_replication_group_seeds="192.168.10.101:33061,192.168.10.102:33061,192.168.10.103:33061"
loose-group_replication_bootstrap_group=OFF
bind-address=192.168.10.103
report_host=192.168.610.103
port=3308
myrouter
[mysqld]
server-id=4
bind-address=192.168.10.100
report_host=192.168.10.100
port=6446
Setup do Cluster via MySQL Shell
Exemplos de como logar no MySQL Shell:
mysqlsh --user=root --password --host=192.168.10.101 --port=3306 --js
mysqlsh --user=root --password --host=192.168.10.102 --port=3307 --js
mysqlsh --user=root --password --host=192.168.10.103 --port=3308 --js
No node myorcl1, vamos configurar a instancia:
mysql-js > dba.configureInstance();
Creating configuration for instance 'myorcl1'
Instance 'myorcl1' configured successfully.
Ainda no myorcl1, criamos o cluster:
mysql-js > var cluster = dba.createCluster("ClusterBrabo");
Creating cluster 'ClusterBrabo'...
Cluster 'ClusterBrabo' created successfully.
The cluster will be available once it is fully initialized.
Initializing cluster 'ClusterBrabo'...
Configuring instance 'myorcl1' for inclusion in the cluster...
Cluster 'ClusterBrabo' initialized and instance 'myorcl1' added successfully.
myorcl1 ainda, adicione os nodes myorcl2 e myorcl3 ao nosso cluster:
mysql-js > cluster.addInstance('root@192.168.10.102:3307');
Adding instance 'root@192.168.10.102' to cluster 'ClusterBrabo'...
Verifying the instance is running and accessible...
Verifying MySQL Group Replication status...
Configuring instance 'root@192.168.10.102' for Group Replication...
Adding instance 'root@192.168.10.102' to the cluster...
Instance 'root@192.168.10.102' added successfully to cluster 'ClusterBrabo'.
mysql-js > cluster.addInstance('root@192.168.10.103:3308');
Adding instance 'root@192.168.10.103' to cluster 'ClusterBrabo'...
Verifying the instance is running and accessible...
Verifying MySQL Group Replication status...
Configuring instance 'root@192.168.10.103' for Group Replication...
Adding instance 'root@192.168.10.103' to the cluster...
Instance 'root@192.168.10.103' added successfully to cluster 'ClusterBrabo'.
Vamos dar um check na bagaça:
mysql-js > cluster.status()
{
"clusterName": "ClusterBrabo",
"defaultReplicaSet": {
"name": "default",
"primary": "myorcl1:3306",
"ssl": "REQUIRED",
"status": "OK",
"statusText": "Cluster is ONLINE and can tolerate up to ONE failure.",
"topology": {
"myorcl1:3306": {
"address": "myorcl1:3306",
"memberRole": "PRIMARY",
"mode": "R/W",
"readReplicas": {},
"replicationLag": null,
"role": "HA",
"status": "ONLINE",
"version": "8.0.23"
},
"myorcl2:3307": {
"address": "myorcl2:3306",
"memberRole": "SECONDARY",
"mode": "R/O",
"readReplicas": {},
"replicationLag": null,
"role": "HA",
"status": "ONLINE",
"version": "8.0.23"
},
"myorcl3:3308": {
"address": "myorcl3:3306",
"memberRole": "SECONDARY",
"mode": "R/O",
"readReplicas": {},
"replicationLag": null,
"role": "HA",
"status": "ONLINE",
"version": "8.0.23"
}
},
"topologyMode": "Single-Primary"
},
"groupInformationSourceMember": "myorcl1:3306"
}
Setup do MySQL Router
O MySQL Router é uma ferramenta que atua como um intermediário entre os clientes (aplicações ou usuários) e o InnoDB Cluster. Ele facilita o roteamento de conexões para os nós corretos do cluster, dependendo do tipo de operação que você deseja realizar.
no myrouter:
wget https://dev.mysql.com/get/Downloads/Router/mysql-router_8.0.40-1_amd64.deb
sudo dpkg -i mysql-router_8.0.40-1_amd64.deb
sudo apt-get install -f
mysqlrouter --bootstrap root@192.168.10.100:3306 --directory /path/to/mysqlrouter/data
Agora, vamos configurar o MySQL Router:
sudo vi /etc/mysqlrouter/mysqlrouter.conf
[logger]
level = INFO
file = /var/log/mysqlrouter.log
[mysql-router]
user = mysql
socket = /var/lib/mysql/mysql.sock
connect_timeout = 10000
client_address = 0.0.0.0
client_port = 6446
[health]
enabled = true
check_interval = 10
onde:
- client_address: Define o IP ou hostname no qual o MySQL Router vai escutar.
- client_port: A porta na qual o Router vai escutar (ex.: 6446).
- connect_timeout: O tempo limite de conexão.
- read_timeout: O tempo limite de leitura.
Se liga nos exemplos de como conectar usando o router:
# Conexão de Leitura e Escrita (R/W)
# O MySQL Router redirecionará a conexão para o nó PRIMARY.
mysql -u root -p -h 192.168.61.100 -P 3306
# Conexão Somente Leitura (R/O)
# O MySQL Router redirecionará a conexão para um dos nós SECONDARY.
mysql -u root -p -h 192.168.61.100 -P 3307
# Verifica o nó ao qual você está conectado
SELECT @@hostname, @@port, @@server_id, @@read_only;
Teste de disponibilidade
Vamos agora parar o myorcl1 e ver o que acontece:
sudo systemctl stop mysql
Vamos dar um check agora:
mysql-js > cluster.status()
{
"clusterName": "ClusterBrabo",
"defaultReplicaSet": {
"name": "default",
"primary": "myorcl2:3306",
"ssl": "REQUIRED",
"status": "OK",
"statusText": "Cluster is ONLINE but cannot tolerate further failures.",
"topology": {
"myorcl1:3306": {
"address": "myorcl1:3306",
"memberRole": "UNREACHABLE",
"mode": "R/W",
"readReplicas": {},
"replicationLag": null,
"role": "HA",
"status": "OFFLINE",
"version": "8.0.23"
},
"myorcl2:3307": {
"address": "myorcl2:3306",
"memberRole": "PRIMARY",
"mode": "R/W",
"readReplicas": {},
"replicationLag": null,
"role": "HA",
"status": "ONLINE",
"version": "8.0.23"
},
"myorcl3:3308": {
"address": "myorcl3:3306",
"memberRole": "SECONDARY",
"mode": "R/O",
"readReplicas": {},
"replicationLag": null,
"role": "HA",
"status": "ONLINE",
"version": "8.0.23"
}
},
"topologyMode": "Single-Primary"
},
"groupInformationSourceMember": "myorcl2:3306"
}
Legal, agora damos um start novamente:
sudo systemctl start mysql
Damos então um rejoin (assim que vi por ai kkkkk):
mysql-js > cluster.rejoinInstance('root@myorcl1:3306')
eeeeeee… ta ai:
mysql-js > cluster.status()
{
"clusterName": "ClusterBrabo",
"defaultReplicaSet": {
"name": "default",
"primary": "myorcl2:3306",
"ssl": "REQUIRED",
"status": "OK",
"statusText": "Cluster is ONLINE and can tolerate up to ONE failure.",
"topology": {
"myorcl1:3306": {
"address": "myorcl1:3306",
"memberRole": "SECONDARY",
"mode": "R/O",
"readReplicas": {},
"replicationLag": null,
"role": "HA",
"status": "ONLINE",
"version": "8.0.23"
},
"myorcl2:3307": {
"address": "myorcl2:3306",
"memberRole": "PRIMARY",
"mode": "R/W",
"readReplicas": {},
"replicationLag": null,
"role": "HA",
"status": "ONLINE",
"version": "8.0.23"
},
"myorcl3:3308": {
"address": "myorcl3:3306",
"memberRole": "SECONDARY",
"mode": "R/O",
"readReplicas": {},
"replicationLag": null,
"role": "HA",
"status": "ONLINE",
"version": "8.0.23"
}
},
"topologyMode": "Single-Primary"
},
"groupInformationSourceMember": "myorcl2:3306"
}
Ain, mas como sei quantas falhas meu cluster tolera? poxa, tem na literarua ramelão, se liga:
Existe uma formula para determinar quantas falhas o seu cluster pode tolerar, onde: S = Server, f = numero de falhas +1, então:
S = 2f + 1
Por exemplo (tirado da literatura, de novo)
Se você tem um cluster com 7 nodes, a tolerancia a falhas é de até 3 🙃
7 = 2f + 1
6 = 2f
2f = 6
f = 6 / 2
f = 3
Agora algo mais proximo do nosso mundo (de pobre kkk), 3 servers:
F = (3 – 1)/2
F = 2 / 2
F = 1
Então é isso, existe também a possobilidade de antes de você se matar em laboratorios ou ir pra produção com duvidas, você pode utilizar o MySQL InnoDB Cluster SandBox que é bem legal de brincar e entender um pouco do InnoDB Cluster.
Te um script doido que rola para criar um sandbox facil facil:
# Introducing InnoDB Cluster
#
# This Python script is designed to set up an InnoDB Cluster in a sandbox.
#
# Note: Change the sandbox directory to match your preferred directory setup.
#
# The steps include:
# 1) Create the sandbox directory
# 2) Deploy instances
# 3) Create the cluster
# 4) Add instances to the cluster
# 5) Show the cluster status
#
# Updated for modern MySQL Shell versions
# Dr. Charles Bell, 2024 (Updated by Assistant)
import os
import time
from mysqlsh import dba, shell # Import MySQL Shell modules
# Method to deploy a sandbox instance
def deploy_instance(port):
try:
dba.deploy_sandbox_instance(
port,
{
'sandboxDir': '/home/user/idc_sandbox', # Adjusted for Linux/macOS
'password': 'root'
}
)
print(f"Instance deployed on port {port}")
except Exception as e:
print(f"ERROR: Cannot set up the instance in the sandbox on port {port}. Error: {e}")
time.sleep(1)
# Method to add an instance to the cluster
def add_instance(cluster, port):
try:
cluster.add_instance(
f'root@localhost:{port}',
{
'password': 'root',
'recoveryMethod': 'clone' # Use 'clone' for modern MySQL versions
}
)
print(f"Instance on port {port} added to the cluster.")
except Exception as e:
print(f"ERROR: Cannot add instance on port {port} to the cluster. Error: {e}")
time.sleep(1)
# Main script
if __name__ == "__main__":
print("##### STEP 1 of 5 : CREATE SANDBOX DIRECTORY #####")
sandbox_dir = '/home/user/idc_sandbox' # Adjusted for Linux/macOS
if not os.path.exists(sandbox_dir):
os.mkdir(sandbox_dir)
print(f"Sandbox directory created at {sandbox_dir}")
else:
print(f"Sandbox directory already exists at {sandbox_dir}")
print("##### STEP 2 of 5 : DEPLOY INSTANCES #####")
deploy_instance(3311)
deploy_instance(3312)
deploy_instance(3313)
deploy_instance(3314)
print("##### STEP 3 of 5 : CREATE CLUSTER #####")
try:
shell.connect('root@localhost:3311', {'password': 'root'})
my_cluster = dba.create_cluster(
'MyCluster',
{
'multiPrimary': False # Updated parameter name for single-primary mode
}
)
print("Cluster 'MyCluster' created successfully.")
except Exception as e:
print(f"ERROR: Cannot create the cluster. Error: {e}")
time.sleep(1)
print("##### STEP 4 of 5 : ADD INSTANCES TO CLUSTER #####")
add_instance(my_cluster, 3312)
add_instance(my_cluster, 3313)
add_instance(my_cluster, 3314)
print("##### STEP 5 of 5 : SHOW CLUSTER STATUS #####")
try:
shell.connect('root@localhost:3311', {'password': 'root'})
my_cluster = dba.get_cluster('MyCluster')
status = my_cluster.status()
print("Cluster Status:")
print(status)
except Exception as e:
print(f"ERROR: Cannot retrieve cluster status. Error: {e}")
Ah, tinha esquecido dos comandinhos para administrar e monitorar o cluster:
// Verificar o status do cluster
cluster.status()
// Verificar o status detalhado de cada nó
cluster.status({extended: true})
// Adicionar um novo nó ao cluster
// Substitua 'senha' pela senha correta do usuário root
cluster.addInstance('root@myorcl4:3306', {password: 'senha'})
// Remover um nó do cluster
// Exemplo: Remover o nó myorcl3:3306
cluster.removeInstance('root@myorcl3:3306', {password: 'senha'})
// Promover um nó secundário a primário (failover manual)
// Exemplo: Promover o nó myorcl3:3306 a primário
cluster.setPrimaryInstance('myorcl3:3306')
// Alterar o modo de topologia para Multi-Primary
cluster.switchToMultiPrimaryMode()
// Alterar o modo de topologia para Single-Primary
cluster.switchToSinglePrimaryMode()
// Reconfigurar o cluster para reintegrar um nó
// Exemplo: Reintegrar o nó myorcl3:3306 ao cluster
cluster.rejoinInstance('myorcl3:3306')
// Remover o cluster completamente
// Nome do cluster: ClusterBrabo
dba.dropCluster('ClusterBrabo')
// Fazer backup de uma instância
// Exemplo: Fazer backup do nó primário myorcl2:3306
util.dumpInstance('root@myorcl2:3306', {password: 'senha', outputUrl: 'file:///backups/cluster_backup'})
// Verificar a configuração do cluster
cluster.describe()
// Atualizar a versão do cluster após atualizar o MySQL
cluster.upgradeMetadata()
// Listar todos os clusters disponíveis no ambiente
dba.getClusters()
// Verificar os nós disponíveis para serem adicionados ao cluster
// Isso ajuda a identificar instâncias MySQL que podem ser configuradas como parte do cluster
dba.checkInstanceConfiguration('root@myorcl4:3306', {password: 'senha'})
// Configurar uma instância MySQL para ser usada no cluster
// Use este comando antes de adicionar uma instância ao cluster
dba.configureInstance('root@myorcl4:3306', {password: 'senha'})
// Verificar a configuração de uma instância específica
// Isso ajuda a diagnosticar problemas de configuração em um nó
dba.checkInstanceConfiguration('root@myorcl3:3306', {password: 'senha'})
// Verificar a replicação entre os nós do cluster
// Isso exibe informações sobre o atraso de replicação e o status de cada nó
cluster.checkInstanceState('myorcl3:3306')
// Alterar o modo de recuperação automática de um nó
// Exemplo: Configurar o nó myorcl3:3306 para tentar se reconectar automaticamente ao cluster
cluster.setInstanceOption('myorcl3:3306', 'autoRejoinTries', 3)
// Alterar o tempo limite de espera para operações de cluster
// Exemplo: Configurar o tempo limite para 60 segundos
cluster.setOption('operationTimeout', 60)
// Verificar as opções configuradas no cluster
cluster.options()
// Verificar as opções configuradas para um nó específico
cluster.getInstanceOptions('myorcl3:3306')
// Forçar a remoção de um nó do cluster
// Use este comando se o nó não puder ser removido normalmente
cluster.forceQuorumUsingPartitionOf('myorcl2:3306')
// Recuperar o quorum do cluster manualmente
// Use este comando se o cluster perder o quorum e precisar ser restaurado
cluster.forceQuorumUsingPartitionOf('myorcl2:3306')
// Verificar o quorum do cluster
cluster.status().defaultReplicaSet.quorumStatus
// Reconfigurar o cluster para tolerar falhas adicionais
// Exemplo: Configurar o cluster para tolerar até 2 falhas
cluster.setOption('memberWeight', 2)
// Atualizar a configuração de replicação do cluster
// Use este comando após alterações na configuração de rede ou replicação
cluster.rescan()
// Verificar o histórico de eventos do cluster
// Isso exibe eventos importantes, como falhas de nós ou mudanças de estado
cluster.status({extended: true}).defaultReplicaSet.events
// Verificar o atraso de replicação de um nó específico
// Isso ajuda a identificar problemas de desempenho na replicação
cluster.status({extended: true}).defaultReplicaSet.topology['myorcl3:3306'].replicationLag
// Verificar o papel de cada nó no cluster
// Isso exibe se o nó é primário ou secundário
cluster.status().defaultReplicaSet.topology['myorcl3:3306'].memberRole
// Verificar o modo de operação de cada nó
// Isso exibe se o nó está em modo de leitura/escrita (R/W) ou somente leitura (R/O)
cluster.status().defaultReplicaSet.topology['myorcl3:3306'].mode
E ainda temos nosso melhor amigo para monitorar MySQL, o Dolphie:

Referencias para estudos

Espero que tenham curtido, se tiverem algum sugestão manda bala, posta ai nos comentarios e nos vemos ou nos falamos por ai. 🤘🏾🤘🏾