Recuperando archivos de imagen de un documento word

Os comento un truquejo, bastante simpático, que os puede ayudar a salir del paso alguna vez.


Problema: Me solicitan por separado las imágenes y en su mejor resolución de un documento word donde están incrustadas. No dispongo de los originales (eran capturas de pantalla) pruebo a copiarlas y pegarlas en Photoshop y salen con una calidad lamentable, mucho peor que la del documento.


Solución: Si el archivo es .docx (Office 2007) podemos hacer lo siguiente: le hacemos una copia (ctrl+c, ctrl+v sobre el archivo) y renombramos la extensión del archivo a .zip. Ojo, quizáis necesitéis ir a una ventana del explorador y en opciones de carpeta desactivar la opción de «Ocultar extensiones de archivos conocidos». Si examinamos el archivo zip veremos que dentro de la carpeta wordmedia encontramos las imagenes en formato png, emf, etc.. y a su resolución original.


Jeje a mi «me salvó la vida».

Parameters.AddWithValue … with Cuidadito

Leyendo el hace unos días unos posts muy recomendables del Sql Server Programmability & API Development Team Blog acerca de cómo funciona caché de planes de consultas y procedimientos de SqlServer 2005, me enteré de una buena práctica para nuestro código .Net que accede a datos de SqlServer. Aunque encuentro lo descubierto más interesante para evitar una mala práctica que, por despiste, seguro que hacemos más de muchos. Y como veremos… me parece que algún chico de Redmond también.

Entremos en vereda…
Cuando ejecutamos una consulta contra SqlServer se emplea un tiempo en crear un plan de ejecución. Ese plan, relativamente costoso de crear, lo almacena en un caché de planes de ejecución. La reutilización de dicho plan mejora considerablemente el rendimiento de SqlServer al ahorrarnos un tiempo precioso, y por ende, también mejora el tiempo de respuesta de nuestra aplicación.Todos estamos más o menos aleccionados a utilizar parámetros en nuestras consultas SQL, sobre todo por razones de seguridad ya que filtra lo pasado a la consulta, evitando ataques por inyección de código SQL. Las consultas parametrizadas son planificadas con sus parámetros, así pueden ser reutilizadas aunque cambie el valor de los parámetros.Una cosa que me sorprendió es que SqlServer  autoparametriza las consultas que planifica en previsión de que sean reutilizadas:

 

SqlCommand command = new SqlCommand(conn);
command.CommandText = «select * from t1 where col1 = 1 and col2 = ‘abcd'»;
command.ExecuteNonQuery();
command.CommandText = «select * from t1 where col1 = 1 and col2 = ‘abc'»;
command.ExecuteNonQuery();

Genera un único plan de ejecución para la consulta:


(@1 tinyint,@2 varchar(8000))SELECT * FROM [t1] WHERE [col1]=@1 AND [col2]=@2

Es bastante común ejecutar un SqlCommand definiendo los valores de los parámetros y no especificar su tipo, especialmente si utilizamos AddWithValue que ya no nos da opción a definir el tipo del parámetro, muchos desarrolladores lo utilizan porque ahorras una llamada a un método.
Es típico ver esta porción de código:

 

SqlCommand command = new SqlCommand(«select * from t1 where col1 = @id and col2 = @str», conn);
command.Parameters.AddWithValue(«@id», 1);
command.Parameters.AddWithValue(«@str», «abc»);
command.ExecuteNonQuery();
command.Parameters[0].Value = 2;
command.Parameters[1].Value = «abcd»;
command.ExecuteNonQuery();


Examinando el cache de planes de ejecución obtenemos:


(@1 tinyint,@2 nvarchar(3))SELECT * FROM [t1] WHERE [col1]=@1 AND [col2]=@2
(@1 tinyint,@2 nvarchar(4))SELECT * FROM [t1] WHERE [col1]=@1 AND [col2]=@2

Uppps, son dos planes de ejecución distintos. No ha reutilizado la consulta. Se ha perdido un tiempo valioso.  Se da la paradoja que la consulta sin parámetros sería más eficaz que la parametrizada en este segundo ejemplo. ¿Por qué ocurre esto? Pues al parecer porque no se ha especificado la longitud del parámetro alfanúmerico y SqlServer lo crea como un nvarchar de la longitud del valor pasado.  Tranquilos, esto no ocurre con tipos fijos como int o datetime, pero si en nuestra consulta tenemos un parámetro que es un string, evitad utilizar AddWithValue.

Este otro fragmento de código si sería más correcto:

