Category Archives: Trabajo

Notificaciones en Tiempo Real – Pusher

Las últimas aplicaciones web utilizan mucho notificaciones en tiempo real; desde chat (por ejemplo Facebook Chat), notificaciones de nuevos elementos (Twitter), actualizaciones de progreso (por ejemplo procesando un archivo).

La primera aproximación fue utilizar un poll en ajax (solicitar datos de manera periódica); pero no puede escalar fácilmente (una mala implementación puede hacer que aplicación sufra un DDos) y tendrás retrasos en tus actualizaciones.

Del lado del servidor hay muchas librerías/servidores con soluciones para esto. La buena noticia es que WebSockets será el estándar y la solución para notificaciones de tiempo real tanto en navegador como en servidor. La mala noticia es que el estándar de WebSocket está en progreso y cambia mucho entre versiones, por lo que no es estable entre los principales navegadores (Firefox, Safari y Chrome lo implementan pero con diferentes versiones)

Una buena opción son las implementaciones en la nube que eliminan todos los problemas de programación y mantenimiento de servidores para esta tarea.

Hasta el momento he probado dos implementaciones: Beaconpush (que me gustaba para implementaciones rápidas y que tenían pocos mensajes que enviar; pero ahora está en proceso de eliminar esta característica y dejar solo un versión para instalar) y Pusher

Pusher.com es una grandiosa solución para notificaciones en tiempo real. El equipo responde rápido y aporta ayuda todo el tiempo.

Detallaré algunas ventajas y problemas que puedes encontrar si necesitas escogerlo.

Ventajas:

  • Barato, especialmente si vas a mandar muchos mensajes
  • Fácil de implementar en servidor y en el navegador
  • Utiliza websockets en los principales navegadores (usa por ejemplo la reciente implementación de Mozilla y nunca tuve un problema de conexión en versiones diferentes de Safari/Chrome)
  • Tiene mecanismos de autenticación que te permiten asegurar tus mensajes (En Beaconpush era fácil recibir mensajes de otros si no escondías el id del usuario)
  • Tiene canales de presencia (presence channels) que significa que puedes ver a los otros usuarios conectados sin implementarlo en tu servidor
  • Puedes almacenar datos cuando el usuario es autorizado(por ejemplo datos rendereados en HTML), por lo que puedes usarlo para mostrar datos en tu navegador
  • Webhooks que te permiten saber cuando tu canal está ocupado o vacío (en línea/desconectado si utilizas un “canal de usuario”)

