Archivo | replicacion RSS feed for this section

Conectarse a varios esclavos de MySQL con Ruby

15 sep

Vamos a ver como nos conectamos a varios esclavos de MySQL que replican de un maestro. Como comentamos en el capitulo anterior, la replicación  de MySQL nos permite aumentar la capacidad de lecturas de nuestra base de datos.

Ahora vamos a estudiar la manera en la que podemos conectar nuestro programa de Ruby a todos esos esclavos. La mayoría de las gemas que nos encontramos soportan múltiples esclavos. Pero la manera en la que se conectan es muy simple, y si nos encontramos en una situación de mucha carga, podemos llegar a tumbar toda la base de datos. Veamos el ejemplo de la gema Sequel.

Para configurar múltiples esclavos, tenemos que configurarla de la siguiente manera (codigo ejemplo de su documentación):

DB=Sequel.connect('mysql://master_server/database', \
    :servers=>{:read_only=>proc{|db| {:host=>db.get_slave_host}}})
  def DB.get_slave_host
    @current_host ||= -1
    "slave_server#{(@current_host+=1)%4}"
  end

Siendo master_server nuestro servidor maestro y slave_server#{(@current_host+=1)%4} nuestros esclavos.

Vamos a hacerlo más aleatorio:

DB=Sequel.connect('mysql://maestro/my_db', \
    :servers=>{:read_only=>proc{|db| {:host=>db.get_slave_host}}})

  def DB.get_slave_host
    slaves = [
      "esclavo_1",
      "esclavo_2",
      "esclavo_3",
      "esclavo_4",
      "esclavo_5",
      "esclavo_6"
    ]
    return slaves[rand(slaves.length - 1)]
  end

Ahora bien, viendo este código, podemos ver ya que la distribución de las conexiones va a ser equitativa, es decir, si tenemos 4 esclavos, cada uno de ellos va a recibir el mismo número de conexiones. Esto en un mundo ideal nos viene bien, pero no nos viene tan bien cuando tengamos alguno de los esclavos con más carga que los demás…

Vamos a darle una vuelta de tuerca. Vamos a tratar de comprobar el estado de los servidores antes de conectarnos, para saber si realmente queremos conectarnos a dicho servidor, o no…

DB=Sequel.connect("mysql://maestro/#{db_name}", \
    :servers=>{:read_only=>proc{|db| {:host=>db.get_slave_host}}})

  def DB.slaves_array
    slaves = [
      "esclavo_1",
      "esclavo_2",
      "esclavo_3",
      "esclavo_4",
      "esclavo_5",
      "esclavo_6"
    ]
    return slaves
  end

  def DB.get_slave_host
    tries = 0
    while tries <= DB.slaves_array.length
      slave = DB.get_slaves_array[rand(DB.get_slaves_array.length - 1)]
      return slave if DB.get_slave_delay <= 1
      tries += 1
    end
  end

  def DB.get_slave_delay(slave, db_name)
    begin
      DBSlave=Sequel.connect("mysql://#{slave}/#{db_name}")
      delay = DBSlave["SHOW SLAVE STATUS"]
      return delay[32].to_i
    rescue
      return 1000
    end
  end

Parte por parte.

Lo que este código hace es simplemente  comprobar si el esclavo al que nos vamos a conectar esta sin retrasos con respecto al maestro y ademas si nos podemos conectar. Para ello, cuando Sequel llame a db.get_slave_host lo que vamos a hacer es lo siguiente:

def DB.get_slave_host
    tries = 0
    while tries <= DB.slaves_array.length
      slave = DB.get_slaves_array[rand(DB.get_slaves_array.length - 1)]
      return slave if DB.get_slave_delay <= 1
      tries += 1
    end
  end

Vamos a sacar un esclavo de la lista de manera aleatoria:

slave = DB.get_slaves_array[rand(DB.get_slaves_array.length - 1)]

Y posteriormente vamos a tratar de conectarnos al esclavo y mirar su estado:

return slave if DB.get_slave_delay <= 1

Esto llama a la función que hemos definido:

def DB.get_slave_delay(slave, db_name)
    begin
      DBSlave=Sequel.connect("mysql://#{slave}/#{db_name}")
      delay = DBSlave["SHOW SLAVE STATUS"]
      return delay[32].to_i
    rescue
      return 1000
    end
  end

Esta función va a comprobar el estado del servidor. Si el esclavo tuviera algún problema, devolvería un retraso elevado (cuando los esclavos tienen mucha carga, suelen retrasarse con respecto a su maestro) o si no podemos conectar, entonces devolverá 1000, dando así un retraso muy elevado indicando que el servidor no esta disponible.

Nota: para que nuestro código pueda preguntar por el estado de los esclavos (“SHOW SLAVE STATUS”) necesitamos dar privilegios SUPER, REPLICATION CLIENT al usuario que hemos designado a nuestro programa para conectarse a los esclavos:

mysql> GRANT SUPER, REPLICATION CLIENT ON *.* TO 'user'@'domain' IDENTIFIED BY 'password'

En conclusión, si queremos que nuestro código soporte varios servidores esclavos y que compruebe la carga antes de cada conexión, debemos complicar la lógica de conexión a la hora de conectarnos a la base de datos. Las gemas como Sequel nos pueden ayudar a ello con abstracciones de conexión. Es muy sencillo hacer esta comprobación cada vez que nos conectamos. Pero como en todo, este método de comprobación no es escalable a grandes sistemas dado que cada vez que vamos a realizar una lectura, primero hacemos una conexión al esclavo para mirar su estado. Esto hace que la cantidad de conexiones se multiplique por dos.
Mi recomendación es que se trate de hacer un método híbrido donde en paralelo a este código, se compruebe el estado de los esclavos cada vez, y se modifiquen los pesos de cada esclavo dependiendo de dichas comprobaciones. Pero esto corresponde al siguiente post…