command.CommandText = «select * from t1 where col1 = @id and col2 = @str»;
command.Parameters.Add(«@id», SqlDbType.Int);
command.Parameters.Add(«@str», SqlDbType.NVarChar, 50);
command.Parameters[0].Value = 1;
command.Parameters[1].Value = «abc»;
command.ExecuteNonQuery();
command.Parameters[0].Value = 2;
command.Parameters[1].Value = «abcd»;
command.ExecuteNonQuery();


Produciendo un único plan de ejecución:
(@id int,@str nvarchar(50))select * from t1 where col1 = @id and col2 = @str


¿Os imagináis que pasaría si está dentro de un bucle que va insertar masivamente valores en nuestra base de datos? Pues que el caché de planes de ejecución se llenaría de consultas preparadas para cada combinación de parámetros alfanuméricos, sacando otras consultas preparadas (planificadas) del caché de planes de ejecución, además de incurrir en un tiempo extra en planificarla.

Lo curioso es que ayer mismo, utilizando un plan de generación de datos en VisualStudio 2005 for Database Professionals, descubrí un pequeño detalle al respecto mientras investigaba otra cosa… estaba insertando 524288 tuplas en una tabla de pruebas en la que tenía dos campos con NVarChar (psche… lo hago todos los días antes de desayunar) cuando paralelamente ejecutó una consulta Transact-sql en Sql Server Studio Management Express sobre la tabla del sistema con los planes de ejecución cacheados y me encuentro con esto:

Pantallazo de una consulta ... 

Cómo podéis observar en la imagen, ha creado un plan de ejecución por consulta, debido al problema que antes os mencionaba. Probé a hacer un count(*) en la tabla del caché de planes de ejecución y contenía 8192 tuplas (oh, curioso límite) con planes de ejecución de esta operación de generación de datos. El impacto de este problema no es excesivamente grave pues los planes de generación de datos se suelen ejecutar en instalaciones de SQLServer en entornos de desarrollo y no en producción, por lo que no parece importante que afecte al rendimiento de otras consultas.Desde mi punto de vista el problema se encuentra en una mala implementación de AddWithValue, el desarrollador no puede estar pendiente de si un método aparentemente cómodo y eficiente no lo sea, esa es la confianza que depositamos en el .Net Framework. Quizás sería recomendable que en futuros frameworks se reescribiera este método sobre todo para que intercepte los tipos de datos alfanuméricos y le ponga un tamaño por defecto al igual que hace SqlServer en las consultas auto parametrizadas. También sería recomendable una sobrecarga de AddWithValue que permita especificar el tipo de dato explícitamente.

Service Pack 2 para Windows 2003 Server en español

Desde ayer teneis listo para descargar el Service Pack 2 de 2003 server por fin en castellano:


http://www.microsoft.com/downloads/details.aspx?displaylang=es&FamilyID=1b9fe9e4-1d57-4698-a5cf-db271ed6d90a


Oh, si alguno tiene un ISA montado que eche un ojo, por si acaso a:


WARNING! Windows Server 2003 SP2 May Destroy Your ISA Firewall without Warning:


http://blogs.isaserver.org/shinder/2007/03/23/warning-windows-server-2003-sp2-may-destroy-your-isa-firewall-without-warning/


You cannot host TCP connections when Receive Side Scaling is enabled in Windows Server 2003 with Service Pack 2:


http://support.microsoft.com/default.aspx?scid=kb;EN-US;927695


 

Desacoplando System.Web.Cache de nuestra lógica de negocio

Normalmente la tarea que más aumenta el tiempo de respuesta en la devolución de una página ASP.NET que consulta contra una BD es la propia consulta a la BD. Un escenario frecuente es la recuperación y presentación de datos con poca tasa de actualización. Para cada cliente que ejecuta la página se ejecuta de nuevo la consulta. Pese a que internamente SQL Server u otro gestor SGBD suele tener planificada las últimas consultas SQL realizadas y dispone de sus propios cachés, no tenemos la certeza de que así sea, sobre todo en momentos de alta carga de consultas, justo cuando más se necesita.


La primera aproximación para mejorar el throughput contra la BD es hacer las consultas solo en la primera petición de la página, en post backs sucesivos no se realiza la consulta.


    protected void Page_Load(object sender, EventArgs e)


    {


        if (!Page.IsPostBack)


        {


            BindData();


        }


    }


El método BindData() obtendría los datos de la BD y, por ejemplo, se los asociaría a un GridView , en peticiones posteriores  de la página por parte del mismo cliente no se ejecutaría este método, pero se seguirían mostrando los datos en el viewstate.  Este codelet significó una gran mejora de ASP.NET 1.0 frente ASP. En ASP.NET 1.0 se introdujo ya un conjunto de clases para gestionar un caché de datos y su política de invalidación. ASP.NET 2.0 mejora y extiende estas características.