Problemas

  • No estoy seguro si realmente limitan el número de conexiones, porque si tienes un sitio grande con pocas notificaciones puedes fácilmente llegar al límite de conexiones (Además que pienso que son realmente límites muy bajos)
  • Todos son canales, así que no puedes manejar id de usuarios, la solución es fácil si creas “canales de usuarios” (canales privados solo para el usuario y manejas la autorización)
  • No tienen un API para enviar un mensaje a múltiples canales (o destinatarios)
  • Del lado del servidor no puedes obtener una lista de usuarios en un canal (Aunque sería difícil de procesar porque no tienen id de usuarios sino un socket_id)
  • Los Webhooks son geniales para páginas ajax, pero no funcionarán fácilmente si tu usuario navega varias páginas, ya que obtendrás muchas notificaciones de conectado/desconectado (tienen una solución para el navegador, pero en servidor necesitas tu propia solución
  • En mis pruebas, si utilizaba la configuración por defecto tenía un retraso en la conexión de hasta 10 segundos para iniciar; si forzaba a utilizar socket seguros, funcionaba sin problemas:
    new Pusher(applicationKey, {encrypted: true});

Pienso que Pusher es una excelente solución de sitios pequeños a medianos; debido a que te permite hacer un desarrollo rápido y las notificaciones en tiempo real tienen un gran impacto en tu aplicación.

“Permission Denied” en un cliente NFS en Debian Unstable

Cuando intentaba acceder a una partición NFS de un servidor in lenny desde un cliente en squeeze, obtuve varios “Permission Denied” (Permiso Denegado) al intentar escribir un archivo. Como existen otros clientes NFS que pueden escribir al servidor, no se trataba de configuración sino más bien de la versión del nuevo cliente.

Después de buscar un poco, encontré este bug:
http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=492970

Si quieres saber si tu versión tiene este problema, puedes verificarlo con el comando: “cat /proc/mounts” notarás que el NFS tiene un parámetro “sec=null” y es lo que genera el problema.

El arreglo es un fácil:

  1. Desmontar todas las particiones NFS (muy importante, en mi caso tenía dos particiones y solo desmonté una e intenté pasar la nueva configuración que no funcionó hasta que desmonté las dos particiones)
  2. En /etc/fstab (o al montarlo manualmente)  pasar el parámetro sec=sys para todas las particiones de ese servidor, por ejemplo para fstab:
    example.com:/shared /tmp/shared nfs rw,sec=sys 0 0
    O de forma manual
    mount -t nfs -o "rw,sec=sys" example.com:/shared /tmp/shared
  3. Montar todas las particiones de ese servidor

Al verificar nuevamente /proc/mounts verás el parámetro sec=sys en lugar de null

Mantener cambios en branches de SVN

Cuando se desarrolla en un branch de SVN el mayor problema es mantener actualizados los archivos que estamos modificando con los cambios que se hacen en la rama principal (trunk) de SVN. Para hacer más fácil esto, he creado un script que checa los archivos cambiados en nuestro branch y checa con los archivos modificados del principal, la manera de invocarlo es simple:

php changes-branch.php BASE_SVN PROYECTO BRANCH

Así por ejemplo si nuestro svn está en: http://svn.example.com/proyecto y nuestro branch es: http://svn.example.com/proyecto/branches/cambios debemos invocar el programa como:

php changes.branch.php http://svn.example.com proyecto cambios

El sistema mostrará cuales archivos han tenido cambios desde nuestra última actualización de nuestro branch, así como la salida del diff para ver los cambios.

El script también tiene una opción que les permite ver todos los cambios desde que se inició el branch, simplemente agregar un cuarto parámetro, por ejemplo:

php changes-branch.php BASE_SVN PROYECTO BRANCH true

El código completo está aqui: https://github.com/danguer/blog-examples/blob/master/php/svn/changes-branch.php

Cuidado al usar Zend_Date

En un proyecto, estaba utilizando Zend_Date para manejo de horas en diferentes zonas horarias, cuando lo estaba desarrollando empecé a tener un error porque olvidé que la fecha se actualiza cuando cambias un parámetro y el problema surge cuando cambias el mes o día y se encuentra con una fecha inexistente aunque en el código parezca correcta, por ejemplo, supongamos que estamos en el mes de febrero y queremos crear una fecha de marzo (asignaré la fecha para reproducir el error, aunque el error es cuando se utiliza la fecha actual)

include_once("Zend/Date.php");
$fecha_febrero = mktime(0, 0, 0, 2, 1, 2010);
$fecha = new Zend_Date($fecha_febrero);
$fecha->setDay(31);
$fecha->setMonth(3);
print "Fecha Marzo: ".date("d/m/Y", $fecha->getTimestamp());

Uno esperaría que el resultado fuera: 31/03/2010

Pero en su lugar mostrará 03/03/2010

Esto es porque al hacer $fecha->setDay(31) el sistema intenta crear la fecha: 31/02/2010 que no existe, por lo que “recorre” los días restantes para quedar en 03/03/2010, al asignar el mes, no tiene efecto.

La solución es simplemente hacerlo en orden inverso:

include_once("Zend/Date.php");
$fecha_febrero = mktime(0, 0, 0, 2, 1, 2010);
$fecha = new Zend_Date($fecha_febrero);
$fecha->setMonth(3);
$fecha->setDay(31);
print "Fecha Marzo: ".date("d/m/Y", $fecha->getTimestamp());

Eso parecería la solución ideal (y es la recomendada por la documentación), pero no es totalmente cierta, si por ejemplo estamos en enero 31 del 2010 e intentamos asignar una fecha de febrero, siempre obtendremos una fecha de marzo:

include_once 'Zend/Date.php';
$fecha_enero = mktime(0, 0, 0, 1, 31, 2010);
$fecha = new Zend_Date($fecha_enero);
$fecha->setMonth(2);
$fecha->setDay(10);
print "Fecha Febrero: ".date("d/m/Y", $fecha->getTimestamp());

Uno esperaría que mostrara la fecha:
10/02/2010

Pero en realidad muestra
10/03/2010

Esto porque cuando asignamos el mes, el sistema intenta crear la fecha 31/03/2010 que una vez más es incorrecta, por lo que crea la fecha 03/03/2010, pero al asignar la fecha, el resultado es un error en el mes.

Aunque esto no es un error, es muy peligroso porque uno puede suponer que la fecha es correcta porque en el código lo parece, la solución sin embargo es trivial, asignar el primer día de un mes que tenga 31 días y asignar los valores en orden inverso (primero año, luego mes y por último día), así el código sin error sería:

include_once 'Zend/Date.php';
$fecha = new Zend_Date(0, 0, 0, 1, 1, date('Y'));
$fecha->setMonth(2);
$fecha->setDay(10);
print "Fecha Febrero: ".date("d/m/Y", $fecha->getTimestamp());

Resultado (el esperado)
10/02/2010

Esto funciona en la mayoría de los casos; aunque si van a manejar diferentes zonas horarias mediante setTimezone() el código anterior tendrá el mismo error en los casos que la zona horaria sea menor a la zona horaria del servidor, por lo que hay que hacer algo más elaborado:

include_once 'Zend/Date.php';
$fecha = new Zend_Date();
//eliminar "errores"
$fecha->setTimezone('mi_codigo_zona_horaria');
$fecha->setDay(1);
$fecha->setMonth(1);

//nuestro código
$fecha->setMonth(2);
$fecha->setDay(10);
print "Fecha Febrero: ".date("d/m/Y", $fecha->getTimestamp());

Lo peligroso del comportamiento de Zend_Date es que funcionará con errores durante algunos días/meses dependiendo de la manera en como se programe y es un poco difícil de diagnosticar por su misma naturaleza, con esto se elimina cualquier tipo de errores.

Una hora perdida

Mientras estaba haciendo un pequeño calendario que mostrara fechas con ligas a otras parte del sitio, pensé que la manera más fácil era evitar llamar a strototime:

strtotime("+1 days", $time)

y en su lugar utilizar directamente la suma de 86,400 segundos que es un día:

60 * 60 segundos = 3600 segundos (1 hora) * 24 = 86400

El calendario no tenía ningún problema hasta que noté que tenía repetido dos veces el día 1º de noviembre, los tiempos eran los siguientes (tiempo Unix):

1257051600: 2009-11-01 00:00:00
1257138000: 2009-11-01 23:00:00

Si hacen la suma, observarán que se sumó 86,400 segundos a una fecha y otra; pero el tiempo regresado por PHP (de hecho por el sistema Linux) es de 23 horas y no 24 horas.

Hice algunas pruebas en otros servidores y solo uno imprimió la fecha que esperaba (2009-11-02 00:00:00), la primera idea (por el cansancio) fue que era un error de PHP/Linux. Buscando un posible error, encontré algunas preguntas que como yo habían tenido un problema similar, pero mencionaban algo de la zona horaria, eso me recordó que el 1º de Noviembre en México atrasamos el reloj una hora por lo que en realidad el cálculo era correcto desde la perspectiva de Linux ya que estaba olvidando esa “hora perdida” en mis cálculos, el servidor que regresó bien la fecha estaba en otra zona horaria por lo que no compartían nuestro cambio de horario.

El tiempo Unix correcto es: 1257141600 no es otra cosa que es el día (86400) más la hora “perdida” (3600), este tiempo lo obtuve llamando a strtotime en PHP en lugar de calcularlo por mi cuenta.

Moraleja: Manipular el tiempo Unix de forma directa agregando valores puede ser muy rápido y simplifica la implementación, pero si quieres hacer algo que no tenga errores utiliza siempre las funciones de tiempo de PHP/Linux.

Dumpear todas las bases de datos

Recientemente tuve que “dumpear” todas las bases de datos de un servidor por lo que utilice un script muy simple para hacerlo:

USER=root;PASS=mi_pass;for database in `echo "SHOW DATABASES" | mysql --user=$USER --password=$PASS | sed '1d'`; do mysqldump --user=$USER --password=$PASS $database > $database.sql; tar jcvf $database.tar.bz2 $database.sql; rm $database.sql; done

Un poco más leible:

USER=root
PASS=mi_pass
for database in `echo "SHOW DATABASES" | mysql --user=$USER --password=$PASS | sed '1d'`
do
    mysqldump --user=$USER --password=$PASS $database > $database.sql
    tar jcvf $database.tar.bz2 $database.sql
    rm $database.sql
done

Básicamente manda todas las bases de datos y quita la linea inicial (que siempre es Databases) y por cada base de datos invoca mysqldump y por último lo comprime con bunzip2 para obtener un archivo muy pequeño y por supuesto elimina las “fuentes” de sql.

Auto inicio de dominios en Xen

Algo muy útil es iniciar/apagar automáticamente dominios XEN cuando se reinicia o apaga el anfitrión (dom0), cuando se instala XEN, por defecto deberemos ver creado un directorio llamado /etc/xen/auto

En este directorio el script /etc/init.d/xendomains buscará las configuraciones de las máquinas virtuales a iniciar/detener estos pueden ser vínculos simbólicos a las verdaderas configuraciones.

Para que todo esto suceda en primer lugar debes de tener el script /etc/init.d/xendomains que se instala junto con /etc/init.d/xend

También debes haber habilitado el inicio de este script con el siguiente comando:

update-rc.d xendomains defaults 21 20

 
El script /etc/init.d/xendomains necesita del archivo de configuración: /etc/sysconfig/xendomains

En este verás una línea que contiene:

XENDOMAINS_AUTO=/etc/xen/auto

Con esto definimos el directorio donde se buscarán los dominios que se auto iniciarán

Tip: Usando rsync para transferencias rápidas entre linux

Usualmente uso el famosímo scp para transferir archivos, el problema es que al parecer scp es muy lento cuando se trata de muchos archivos pequeños. 

rsync es un comando para hacer réplicas de un directorio a otro lugar, lo que hace es checar el comparar los directorios y copiar todos los archivos que hayan sido modificados, que sean nuevos o incluso que hayan sido elminados para hacer una copia exacta.

La gran ventaja de rsync es que aparte de crear una réplica, es sorprendentemente rápido, en especial con archivos pequeños.

El uso es muy simple:

rsync -az -e ssh FUENTE usuario@servidor:DESTINO

Eso es todo, para que funcione ambos servidores deben tener el programa rsync instalado, FUENTE es el directorio local a copiar (o puede ser un archivo), DESTINO es el directorio del servidor remoto a donde se copiará.

La opción -az envia la información compresa y -e ssh indica que hay que usar el programa ssh para transferir los archivos (con esto se puede enviar también mediante ftp, etc)

Otra opción que uso mucho es --delete, de esta manera rsync también borrará los archivos “extra” para que la copia sea exacta. Hay que tener cuidado, porque en algo descuidado se pueden borrar los archivos locales (inversión de parámetros)… algo que me pasó una vez, pero tenía respaldo =).

 

 

