Oct 08 06

Tutorial para hacer un balanceador de carga (load balancer) de servidores de base de datos con PHP

Tiempo estimado de lectura: 3,18 minutos

Load balancer in PHPPor lo general, nuestras aplicaciones web necesitan ser escalables especialmente en las consultas a las bases de datos. Esto es extremadamente necesario cuando no se dispone de un cluster y el tráfico puede comprometer la performance de un sólo servidor de base de datos.

En este tutorial voy a detallar los pasos necesarios para hacer un sencillo load balancer de mysql a alto nivel utilizando PHP. Para lograr esto, es requerido que los dos o más servidores de bases de datos tengan exactamente la misma información. En MySQL, por ejemplo, pueden lograr esto utilizando la replicación que ofrece el motor de base de datos.

Para comenzar, imaginemos el posible escenario mínimo para setear nuestro balanceador de carga.

Un caso particularmente popular es tener, en el mismo equipo, el servidor web y el servidor de base de datos. Es ahí cuando nuestra infraestructura puede quedar chica y necesitamos otro servidor para balancear la carga. Para entonces podemos agregar otro servidor que replique la información del primero para tener así una réplica exacta de nuestros datos.

Ilustrando el concepto

El concepto es muy simple. Para que el balanceador de carga funcione como tal, es necesario conocer el load average de cada uno de los servidores. Luego, basándonos es ese dato, podemos decidir dinámicamente que servidor usar para establecer la conexión.

Manos a la obra…

Voy a tratar de simplificar los pasos y enumerarlos para que resulten más fácil de entender. pero repito es muy simple.

1) Crear spool de conexión a los servidores

Este paso es el más simple de todos, se trata de generar un spool de conexión (un lugar en donde poner los datos de conexión), el mismo tiene que ser escalable y permitir agregar servidores de forma sencilla. Hay varias formas: un array, archivos de texto, o cualquier otra cosa que permita agregar la información de la conexión para su posterior lectura.

Para mi, la mejor forma de lograr esto es a través de archivos .ini ya que php ofrece una forma natural de parseo de estos archivos.

Entonces, recomiendo tener una carpeta que tenga un archivo .ini por defecto con la información de conexión a la base de datos local y otro .ini por cada servidor extra disponible.

Con esto, se puede disponer dinámicamente todas las conexiones necesarias.

Ejemplo:

/dbconf/default.ini
/dbconf/1_server.ini
/dbconf/2_server.ini

Cada uno de estos .ini pueden ser algo como:
[192.168.0.111]
host= server1.host.com
username= johndoe
password= j0hnnyD0e
dbname= myapp_db_name

Entonces con sólo agregar un nuevo archivo a la carpeta, ya es incluido dinámicamente en la ronda de servers

2) Averiguar la carga de cada uno de los servidores

Una vez que ya tenemos un lugar con la información a los servidores, lo siguiente es averiguar dónde conviene conectarse.

Este paso requiere comunicación con el sistema operativo, por ende, este paso será distinto para cada SO. Por el momento lo implementé con éxito en la plataforma LAMP por lo que me centraré en Linux.

Para lograr esto, hay que utilizar un comando en linux que devuelva el load average del server. Hay varias formas, esta es una de ellas:

uptime | gawk 'BEGIN { FS = "load average:"};{print substr($2,0,match($2,",") - 1 );}'

Este comando, combinado con la función exec(), obtiene la carga actual del servidor.

Esto debe ser realizada localmente en cada uno de los servers, asi que tendrán que tener un .php en cada server para que sea llamado desde el servidor web donde está la aplicación.

Como no es necesario revisar la carga de todos los servidores siempre, se puede armar una secuencia de chequeo dependiendo nuestra necesidad.

Entonces por ejemplo:

1) Si el server local tiene un load menor a 2 segundos, no es necesario revisar los otros servidores, sabemos que este responderá correctamente.

2) En caso que el load del servidor local supere los 2 segundos, se revisa el segundo servidor y si es menor a 2 se usa, sino se sigue buscando en el siguiente.

3) Si se da que ninguno de los servers tiene menos de 3 segundos de load, se puede usar el que menor carga tenga.

3) Generar la capa de conexión

Como dijimos, tenemos un spool de conexión a servidores representados por una carpeta con archivos .ini. Entonces una vez que detectamos el servidor a utilizar, buscamos la información de conexión correspondiente a ese archivo simplemente utilizando la función ini_parse_file y creamos la conexión que será utilizada por la aplicación.

4) Listo, ¡a probarlo!

Al finalizar estos pasos tendremos un simple script que decida a qué servidor conectarse, dependiendo la carga que tengan. Para probarlo, pueden cambiar los parámetros del script para que devuelva directamente los datos de un archivo u otro para corroborar que todo ande sobre ruedas.

Ojalá les resulte tan útil como a mi.