Cómo iniciar una replicación Maestro – Esclavo en MySQL >5.0

3 dic

La replicación en MySQL funciona de una manera muy sencilla. Existe un maestro, que es el encargado de propagar los cambios y los esclavos, que son los que reciben estos cambios a fin de tener una copia exacta de lo que tiene el maestro.

Para que esto suceda, los esclavos tienen una conexión abierta contínua con el maestro para recibir estos cambios. Con esta conexión se comprueba desde el esclavo la última posición escrita en el log binario del maestro -eso es, se replican por medio del log binario. Cuando sucede algún cambio en el log binario del maestro, se remiten estos cambios a los esclavos, que posteriormente ejecutan estos cambios en sus tabla.s

Para empezar una replicación maestro esclavo sencilla hay que tener como mínimo dos instancias del servidor MySQL, una que haga de maestro y otra que haga de esclavo.

En el maestro tienen que estar las siguientes líneas en el fichero de configuración [my.cnf]:

log-bin=mysql-bin
server-id = 1

Por otro lado, en el esclavo tendremos que tener las siguientes lineas:

server-id = 2

Una vez configurados los servidores, podemos arrancarlos -obviamente aún no van a replicar porque no les hemos indicado que empiecen a replicar.

Cuando estén arrancados, en el maestro creamos o instalamos las tablas y la base de datos que queremos replicar. Cuando estén creadas y hayamos insertado los datos necesarios dentro de las tablas entonces tendremos que hacer un volcado de la base de datos a un archivo de texto. La razón de esto es que vamos a instalar esta misma base de datos en el esclavo. Para ello lo podremos hacer así:

mysql> GRANT REPLICATION PRIVILEGES ON *.bbdd TO 'usuario_replicacion'@'192.168.0.3' IDENTIFIED BY 'pass_replicacion';
mysql> flush privileges;
mysql> LOCK ALL TABLES READ ONLY
mysql> exit

Ahora desde la línea de comandos sacamos un DUMP del maestro:

joax@maestro> mysqldump bbdd > bbdd_dump_20071202.sql

Una vez terminado el proceso, tenemos que recordar la situación del log binario en el master:

mysql> SHOW MASTER STATUS;
+------------------+----------+--------------+------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000005 | 98 | | |
+------------------+----------+--------------+------------------+
1 row in set (0.00 sec)

Con esto ya podemos desbloquear las tablas en el maestro:

mysql> UNLOCK ALL TABLES;

Y con ello, en el esclavo ahora ejecutamos lo siguiente:

joax@esclavo> mysql bbdd < bbdd_dump_20071202.sql

Y una vez que el esclavo tenga generada la base de datos, lo configuramos para que esté posicionado con respecto al maestro:

mysql> CHANGE MASTER TO MASTER_HOST = '192.168.0.2';
mysql> CHANGE MASTER TO MASTER_USER = 'usuario_replicacion';
mysql> CHANGE MASTER TO MASTER_PASSWORD = 'pass_replicacion';
mysql> CHANGE MASTER TO MASTER_LOG_FILE = 'mysql-bin.000005';
mysql> CHANGE MASTER TO MASTER_LOG_POS = 98;
mysql> start slave;

Ahora el esclavo comenzará la replicación con el maestro. Quedándose a 0 segundos de él una vez copiado todo el log binario que le resta de lo sucedido hasta la fecha. Para comprobar que realmente está replicando, tendremos que ejecutar el siguiente comando en la consola de MySQL:

mysql> show slave status \G

Eso muestra:

*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.0.2
Master_User: usuario_replicacion
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000005
Read_Master_Log_Pos: 98
Relay_Log_File: esclavo-relay-bin.000001
Relay_Log_Pos: 98
Relay_Master_Log_File: mysql-bin.000005
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB: bbdd
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 98
Relay_Log_Space: 98
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: 0
1 row in set (0.00 sec)

Lo importante es lo que sigue a Seconds_Behind_Master: porque es el valor que indica el retraso de este esclavo con respecto al maestro.

En definitiva y a modo de resumen:

1. Cambiar la configuración del maestro con las líneas:

server-id=1
log-bin=mysql-bin

2. Cambiar la configuración del esclavo:

server-id=2

3. Bloquear las tablas del maestro y hacer un dump:

mysql> LOCK ALL TABLES READ ONLY
mysql> SHOW MASTER STATUS
mysql> exit
joax@maestro> mysqldump bbdd > bbdd_dump_20071202.sql

4. Instalar la base de datos en el esclavo:

joax@esclavo> mysql bbdd < bbdd_dump_20071202.sql
mysql> CHANGE MASTER TO MASTER_HOST = '192.168.0.2';
mysql> CHANGE MASTER TO MASTER_USER = 'usuario_replicacion';
mysql> CHANGE MASTER TO MASTER_PASSWORD = 'pass_replicacion';
mysql> CHANGE MASTER TO MASTER_LOG_FILE = 'mysql-bin.000005';
mysql> CHANGE MASTER TO MASTER_LOG_POS = 98;
mysql> start slave;

En un cluster podremos incluir tantos esclavos como necesitemos. He llegado a ver clusteres de varias decenas de esclavos sobre el mismo maestro. Pero son puntos donde a lo mejor tienes que tomar una estrategia de particionado de la base de datos. Estrategia que veremos próximamente.

Seguir

Recibe cada nueva publicación en tu buzón de correo electrónico.

Únete a otros 1.232 seguidores