Caché de datos global


Supongamos un escenario en el que tenemos una serie de datos en la BD relativamente inmutables y que utilizan en común todos los clientes de nuestra aplicación ASP.NET 2.0. Por ejemplo, una tabla de provincias o de empleados. Con la técnica del apartado anterior, cada vez que un nuevo cliente solicita o recarga la página que contiene un combo con la lista de provincias se ejecuta al menos una consulta contra la BD.


Puesto que los datos son comunes a todos los clientes de la aplicación podemos usar el caché de datos global. Este caché, a menos que se especifique lo contrario, tiene el mismo tiempo de vida y validez que el proceso de nuestra aplicación ASP.NET en el servidor. La afinidad del caché es a nivel de aplicación en un pool de IIS. Por lo que en un entorno de webfarming se tendría un caché global para cada aplicación en un pool dentro de un nodo del clúster.


El caché de ASP.NET en una arquitectura de tres capas


En los ejemplos que se pueden encontrar en la documentación de ASP.Net o en los tutoriales de www.asp.net se muestran ejemplos del uso del caché con datos en aplicaciones de una sola capa; dentro de una misma página ASP.NET se realizan las consultas a datos, su procesamiento y visualización.


Si bien es práctico en desarrollos de poco alcance, cuando se va a desarrollar una aplicación en un entorno empresarial es muy recomendable una arquitectura en al menos tres capas; Interfaz de usuario (UI), Lógica de Negocio (Business Logic, BL) y acceso a datos (Data Access, DA).


Si queremos emplear el caché global de datos en nuestra lógica de negocio observaremos que depende realmente de clases del interfaz de usuario pues el objeto Cache se obtiene o bien del objeto Page actual o bien del objeto HttpContext. No podemos usar estas clases en nuestra Lógica de Negocio porque se haría dependiente del interfaz empleado.  La capa BL debería servirnos para cualquier tipo de interface, WindowsForms, aplicación de consola, etc.


Para desacoplar las clases de caché dependientes de ASP.NET de la lógica de negocio, os propongo el siguiente diseño, basado en el patrón de diseño Estrategia (Strategy Pattern) [GAM97] y un patrón Adaptador  (wrapper) [GAM97] . Permite elegir varias estrategias de cache y el segundo abstrae el interfaz de System.Web.Caching.Cache .  Para el acceso a datos del modelo conceptual se utilizan DataSets en vez de objetos DTO junto a una clase DAO (Direct Access Object) que realiza las consultas al gestor  SqlServer.


El interfaz de usuario (UI) accede a la lógica de negocio a través de una fachada (Facade Pattern)[GAM97] que al carecer de estado es una clase de instancia única (Singleton Pattern)[GAM97]


 Diagrama 1. Diagrama estático de clases del ejemplo Sample.TestCache


Diagrama 1. Diagrama estático de clases del ejemplo Sample.TestCache


La página por defecto invoca con su método BindData() el enlace inicial de los datos a los controles visuales.


        private void BindData()


        {


            FacadeData facade = FacadeData.getWithCacheStrategy();           


            GridView1.DataSource = facade.Employees;


            GridView1.DataBind();


        }


En el fragmento de código superior vemos cómo invocando el método getWithCacheSrtategy() se obtiene de forma implícita una instancia de la fachada de acceso a la lógica de negocio y se solicita que utilice una estrategia de caché. La implementación concreta de ICacheStrategy se establece en el fichero web.config.


  <appSettings>


    <add key=«CacheStrategyClass« value=«Samples.TestCache.CacheStrategyAspNet, Samples.TestCache«/>   


  </appSettings>


La fachada tiene otros dos métodos para obtener una instancia de si misma;  getInstance(), que carece de estrategias de cache y getWithCacheStrategy(ICacheStrategy) que acepta desde código una estrategia de cache explícita.


 


        public static FacadeData getWithCacheStrategy(ICacheStrategy ipd)


        {


            if (instance == null)


                instance = new FacadeData();


            CacheStrategy = ipd;


            return instance;


        }


 


        public static FacadeData getWithCacheStrategy()


        {


            return getWithCacheStrategy(getInstanceCacheStrategy());


        }


        private static ICacheStrategy getInstanceCacheStrategy()


        {


            string strCacheStrategyClass =


                   WebConfigurationManager.AppSettings[«CacheStrategyClass»];


           


            if (strCacheStrategyClass != null)


                return (ICacheStrategy) Activator.CreateInstance(


                                            Type.GetType(strCacheStrategyClass));                     


            else


                return null;


        }


