February 3rd, 2010 Category: Programacion SOA Zend Framework
4 Comments »

En esta serie de artículos voy a tratar de explicar como hacer para que nuestros sitios web sean escalables. Trabaje en varios proyectos donde inicialmente eran webs para unos pocos usuarios pero con el tiempo fueron tomando fuerza y hoy tienen grandes cantidades de visitas y consumo de recursos. El problema es que estos sistemas no tuvieron una gran inversión y no están preparado para el consumo que generar.

Esta situación es muy común hoy en dia. En cualquier libro de escalabilidad, o web, foros, etc. uno de los puntos mas recurrentes es tratar de que las tareas que no requieran una interacción directa con el usuario se ejecuten en segundo plano. También podríamos agregar que lo ideal es que para ejecutar estas acciones en segundo plano se ejecuten a través de un único proceso, la centralización de estas acciones nos permite migrar a otro servidor el dia de mañana en caso de que sea requerido o si el servidor se  cae y poder retomar las acciones sin procesar una vez que el servidor vuelva, y sin que el usuario se haya enterado de este problema. Para hacer esto vamos a aplicar la teoría de colas.

Teoria de colas.

La teoría de colas es según la wikipedia, el estudio matemático de las lineas de espera. Básicamente se encolan procesos en orden de llegada, cuando la aplicacion libera un proceso toma el que entro después, y así sucesivamente. En todos los aspectos de la vida podemos encontrar colas, en el supermercado, cuando vamos a pagar impuestos, cuando vamos a la cancha a ver a Boca, etc.

En nuestro caso, supongamos que tenemos recursos limitados en nuestro hardware y nuestro servidor mysql solo atiende 10 conexiones simultaneas, cuando la visita 11 se conecte a la base de datos, el servidor de mysql hara esperar al proceso hasta que se termine de procesar alguno de los anteriores 10 procesos. En el caso que tengamamos muchas mas visitas el cuello de botella seria mas grande, y puede pasar que no podamos alcanzar a procesar todos los pedidos.

La forma ideal de evitar estos cuellos de botella y evitar que el usuario espere inecesesariamente, es enviar todos los procesos a una cola, esta cola podría contener un objeto serializado, que al correr el proceso que desencole deserealizaria el contenido pudiendo ejecutar la acción del usuario, sin que el usuario tenga que esperar a que se desencolen todos los procesos anteriores, o sobrecargar el servidor.

Podemos usar una única cola. O crear una cola para cada tarea diferente. Una cola para el envió de mails, otra para subir los comentarios de un producto, otra para procesar visitas en el sitio, etc.

Por que usar Colas.


amazon_simple_queue_service

La idea de usar colas es para evitar usar insert/update/delete en la base de datos en tiempo real haciendo al usuario esperar que se termine este proceso antes de mostrarle la pagina cargada para que pueda seguir usando el sitio. Que este encolado no significa que tengamos que esperar 10 minutos para correr un cron y ejecutar los cambios estos cambios pueden demorar un minuto o quizás menos. Pero todo varía dependiendo de la velocidad que necesitemos esa información. También podemos evitar el cron y correr un deamon que ejecute un evento cada vez que recibe un nuevo evento encolado.

Lo ideal seria reemplazar todos los insert/update/delete a la base por un envió a una cola x, para ser procesada en segundo plano. Esto a veces no es posible, en este caso podemos ver otras opciones.

Las colas simplifican nuestro trabajo. Supongamos que tenemos un proceso que envía mails. Este proceso (mailer) recibe un mail de envio, uno de respuesta, un asunto, y mensaje. Con estos datos que recibe el mailer envia mails, sin importar que sea de registro, de recuperación de clave, de aviso que un usuario le envió un correo interno, etc.. Esto hace que un unico lugar tengamos la logica necesaria para enviar un mail.

Supongamos que tenemos 3 servidores en nuestra red. Y solo uno esta configurado para enviar mails correctamente usando sendmailpostfix, da la casualidad que por necesidad el frontend del sitio tuvimos que moverlo o inicialmente esta en un servidor diferente. En nuestro caso lo unico que tenemos que procurar es instalar el mailer en nuestro servidor que si puede enviar mail. Sin importar en que servidor esta instalado el frontend.

Lo mismo podemos aplicar para diferentes procesos. El calculo de estadísticas del sitio, la carga de datos de nuestro backoffice, etc. No tienen porque compartir la misma base, ni el mismo servidor. El proceso de sincronización se puede hacer mediante colas.

Esto nos permite evitar la recarga de la base de datos, cuando la gente de administración necesita saber cuantos usuarios se registraron a las 16:45 del martes anterior, o alguna de esos reportes extraños que a ellos les encanta pedir, y que generalmente significan querys con un costo altísimo en cuanto a procesos. Procesos que necesitamos libres en nuestro frontend para que el usuario navegue sin problemas.

Distintos tipos de colas disponibles para nuestros sistemas web.