Solución a “Failed to find an unused loop device”

En Xen, si no configuras de antemano linux verás que después de iniciar varios servidores virtuales te marcará un error que dice: “Failed to find an unused loop device

Esto significa que linux ya no tiene “lugar” para montar más dispositivos loop (montar tu disco duro virtual o swap), por defecto tienes ochos dispositivos loop permitidos, que son para cuatro máquinas virtuales (debido a que utilizan al menos dos imágnes, una para el swap y otra para el disco duro)

Para aumentar este límite es muy fácil, primero detienes las instancias virtuales de Xen:

/etc/init.d/xendomains stop

Agregas al archivo /etc/modprobe.d/local-loop lo siguiente (o crealo si no existe):

options loop max_loop=64

Después vuelves a cargar el módulo

rmmod loop
modprobe loop

Por último inicias las instancias virtuales

/etc/init.d/xendomains start

Con esto puede tener hasta 32 instancias de Xen sin problemas.

Ruteo en Xen

Hace poco instalé un servidor Xen, después para terminar de configurarlo solicitamos IP’s adicionales para que cada servidor virtual tuviera su IP dedicada.

Lamentablemente nos dieron IPs en otra subred, esto puede ocasionar algunos problemas pero aqui está una solución =)

Vamos a suponer que nuestro servidor Xen tiene ip 192.168.1.90 y que nuestras nuevas ips a asignar son 192.168.247.130 y 192.168.247.131, y que el gateway de nuestra máquina anfitrión es 192.168.1.1

 