La fachada también actúa en como proxy a la hora de recuperar los empleados. Se hace bajo demanda y devuelve el dataset del caché en caso de que así se haya configurado o directamente del DAO.


        public DataSet Employees


        {


            get


            {


                if (CacheStrategy != null)


                {


                    if (CacheStrategy.getData(«cachedEmployees») == null)


                        CacheStrategy.setData(«cachedEmployees», EmployeeDAO.getEmployees())


                    


   return CacheStrategy.getData(«cachedEmployees»);


                }


                else


                    return EmployeeDAO.getEmployees();


            }


        }


El interface de ICacheStrategy para este ejemplo, es muy sencillo:


    public interface ICacheStrategy


    {


       DataSet getData(string dataKey);


       void setData(string dataKey, DataSet data);


    }


La implementación para el caso de utilizar el caché de ASP.NET:


        public DataSet getData(string dataKey)


        {


            HttpContext context = System.Web.HttpContext.Current;


            return (DataSet) context.Cache[dataKey];


        }


 


        public void setData(string dataKey, DataSet data)


        {


            HttpContext context = System.Web.HttpContext.Current;


            context.Cache.Remove(dataKey);


            context.Cache.Add(dataKey, data, null, DateTime.Now.AddMinutes(MIN_TO_EXPIRE),


                   Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);


        }


El método setData podría haberse simplificado, sin embargo he preferido esta opción porque, entre otras cosas, se puede controlar el tiempo de invalidación del caché y su prioridad. La clase precisa del contexto HTTP para poder acceder al caché.


Si distribuímos en ensamblados las clases, en el del IU tendríamos las clases Default (página ASPX). En el ensamblado de la lógica de negocio estarían el resto de clases. Nótese que CacheStrategyAspNet tiene una dependencia indirecta a HttpContext.


Este ejemplo podría haberse realizado igualmente con objetos del modelo en vez de con DataSets.


Si ejecutamos la aplicación veremos que múltiples peticiones devuelven los mismos datos obtenidos en el mismo instante (columna  RetrievedAt). Incluso cuando el cliente recarga la página.  Solo cuando hace clic en “Refresh Data” se actualiza el caché.  Recordad que este caché es privado a la aplicación en ejecución, se comparte entre todas las sesiones que mantenga la aplicación dentro del pool del servidor IIS, si por ejemplo queréis hacer una consulta con parámetros dependientes de la sesión este esquema no os valdría.


Con este ejemplo hemos visto cómo desacoplar la tecnología de caché de asp.net de una fachada de nuestra lógica de negocio.


Queda abierta la discusión para el caso de querer crear un cliente ligero y llevar la lógica de negocio a un servidor distinto al de IIS, usando por ejemplo, Windows Communication Foundation.


Referencias


 [GAM97]            Design Patterns, Elements of Reusable Object-Oriented Software, Gamma et Al.  Addison-Wesley Eds. 1997. ISBN 0-201-63361-2


Apple haciendo amigos

Pues resulta que algunos ipods han salido al mercado (desde apple) con un virus. El virus RavMonE está diseñado para S.O. Windows. Apple, textualmente ha dicho:

"As you might imagine, we are upset at Windows for not being more hardy against such viruses, and even more upset with ourselves for not catching it."

Como puedes imaginarte, estamos enfadados con Windows por no luchar más contra los virus con dureza  e incluso estamos más enfadados con nosotros mismos por no haberlo pillado.

Flipante, echando M. sobre Microsoft por no tener un antivirus… si es que le dan a la razón a Ms cuando dice que en Vista va a tener un papel más activo en la tecnología de antivirus.

Por otra parte… ¿qué hace apple con un windows para configurar sus ipods? al parecer  tiene un contratista que lo usa… eso ha dicho.

Dios me voy corriendo a pasarle AppleAntiVirus a mi ipod, no sea que por usar windows haya pillado el virus. Claro que si me entra no le echaré a la culpa al hijo puta que hizo el virus, ni a los despistados de PcCillin al que estoy subscrito, ni siquiera a apple por dejar alguna puerta abierta… noooo… la culpa es de microsoft… y de mi santísima madre desde el día que me parió. Flipante.

Finalmente, en bastantes blogs la gente está responsabilizando a apple y no les parece tampoco muy ético echar pestes sobre microsoft, podeis consultarlos en http://news.com.com/2061-11199_3-6127343.html?part=rss&tag=6127343&subj=news

 

 