En la actualidad existen muchas formas de implementar un servicio de cola en el desarrollo web, los mas comunes son ActiveMq de apacheMemcacheqGearman, etc. Tambien podriamos usar la base de datos para encolar tareas, pero la verdad es que mientras menos abusemos del motor de la base de datos mejor.

apache-activemq-logo

Un sistema de colas con ActiveMq es mas rapido que hacer una conexion y un insert en la base de datos, y consume menos recursos. Despues de todo lo unico que hace es abrir un socket para recibir un string, y guardarlo con un nombre x.
He probado Activemq con mucho exito, pero actualmente estoy usando el servicio de Amazon SQS, el cual esta dentro del abanico de servicios de Amazon AWS, en proximos capitulos espero poder hablarles sobre Amazon S3Amazon cloudfront.
Amazon SQS
amazon_web_services

Con Amazon SQS tenemos nuestro sistema de colas alojado en un servidor de alto rendimiento sin limites de espacio o de procesos a costos bajisimos. Muy facil de implementar y mucho mas facil de usar gracias a Zend Framework, que trae un componente Zend_Services_Amazon_Sqs, con el cual podemos encolar, desencolar de forma muy facil. Por el tema de costo para que se den una idea, yo voy un mes de uso, y no llego a u$d 1 de costo.
Ejemplo de uso con Zend_Service_Amazon_SQS

Encolar un tarea.
$key = 'Mi Clave provista por Amazon';

$secretKey = 'Mi Clave Secreta provista por Amazon';

$data = Zend_Json::encode( $objetoAProcesar );
$sqs = new Zend_Service_Amazon_Sqs($key, $secretKey );
$queueUrl = $sqs->create( 'to_process_message' );
$sqs->send($queueUrl, $data);

Desencolar y procesar.

$config = Zend_Registry::get("config");
$sqs = new Zend_Service_Amazon_Sqs($key, $secretKey );
$queues = $sqs->receive( $queueUrl );
foreach ($queues as $message) {
    // Aca procesamos el mensaje. Si el objeto esta serializado lo deserializamos, y ejecutamos el proceso correspondiente.
    // Si usamos un proceso unico para desencolar todas las colas. No estaria de mas establecer una interfaz para pasar estos objetos.
    $data = Zend_Json::decode( $message );
    print_r($data);
    // Terminado de procesar lo borramos, asi no volvemos a traerlo
    $sqs->deleteMessage( $queueUrl, $message['handle']);
}

Este es un inicio al escalamiento de sistemas web. En el proximo capitulo contenido estatico en un dominio y servidor diferente.

Seguir leyendo

September 2nd, 2009 Category: SOA Zend Framework
6 Comments »

En el proyecto actual en el que estoy trabajando nos topamos con un problema para validar que un usuario solo pueda ver sus datos y/o productos y no los de otro usuario. Este problema se da porque el proyecto actual esta muy orientado a SOA, servicios orientado a la arquitectura, básicamente esto significa subdividir una aplicacion en aplicaciones mas chicas, generalmente están desacopladas unas de otras, y cada una funciona como un ser independiente. La comunicacion entre ellas es a través de los conocidos Web Services (rest, xml-rpc, soap, json ).

Una de estas aplicaciones es un Single Sign-on el cual administra todo lo referente a los usuarios, sesiones, datos, etc. Otra de las aplicaciones es un Shopping cart, este tiene las ordenes asociadas a un id de usuario, como conte antes la idea es que estén lo mas desacopladas posibles, esto es para poder reutilizar estas apis en otros proyectos evitando reescribir código, y con tocar algunos items de configuracion salga andando.

Si desde una tercera aplicacion quiero traer los items agregados por un usuario, debería pedirle al shopping cart los items a partir del id de usuario. Por ejemplo, si la api de shopping cart tiene la siguiente interfaz.

public getOrder( $userId );
public getItems( $orderId );
public payOrder( $orderId, $method );

Si nosotros queremos traer la orden abierta de un usuario enviamos el id del usuario y obtenemos el id de la orden, con el mismo conseguimos los items, y porque no pagarlo. Para evitar este problema, tendríamos que validar que el que esta ejecutando estas funciones este logueado en el sistema, y no alguien aprobechandose de la falta de validacion.

La forma logica de hacer esto es validar la sesión de usuario, pero como comente antes  esta opción no es posible porque las aplicaciones están separadas, pueden llegar a estar en el mismo server como puede que no.

Para solucionar esto utilizamos un sistema parecido al de Kerberos, aprovechando que tenemos un sistema donde centralizamos los usuarios ( el Single Sign-on), vamos a usarlo para emitir certificados que se van a poder utilizar para validar los datos de un usuario.

Este certificado como dijimos lo emite el single sign-on, el mismo va a viajar encriptado, cada aplicacion va a tener conocimiento de como desencriptarla.

Cada vez que nosotros busquemos los items del carrito de compras de un usuario, vamos a hacer la llamada pasando el certificado, el id de usuario, y los parámetros específicos que requiera cada método de la api.