Configuración de Xen

Debido a que en mi caso necesitaba rutear los datos entre mi máquina anfitrión (dom0) y mis máquinas virtuales (domU) habilité lo siguiente en xen:

(network-script network-route)
(vif-script vif-route)

Tanto network-script  y vif-script deben ser las únicas configuraciones disponibles (esto es comentar todos los otro network-script y vif-script)

 

Reiniciamos el servidor Xen:

/etc/init.d/xend restart
/etc/init.d/xendomains restart 

Es muy importante que verifiques si te marca un error, ya que a mi me mandaba un error por un script que no reconocía la interfaz que ibamos a utilizar, así que hice el siguiente cambio en el archivo: /etc/xen/scripts/network-route

dir=$(dirname "$0")

. "$dir/xen-script-common.sh"

evalVariables "$@"

 

#netdev=${netdev:-eth${vifnum}}

netdev=eth0 #definir por defecto la interfaz a utilizar

echo 1 >/proc/sys/net/ipv4/ip_forward
echo 1 >/proc/sys/net/ipv4/conf/${netdev}/proxy_arp

 

Básicamente lo que hace el script de Xen es habilitar el ruteo en linux y crear unas reglas de iptables (que tu puedes configurar si quieres personalizar el rendimiento o seguridad de tus máquinas virtuales por medio del anfitrión)

Ya por último debes borrar el puente que hace Xen en la configuración por defecto de bridge (usando el comando brctl)

 

Configuración de la Imagen

Al crear la imagen. proporciona la IP, el gateway de tu máquina anfitrión (dom0) y la máscara de red correspondiente, en nuestro ejemplo 255.255.0.0, ejemplo:

xen-create-image --hostname=misitio.com --ip=192.168.247.130 --netmask=255.255.0.0 --gateway=192.168.1.1 --passwd

 

Con esto tus máquinas clientes podrán ser vistas y accesar a internet sin problemas.