Wishlist para Live…

¡A lo mejor me escuchan!

Bueno, después de comentar con Bruno las últimas novedades de Live con su OneCare. Se me ocurre una característica que desconozco si ya la tienen en mente pero que a mi y a unos cuantos nos resolvería bastantes problemas; os cuento:

Ahora, prácticamente todo el mundo, tiene una conexión a internet permanente en cualquier entorno de trabajo. Por otro lado yo tengo cuatro equipos donde desarrollo mi trabajo / ocio. El del trabajo de la mañana, el del trabajo de la tarde, el de casa y un portatil que llevo cuando hay fiestas fuera de casa.

Toda mi información y preferencias no ocupan más de 1GB-2GB. Evidentemente no incluyo películas o música, simplemente words, código de proyecto, excel, email, etc. Es bastante común que cuando entro en un ordenador tenga que ir por escritorio remoto a otro para coger los archivos que me hacen falta. No hablemos del correo, que me lo tengo que bajar por pop en 3 outlooks… con idénticas reglas y filtrando el correo no deseado por triplicado.

A mi me gustaría que mi perfil, con mis datos estuviese en un repositiorio global, que al iniciar sesión en live, se abriera como una carpeta y que automáticamente se cargasen mis preferencias en VisualStudio, Office, y Outlook.  Podrían tener una tecnología tipo shadow folders para prevenir caídas en la red y mejorar el rendimiento. Evidentemente esto se podría hacer para una intranet con tecnología actual, pero la realidad es que muchos de nosotros trabajamos en 3 sitios distintos dónde no tiene sentido tener la misma intranet. Por otra parte Sharepoint Team Services me arregla algo este problema pero no tiene la soltura y comodidad de lo que acabo de describir.

Es cierto que la mayoría de usuarios tendrían recelos de que sus archivos estén en un disco duro remoto que no controlan, pero es un argumento vacío desde que todos envíamos correo con archivos por hotmail o gmail. Creo que esa desconfianza está superada.

Lo dicho a ver si me escuchan… <<suspiro>> … tendría que despreocuparme de llevar un pen drive o un hd portatil sincronizado.

 

Tiembla McAfee y Symantec con Vista

Las empresas de antivirus para windows como McAfee, Symantec, etc. están un poco nerviosas con las nuevas características de seguridad de Windows Vista como el PatchGuard. En el artículo, un tanto capcioso, "Why Microsoft is wrong on Vista security" de George Heron (investigador jefe de mcafee) dice que la confianza en los antivirus se basa en que ha sido gestionada por terceras empresas y Microsoft siempre les dejó hacer… (les dejó hacer negocio, vamos) y que eso era una gran ventaja porque así protegían tanto a microsoft de sus propios bugs como a los usuarios 😉 pero con el nuevo enfoque de vista con protecciones como PatchGuard (impide hacer hooks a ciertas funciones del API/Kernel) hace que sea muy dificil de implementar antivirus a los "chicos buenos" y que, con lo "ineptos" que son en microsoft con sus "gravísimos" bugs de seguridad ellos ya no podrían hacer nada para que un montón de chicos malos se metieran en nuestros ordenadores por culpa de Ms, claro.

Menuda retórica. Todo para decir que se les acaba el pastel y que a ver si se movilizan los usuarios en contra de Ms para que las bondadosas empresas de antivirus puedan seguir facturando. Sinceramente, que alguien como el Sr. Heron se ponga las sábanas y haga del fantasma de la inseguridad de los operativos de microsoft solo demuestra lo preocupante de la situación.

De la misma forma que algún restrasado perteneciente a brigadas de extinción de incendios quemó el monte para crear la necesidad de contratarlo… deberían investigar la relación de las empresas de antivirus con la aparición de nuevos virus.

Que el colega Heron no nos toque la moral con los fantasmas de siempre. Que se pongan las pilas. Si las multinacionales que fabrican virus y antivirus están nerviosas es que hay algo bueno para los usuarios. Y de quien empiezo a desconfiar seriamente es de McAFee con artículos como estos. Habrá que tenerlos vigilados.

Clúster web de applicaciones ASP.NET con Linux Virtual Server e IIS 6.0

En este post os describo como montar un cluster o web farming de IIS con aplicaciones ASP.NET utilizando como balanceador de carga entre nodos del clúster un Linux Virtual Server (LVS).