Cuando se desencripta el certificado con una cable en la configuracion de cada api obtenemos un objeto PHP el cual tiene 3 propiedades, userId, timestamp, y un token. El userId sirve para validar que el id de usuario que nos envían es el mismo que el certificado, el timestamp es para saber si el certificado sigue vigente o caduco, el token, es solo para que el certificado nunca sea el mismo, y sea difícil interceptarlo.

Aplicando esta solución, la interface del Shopping cart nos quedo así

public getOrder( $userId, $credential );
public getItems( $orderId, $userId, $credential );
public payOrder( $orderId, $method, $userId, $credential );

Hay muchos protocolos de encriptacion el tema es bastante amplio y hay para todos los gustos. En este caso nosotros nos decidimos por Mrcrypt, esta era la opcion mas comoda, no solo por que viene por default en PHP, sino porque tambien hay un componente de Zend Framework que nos deja aprovechar esta extension.

Esta situacion se pueda dar en varias situaciones, no precisamente en este aplicacion en particular, tambien se puede aplicar el sistema de clave publica y privada pero hay una funcion en php para usar GnuPG (http://ar.php.net/manual/en/function.gnupg-encrypt.php), no lo probe ni investigue mucho, pero si esto no soluciona el problema podemos usarlo desde command line, en Devzone.zend.com encontre este articulo que nos puede ayudar un poco mas http://devzone.zend.com/article/1265

Para implemntar esta solucion en la aplicacion en la que trabajo use Zend_Filter, basicamente Zend_Filter toma un valor le aplica ciertas reglas, y devuelve un resultado filtrado por estas reglas, por ejemplo un nombre de usuario tiene que estar en minuscula, sin espacios, sin guiones medios ( – ), para esto nosotros vamos a aplicarle una funcion para que le limpie los espacios en los extremos, le quite los guiones, o caracteres que no queramos, y ejecute un strtolower para devolver el string en minuscula. Todo esto aplica como filtro, a un valor se le puede aplicar mas de un filtro. Zend_Filter nos da un conjunto de Filtros mas complejos que un simple trim(), o strtolower.

Zend_Filter nos provee dos filtros que en realidad son uno solo (para saber el porque mirar el codigo fuente de Zend_Filter_Decrypt ), Zend_Filter_Decrypt y Zend_Filter_Encrypt.

Estos nos sirven para recibir un parametro encriptarlo, y desencriptarlo.

Es muy sencillo utilizarlo pero tiene algunas cosas que por ahi no quedan tan claro. Para este caso yo use mrcrypt y aproveche el adapter de Mrcrypt que esta para este filtro. Al usar un adapter obtenemos un monton de propiedades que en el caso de no  agregar el adapter tendriamos que completarlas nosotros mismos una por una. El problema con el que nos encontramos es que si o si necesitamos pasar el adapter, y el vector, el vector basicamente es la cantidad de bytes que va a tener cada bloque de encriptacion (para mas leer la wiki), este parametro es obligatorio, porque sino lo ingresamos usa un valor random, y seria imposible desencriptarlo sin la cantidad exacta de bytes por bloque. No entendi porque la gente de Zend no incluyo un valor default para los vectores pero alguna razon tendran.

En el ejemplo que vamos a ver a continuacion, se crea un objeto con ciertas propiedades, lo serializamos, lo encriptamos, lo pasamos a base64, para que podamos enviarlo via get a otros sistemas.


$obj = new stdClass();
$obj->userId = 12;
$obj->timestamp = time();
$obj->token = sha1(uniqueid(mt_rand(),true);

$objSerialized = serialize( $obj );

$encrypt = new Zend_Filter_Encrypt(
    array(
        'adapter' => 'mrcrypt'
        , 'vector' => '12345678'
    )
);

//Aplicamos el filtro a nuestro objeto serializado, esto nos devuelve un binario encryptado
$objEncrypted = $encrypt->filter( $objSerialized );

//Convertimos el binario en algo que podamos usar mas facilmente
$credential = base64_encode( $objEncrypted );

?>

El valor que obtenemos de esta operacion va a ser nuestro certificado.

Cuando queramos desencriptarlo el codigo que tenemos que aplicar a nuestro certifiado es el siguiente

$encrypt = new Zend_Filter_Decrypt(
    array(
        'adapter' => 'mrcrypt'
        , 'vector' => '12345678'
    )
);
$objEncrypted = base64_decode( $credential);
$objSerialized = $encrypt->filter( $objEncrypted );
$obj = unserialize( $objSerialized );

/* ahora mostramos nuestro objeto desencriptado y desserializado */

Zend_Debug::dump( $obj );

Ademas de obtener el objeto deseariliazado ahora nos toca validar la credencial, para esto con el objeto deserealizado comparamos si el userId que nos llega es el mismo que en la credencial, y si la fecha es cercana al timestamp que tiene la credencial. Como parametro exta mis credenciales tienen un timestamp y timelife, que me dice cuanto puede vivir un certificado.

A Zend_Filter_Decrypt y Zend_Filter_Encrypt podemos agregarle un salt que va a darle mas seguridad a nuestros certificados.

Seguir leyendo