Copame! Digg it Digg it Reddit Del.icio.us

    6 Comentarios

  1. Matías


    No es por pincharte el globo o similar pero hay un serio problema con este método. Si los primeros servidores están muy congestionados y/o no disponibles, de tal forma que la lectura del la carga que se hace via web tarda 1 ó 2 segundos, tenés esa demora en redireccionar el tráfico correspondiente.

    Como si fuese poco, también implica tener un servidor web andando en los servidores de base de datos, cosa que no sólo que no es recomendable sino que a veces no es posible tampoco.

    Agregale el hecho que eso no distribuye la carga equitativamente tampoco sino que atora el primer servidor antes de pasar al segundo.

    Lo que yo haría sería usar alguna solución específica para eso:
    MySQL proxy: http://dev.mysql.com/doc/mysql-ha-scalability/en/mysql-proxy.html
    MySQL Load Balancer: http://dev.mysql.com/doc/mysql-ha-scalability/en/load-balancer.html

    Si no es posible, tendría que emularlo de alguna forma desarrollando un modelo cliente-servidor en el que el servidor recibe de parte de los clientes updates con la carga de cada uno (via SNMP o similar) y luego la aplicación PHP tiene una forma para obtener el servidor con menos carga (podría ser que el servidor actualice una mini base de datos que el script en PHP lea para decdir).

  2. Lucas Zallio


    Matías, tu punto es muy bueno, gracias por participar. Sin embargo, el chequeo en el servidor cargado es sólo un “if” ya que el resto se hace en los demás servidores. Y si bien puede llevar su tiempo, es siempre menor a la respuesta de un único servidor.

    Probe hacer también lo que decis sobre el modelo cliente-servidor para llevar un log local del load de los servers; sin embargo tardaba más en responder la “mini-db” con los loads que un request http de los otros servers.

    El problema principal del load balancer MySql es que no es posible modificar las bases de datos, es sólo lectura (al menos por ahora). Con esta solución se puede modificar en todas o algunas más allá de la carga del server. No probé lo del proxy, pero supongo que debe funcionar de manera similar.

    Siempre es bienvenida la review de gente grosa en el tema, gracias de nuevo!

  3. Matías


    En realidad el chequeo en el servidor cargado es el correr el script php y responder el request PHP. Además, si no tenés un servidor de proxy en el medio, el primero a probar es sí mismo que ya viene cargado de por sí. Por lo que no estás haciendo balanceo de carga sino más bien un failover; balanceo de carga sería si tratás que todos estén más o menos equitativamente cargados.

    En cuanto a lo del proxy de lectura, es lo ideal. Por lo general vas a tener un sistema de bases de datos armados con replicación. Ergo, tenés un master y esclavos que son copias de ese master. Por lo tanto, las conexiones de escritura se tienen que hacer siempre en la misma base de datos o quedás con inconsistencias.

    Para algo más ad-hoc tal vez quieras revisar lo que es DB Sharding. Esto es, repartir una sola base de datos en varios servidores y, según a qué parte quieras acceder, es el servidor al que te tenés que conectar. Toda la administración de conexiones para que ande todo se hace también con algo así como un proxy.

  4. Lucas Zallio


    Matías, Quizá al principio no se comporte exactamente como un balanceador, pero llegado un momento, todos tendrán una carga similar ya que cuando estén todos por sobre el nivel mímino aceptable, se elegirá el menos cargado y así sucesivamente.

    En vez del proxy, lo evitamos modificando las N bases de datos al mismo tiempo cuando es necesario. Nuestra situación es un tanto especial porque las dos bases en las que, por ahora, implementamos esto, son ambas esclavas de otra tercera que jamás participa del chequeo.

    Al principio hacíamos algo así como el DB Sharding, pero no resultó porque no teníamos idea de cuando ni qué sección se utilizaría. Y estábamos como al principio.

    Por ahora esto nos resultó. Mantiene una carga pareja entre los dos servidores de bases de datos y, si bien aveces se frena un poco cuando tiene que realizar mas de un chequeo, sigue siendo más rápido que las opciones que probamos anteriormente.

    Estos comentarios dan para otro post completo XD. Thanks again.

  5. hola, muy buen articulo


    hola, felicitarte por la explicacion es muy buena, tanto la replicacion como el failover. Entender lo entiendo, pero a la hora de implementar es mas dificil podrias especificarme algunas cosillas?

    como por ejemplo el script como seria
    o donde se ejecuta el script cuando llega la pregunta de internet?, en el index del servidor inicial?

  6. Martin


    Mis felicitaciones por el articulo, Muy completo, ahora mismo ando trabajando en balancear la carga de mySQL con otro server, pero las conexiones van lentisimas, aun no se porque va tan lento, tenes alguna idea de que puede ser ?

Deja un Comentario

Tags permitidos:

  • <a href="" title="">
  • <abbr title="">
  • <acronym title="">
  • <blockquote cite="">
  • <code>
  • <em>
  • <strong>