Como sabeis, cuando crece el número de usuarios web el escalado interno de vuestro servidor suele resolver a corto plazo el problema de atender gran numero de peticiones. Pero si teneis éxito en vuestra web 😉 y empieza a subir exponencialmente el número de peticiones, vais a tener que buscar otra solución: el escalado externo.

Las técnicas de Clustering o Webfarming permiten añadir servidores web o nodos formando un conjunto de máquinas que se reparten las peticiones http/https y si una está muy cargada o no disponible, otro nodo operativo resuelve la petición. Al final se logran entornos con alta disponibilidad y tolerante a fallos. Para realizar el balanceo de carga puede optarse por soluciones software como LVS o NLBs o por soluciones hardware como conmutadores de capa 7, también llamados conmutadores de capa de aplicación.

La solución más óptima es la última pero es muchísimo más cara (entre $30.000 y $60.000). Mediante software se puede montar un balanceo muy económico (tanto por hardware requerido como por licencias software) utilizando LVS.

Ya puestos en materia los aspectos a considerar son:

1.         La instalación de un Linux con LVS.

2.         La configuración de red y LVS del clúster.

3.         La persistencia de la sesión de las aplicaciones web.

Como la mayoría de nosotros estamos un poco más acostumbrados a los servidores Windows, seré algo más extenso en la configuración de LINUX y LVS.

Para simular el entorno utilicé vmware sobre un Debian ya que solo disponía un equipo con 1GB de RAM y necesitaba ejecutar 3 Windows 2003 Server y un LVS.

Instalación de LVS

Existen bastantes recursos en línea de cómo instalar LVS, concretamente yo lo instalé sobre un Debian con kernel 2.4.7 y la distribución UltraMonkey de LVS. También hay productos comerciales como Piranha de RedHat. En el sitio web de UltraMonkey está bastante detallado el proceso de instalación en debian para diversos escenarios.

En esencia, no es muy complicada, pero en mi kernel tuve que compilar e instalar los módulos de IPVS (el core del LVS) manualmente con insmod y activar el reenvío de paquetes ip (packet forwarding). Por cierto, tiene tres mecanismos para hacerlo y dependen un poco de la infraestructura de red que tengamos: Rutado directo, encapsulación IP a IP y NAT. Por comodidad yo elegí NAT en mi entorno de vmware.

IPVS no es simplemente un IP forwarding con un algoritmo de balanceo de carga. IPVS reconstruye los datagramas IP (layer 3) en el mensaje de capa de aplicación (Layer 4), como por ejemplo mensajes HTTP, y se lo manda completo (en uno o más paquetes IP) al nodo que lo va a resolver. Esto, que parece una tontería, es muy importante porque si no podrían llegar peticiones HTTP incompletas y el clúster funcionaría intermitentemente.

Aquí podéis ver el esquema de la red del clúster que monté.

El servidor con LVS se suele llamar Linux Director (LD). El esquema presentado está pensado para balanceo de carga con alta disponibilidad. En el caso de precisar tolerancia a fallos en el Linux Director se podría montar un nuevo servidor gemelo y configurarlo según la documentación  para que actúe como respaldo del Linux Director principal.

Fijaos que el LD tiene dos interfaces de red, uno público de cara al exterior (10.200.11.128) y uno interno para los servidores del cluster (10.200.10.128). Los nodos del cluster (wincluster01 y wincluster02) tienen como puerta de enlace principal la 10.200.10.128, de esta forma las respuestas de los wincluster llegan al cliente remoto enmascaradas con la ip 10.200.11.128 y no con la IP del nodo del clúster que ha respondido. Esto es imprescindible si estamos manteniendo el seguimiento de la sesión con cookies (así lo hace por defecto ASP.NET) y no queremos que se cree una nueva sesión cuando cambia el nodo que responde.

El fichero de configuración del ldirectord es ldirector.cf, ahí es dónde reflejaremos la estructura de nuestro clúster.

# Global Directives

checktimeout=10

checkinterval=2

autoreload=no

quiescent=yes

# A sample virtual with a fallback that will override the gobal setting

virtual=10.200.11.128:80

            real=10.200.10.129:80 masq

            real=10.200.10.130:80 masq

            #fallback=127.0.0.1:80 gate

            service=http

            request="/TestCluster"

            receive=".NET"

            scheduler=rr

            #persistent=600

            protocol=tcp

            checktype=negotiate

 

En ese fichero definimos el servidor virtual, 10.200.11.128:80 y los nodos reales (en este caso 2).  Con fallback podemos definir un servidor de respuesta en caso de que todos los nodos estén caídos.

Lo que me resultó curioso fue la forma de comprobar cada dos segundos (CheckInterval=2) qué nodos del cluster están activos. No envía un simple ping, en el caso de un servicio http o https hace una petición a una página de pruebas (request="/TestCluster") y busca una expresión regular en ella (receive=".NET"). En caso de no obtener respuesta (checktimeout=10) o no sea la esperada marca el servidor como caído y manda la petición HTTP a otro lado del cluster. El algoritmo de balanceo para este clúster es RoundRobin (rr), pero hay muchos más. Por ejemplo, si tenemos servidores con distinta capacidad podemos usar Weighted Round Robin (wrr) y darle pesos adecuados (entre 0 y 1) a los nodos. A menor peso, menos peticiones a resolver.

¿Y cómo sabemos que todo marcha?, fácil, tenemos unas cuantas herramientas para ello. La primera es mirar el syslog y ver que el deamon ldirectord ha arrancado perfectamente. Luego podemos utilizar ipvsadm, nos informará de los nodos definidos, su peso actual (0 – inactivo, 1- activo) y el número de conexiones activas.

Además pueden consultarse estadísticas que serían representables con software de monitorización.

#ipvsadm

IP Virtual Server version 1.0.11 (size=4096)

Prot LocalAddress:Port Scheduler Flags

  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn

TCP  10.200.11.128:www rr

  -> 10.200.10.129:www            Masq    1      1          0        

  -> 10.200.10.130:www            Masq    1      0          1         

#ipvsadm -Ln –stats

IP Virtual Server version 1.0.11 (size=4096)

Prot LocalAddress:Port               Conns   InPkts  OutPkts  InBytes OutBytes

  -> RemoteAddress:Port

TCP  10.200.11.128:80                   16     1105      536   316196   732713

  -> 10.200.10.129:80                    8      690      337   194315   468421

  -> 10.200.10.130:80                    8      415      199   121881   264292

 

En caso de problemas os recomiendo que utilicéis tcpdump, aunque no os conozcáis el protocolo IP al detalle os puede ayudar a resolver problemas en la configuración de red o de ldirectord.

Una vez que el LD está funcionando vamos a configurar los nodos para que puedan ejecutar aplicaciones ASP.NET en el clúster.

Una vez que el LD está funcionando vamos a configurar los nodos para que puedan ejecutar aplicaciones ASP.NET en el clúster.

 

Persistencia de sesión

La sesión de las aplicaciones web contienen el contexto de ejecución del cliente remoto. Supongo que es un concepto que conocereis todos ;-). La sesión se almacena en la memoria del servidor, el cliente remoto utiliza cookies o parámetros en el POST para identificarse en el servidor en cada petición y que éste sepa asociar los contenidos de una sesión determinada con el cliente correcto.

Lo interesante es que en un clúster hay varios nodos y una petición http puede ser resuelta por cualquier nodo activo del clúster. Si el nodo del clúster no tiene en memoria los datos de la sesión del cliente, porque fue creada en otro nodo, tenemos un problema realmente grave ya que no se puede recuperar el contexto de la aplicación.

Existen dos enfoques para resolver peticiones http a una aplicación web dentro de un clúster y no tener el problema mencionado:

El primer enfoque utiliza afinidad de nodo (sticky sessions, persistent connections…) en esencia: si un nodo crea una sesión para un cliente, ese nodo resuelve todas las peticiones del cliente remoto para esa sesión.

Es una aproximación sencilla pero no hay tolerancia a fallos real. Si un nodo se cae, lo hace con las sesiones de sus clientes. Además si un nodo esta cargado los que hayan iniciado sesión allí no pueden moverse a otro nodo menos saturado.

En el segundo enfoque cualquier nodo del clúster puede responder la petición. Para eso es necesario que todos los nodos tengan acceso a los datos de las sesiones de los clientes.

Con ASP.NET es posible compartir la sesión de dos formas: en la memoria o en un SqlServer de un servidor al que preguntan los nodos para recuperar una sesión activa.

Esta aproximación sí es tolerante a fallos (respecto a los nodos del clúster) y mejora el rendimiento. Aunque hay un punto único de fallo. Para evitarlo sería necesario montar dos SqlServers para mantener la sesión con replicación.

En mi caso monté el gestor de sesiones en memoria dentro de un Server 2003. Basta con iniciar el servicio "Servicio de estado de ASP.NET". Si bien hay que tocar el registro de windows para indicarle que acepte conexiones remotas y reiniciar el servicio.

Concretamente hay que darle un valor positivo a la clave:

HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServicesaspnet_stateParametersAllowRemoteConnection

Ahí también podéis cambiarle el puerto por defecto, que es el 42424.

Ahora hay que decirle a las aplicaciones ASP.NET dónde deben gestionar la sesión.

Para .Net 1.1 hay que tocar el web.config de la aplicación y modificar la forma de mantener la sesión:

    <sessionState

            mode="StateServer"

            stateConnectionString="tcpip=10.200.10.131:42424"

            cookieless="false"

            timeout="20"

    />

Para probarlo cree una pequeña página .aspx que incrementa una variable de sesión en cada invocación e informa del nombre de la máquina que resuelve la petición junto con el código de sesión.

Esta aplicación debe montarse en el mismo directorio virtual de IIS en todos los nodos. En una ejecución típica y para el entorno descrito podemos obtener resultados como los que siguen.

Petición a http://10.200.11.128/TestCluster

Postback 1:

Fecha:  13/09/2006 10:19:06

Servidor: WINCLUSTER01 (10.200.10.129)

Sesión: epeh12552w2blr45u2nhur2e (nueva)

Timeout: 20 min.

Counter (sesión): 1

Postback 2:

Fecha: 13/09/2006 10:19:07

Servidor: WINCLUSTER02 (10.200.10.130)

Sesión: epeh12552w2blr45u2nhur2e (vieja)

Timeout: 20 min.

Counter (sesión): 2

En el primer post se crea una nueva sesión y la crea el nodo WINCLUSTER01. El contador, que se almacena en la sesión, comienza en 1. Se hace una recarga de la página y el segundo post es contestado por el nodo WINCLUSTER02. Se observa que la sesión es la misma y se ha incrementado el counter almacenado en la sesión.

Hice varias pruebas tumbando algún nodo del clúster y no hubo ningún problema. En cuanto el servidor Linux Director detecta que un nodo no le devuelve la página de pruebas le asigna un peso 0, lo descarta y busca otro nodo según el algoritmo de balanceo. El cliente sufre un retraso de 1 segundo pero recupera el flujo normal de la aplicación.

Conclusiones

El aumento de tráfico de un sitio web solo puede ser atendido con una solución basada en clústers. Lo ideal es garantizar alta disponibilidad y tolerancia a fallos. En este artículo se describe una solución de clustering para ASP.NET basada en tecnología heterogénea (Linux/OpenSource, Windows/Microsoft). Utilizando Linux/LVS puedes dedicar hardware relativamente antiguo como equipo LinuxDirector y evitarte alguna licencia de 2003 para hacer NLB.

Las ventajas de clustering son la alta disponibilidad y una tolerancia a fallos total o parcial (dependiendo del esquema elegido y los puntos de fallo únicos). Incluso con un número pequeño de usuarios, si la aplicación es crítica, merece la pena optar por un esquema como el presentado.

Queda para un futuro una prueba de rendimiento de NLB y LVS

Espero que os haya gustado! y si alguna parte os queda poco clara u oscura comentádmelo y amplío el post.

Las aplicaciones OpenSource también cascan

No parece una novedad, pero es que os estaba preparando un post sobre clustering con LinuxVirtualServer e IIS 6.0 y se me ocurrió hacerlo con el editor de textos AbiWord.. cuando tenía medio artículo escrito pulsé no sé que teclas y me salió un error irrecuperable y que si me apetecía informar a los programadores. No lo consideré necesario (no sea que ese proceso se cargase algo más)… perdí bastante texto… y eso que guarda una copia de seguridad automática (que ni te informa de que lo ha hecho o te la recupera al abrir de nuevo el programa).

Pues eso, que sigo con el Post… pero que me fío más del Word 2007 Beta2.

Chao chiquilines

 

 

Instalando Atlas CTP Julio 2006

Yo tengo imán para que las instalaciones de CTPs y BETAs casquen. La última es la de Atlas en su CTP de Julio 2006. Al ejecutar ASPNETAtlas.vsi en la instalación se recibe el mensaje de error «La longitud de la cadena no puede ser cero». El resultado final es que no instala / registra las plantillas de proyecto para VS.2005. Para solucionarlo debeis meter en vuestro registro las dos claves siguientes:


En:
HKEY_LOCAL_MACHINESOFTWAREMicrosoftMSEnvCommunityContentContentTypesVSTemplate


Meteis:
«ClassName»  con «VSContentInstaller.VSTemplateInstallerPage» y «DisplayName» con «#VSTemplateContentDisplayName»


Volveis a ejecutar ASPNETAtlas.vsi que en mi caso anda por la carpeta C:Archivos de programaMicrosoft ASP.NETAtlasv2.0.50727


Feliz Agosto!