¿Cómo recibir las noticias? Utilizando la tecnología JMS como solución para la replicación de datos
4 de junio de 2002 Este artículo proporciona una descripción general de cómo copiar archivos grandes utilizando Java Messaging System (JMS). Dan Drasin describe una solución al problema de datos distribuidos de un cliente de Applied Reasoning y proporciona detalles de implementación para una solución basada en JMS. Analiza las ventajas, algunos problemas potenciales y algunas instrucciones prácticas para configurar con éxito IBM MQSeries (ahora llamado WebSphere MQ) como un servidor JMS. Antecedentes Cuando piensa en soluciones de mensajería, puede pensar en un sistema que integra dos aplicaciones diferentes a través de un mecanismo de mensajería remota. En términos generales, este tipo de acoplamiento se utiliza a menudo para entidades distribuidas que no se comunican con frecuencia y cuando la cantidad de transmisión de datos no es grande. El ejemplo clásico son las interfaces homogéneas conectadas a backends y portales heterogéneos a los que se les asigna el procesamiento backend de las solicitudes de los usuarios y luego reformatean esas solicitudes para su presentación al usuario final. Los subprocesos públicos en los enfoques de mensajería siempre se han basado en el supuesto de que, si bien las soluciones de mensajería proporcionan una comunicación sólida y de alta disponibilidad entre sistemas, son esencialmente ineficientes y se utilizan solo como recurso cuando no se puede evitar la comunicación con partes externas. con el sistema. Esta visión de la mensajería se hizo popular con la llegada de la invocación de métodos remotos (RMC). Además, hasta la aparición de soluciones de mensajería más modernas como CORBA y DCOM, la mensajería normalmente aplicada se limitaba a resolver unos pocos tipos de problemas. Objetivos Durante la última década, ha habido una mayor comprensión de los requisitos para los sistemas distribuidos. Las tecnologías emergentes como Java y .NET ya incluyen la distribución de código como parte de su modelo de programación básico. Al hacerlo, estas tecnologías han incorporado alta disponibilidad y tolerancia a fallas en la mensajería, al tiempo que alientan a los proveedores que ofrecen soluciones a ofrecer sistemas que consideren el rendimiento en una gama más amplia de problemas. Recientemente, se le pidió a nuestra empresa que implementara soluciones de replicación y distribución de archivos, que anteriormente requerían sistemas personalizados que integraran FTP seguro, replicación de bases de datos y otras soluciones únicas. En lugar de simplemente seguir el camino del desarrollo personalizado, analizamos la posibilidad de aplicar las últimas soluciones de mensajería a este problema. Descubrimos que JMS no solo proporciona la infraestructura necesaria para la entrega de información, sino que también maneja todos los problemas de infraestructura que nuestros clientes requieren con respecto a la calidad del servicio, la seguridad, la confiabilidad y el rendimiento. Este artículo describe los desafíos que enfrentó nuestro equipo y cómo JMS (en forma de MQSeries) nos permitió cumplir y superar los requisitos de nuestros clientes. Problema Nuestro cliente enfrentó un importante desafío de datos distribuidos con muchos centros de llamadas en todo el país donde los operadores registraban las interacciones con los clientes. Estos registros deben indexarse y archivarse en centros de datos remotos de forma rápida y confiable. La indexación y el archivo de procedimientos almacenados no deben afectar la capacidad del sistema del operador para registrar y almacenar información sobre la interacción del operador con el cliente. El cliente ya tenía un sistema que incluía código, VPN y otras tecnologías combinadas. Sin embargo, la solución existente está muy por debajo de los objetivos de rendimiento y confiabilidad, y es una tecnología deficiente, difícil de entender y costosa de mantener. Al desarrollar un reemplazo para el sistema heredado de un cliente, consideramos JMS y una variedad de soluciones que no son JMS, particularmente aquellas basadas en FTP y Copia Segura (SCP). Sin embargo, las soluciones que no son JMS tienen dos desventajas principales: son inmunes a fallas de seguridad. (Las vulnerabilidades de seguridad en FTP son bien conocidas y están ampliamente documentadas. Si necesita un ejemplo de esto, consulte Recursos). La infraestructura que proporcionan es adecuada solo para la transferencia de datos real, para abordar problemas como confiabilidad, tolerancia a fallas, seguridad, plataforma. independencia y optimización del rendimiento, se requiere un desarrollo personalizado.
Nuestro equipo finalmente concluyó que el esfuerzo de desarrollo requerido para agregar estas características adicionales era prohibitivo, por lo que nos decidimos por una solución JMS que eliminaría estos problemas. Solución Desarrollamos un sistema basado en JMS que: Proporciona un archivado confiable de archivos multimedia grabados Admite escalabilidad para permitir que múltiples centros de datos reciban archivos Admite el archivado de otros tipos de datos Los archivos que analizamos aquí son más grandes que antes Los datos transferidos en proyectos que involucran mensajería soluciones es aún mayor (50K - 500K). Nuestra primera tarea es garantizar que el tamaño de los datos no afecte la solución JMS. Evaluamos una serie de soluciones JMS, incluida IBM MQSeries, probando el rendimiento del sistema al entregar cargas útiles de mensajes de varios tamaños. Los resultados muestran que, configurados correctamente, los mensajes de hasta 1 megabyte de tamaño no tienen un impacto significativo en el rendimiento general del sistema. Debido a que el sentido común sostiene que las soluciones de mensajería solo son adecuadas para cargas pequeñas y periódicas, nuestros resultados son un hallazgo importante. Continuamos analizando la arquitectura del sistema (descrita en la Figura 1) que puede proporcionar la seguridad, alta disponibilidad y confiabilidad que los clientes requieren. Figura 1. Arquitectura del sistema de alto nivel La infraestructura existente tiene un sistema en cada cliente que crea archivos multimedia en respuesta a las interacciones entre operadores y usuarios. Además, estos documentos deben archivarse. Nuestro sistema inicia un proceso (que se ejecuta en cada máquina) y busca estos archivos en directorios conocidos. Cuando se detectan archivos nuevos, el proceso los empaqueta en una carga útil JMS y los envía a un servidor JMS en uno de los centros de datos para su entrega. Una vez que el servidor JMS acusa recibo, estos archivos se eliminan del remitente. El servidor JMS transfiere los datos a un controlador disponible en el centro de datos para su archivo. Conceptos clave JMS es una implementación de mensajería y colas específica de Java. Hay dos ideas básicas en mensajería y colas: los sistemas se comunican mediante paquetes discretos, que tienen una carga útil (es decir, la información que se va a transferir) y atributos (es decir, las características de esa información y cómo debe comunicarse). Este paquete se llama mensaje. Los mensajes no se envían al sistema sino a un área de almacenamiento separada. La cantidad de áreas de guardado se puede determinar según sus necesidades, y mediante nombres únicos se pueden identificar y ubicar. Cada zona de guardado puede recibir mensajes y, según la configuración, la zona entrega cada mensaje a todos los sistemas interesados (publicación-suscripción) o al primer sistema interesado (punto a punto). Esta área guardada se llama destino. El sistema que construimos utiliza destinos punto a punto, llamados colas en JMS. Las colas son un aspecto importante del diseño del sistema que se muestra en la Figura 1. El diagrama muestra el mensaje entregado desde el intermediario JMS directamente al cliente del destinatario, pero esto no es del todo exacto. Efectivamente, los mensajes se entregan a una cola y el cliente receptor los recupera de la cola. Esta distinción será importante cuando veamos los detalles de implementación más adelante, porque permite que el sistema procese mensajes entrantes en paralelo. Multiplataforma y entre proveedores Para nuestros clientes, minimizar la dependencia de un proveedor significa que debemos diseñar nuestro código para minimizar el impacto de cambiar de proveedor de JMS. Esto es muy importante. Una de las principales ventajas de JMS es que se basa en un amplio soporte de la industria y estándares abiertos, por lo que con un código correctamente diseñado, podemos hacer que el sistema utilice cualquier sistema JMS. (Puede ser una actualización sencilla de un sistema existente diseñado para ejecutarse en un determinado conjunto de hardware y coincidir con una solución específica del proveedor). Al encapsular todas las llamadas específicas del proveedor en una Clase, puede lograr fácilmente la independencia de la plataforma. Estas clases de proveedor manejan problemas específicos del proveedor, como consultas de fábrica, manejo de errores, creación de conexiones y configuración de propiedades de mensajes. Consulte el código de muestra en el Listado 1 a continuación.
Listado 1. En la clase ar.jms.JmsProvider public QueueConnection createConnection() lanza JMSException { return getConnectionFactory().createQueueConnection(getUserName(), getPassword() } Al aprovechar la "Interfaz de directorio y nombres de Java (JNDI)"; Almacene la configuración específica del proveedor en un repositorio (por ejemplo, un repositorio LDAP) para que el código real requiera pocas referencias específicas del proveedor. Sólo se requiere una pequeña cantidad de código específico del proveedor para manejar algunas funciones, pero dicho código puede restringirse a algunas clases de "adaptadores" y mantenerse fuera del código de la aplicación. Consulte el código de muestra en el Listado 2 a continuación. Debido a que JMS fue diseñado para facilitar el uso de JNDI, esta es otra ventaja inmediata sobre otras soluciones: el almacenamiento centralizado de información de configuración puede contener no solo información basada en texto, sino también objetos configurados.
Listado 2. En la clase ar.jms.JmsProvider cadena estática final pública CONNECTION_FACTORY_LOOKUP_NAME_KEY = "CONNECTION_FACTORY_LOOKUP_NAME"; cadena estática final pública FILE_TRANSFER_QUEUE_LOOKUP_NAME_KEY = "FILE_TRANSFER_QUEUE_LOOKUP_NAME"; _CLASS"; public void init() lanza NamingException {InitialContext jndi = createInitialContext(); initConnectionFactory(jndi); initFileTransferQueue(jndi); } public QueueConnection createConnection() lanza JMSException {return getConnectionFactory().createQueueConnection(getUserName(), getPassword() } public void initConnectionFactory(InitialContext jndi) lanza NamingException { setConnectionFactory((QueueConnectionFactory)jndi.lookup (getProperties().getProperty(CONNECTION_FACTORY_LOOKUP_NAME_KEY)) } public void initFileTransferQueue(InitialContext jndi) lanza NamingException { setFileTransferQueue((Queue) jndi.lookup (getProperties().getProperty(FI LE_TRANSFER_QUEUE_LO OKUP_NAME_KEY ) )); } Rompiendo con el paradigma tradicional, las soluciones JMS permiten que los mensajes se entreguen de manera confiable, es decir, una vez que se confirma que el mensaje ha sido entregado al servidor JMS, se entrega al destino direccionado (cola). ). MQSeries no es una excepción. Una vez que el código que envía el mensaje al servidor se ejecuta con éxito, se garantiza al cliente que el destino eventualmente recibirá el mensaje, incluso si el servidor en cuestión falla durante el procesamiento (si el destino no está disponible temporalmente o el servidor JMS muere, etc.) . Consulte el código de muestra en el Listado 3 a continuación. La clase en el código siguiente es en realidad responsable de realizar el envío de los datos una vez que determina que necesita enviar el archivo. Al configurar un mensaje para que sea persistente, garantizamos que una vez que se recibe un mensaje en un destino (cola), permanecerá allí hasta que se recupere de esa cola, incluso durante fallas del sistema. Por lo tanto, una vez que el mensaje se entrega de forma segura al servidor JMS local, se puede eliminar. No se puede sobrestimar la capacidad de superar fallas del sistema; el manejo de fallas periódicas del sistema es una de las cuestiones más importantes en el desarrollo de soluciones de archivado distribuido.
El código para manejar las condiciones de falla en el sistema existente del cliente era complejo y frágil, y manejar y mantener estas fallas era costoso. JMS nos permite resolver todos estos problemas con una solución empresarial sólida y probada. gt; Listado 3. De la clase ar.jms.file.send.ConnectionElement public void sendMessage(byte[] carga útil, booleano persistente) lanza SendFailedException { QueueSender remitente = null; Mensaje mensaje = createMessage(remitente = createSender); persistente? DeliveryMode.PERSISTENT: DeliveryMode.NON_PERSISTENT); sender.send(message); getClient().getLogService().logInfo(getName() "mensaje enviado" message.getJMSMessageID() "."); ) { getClient().getLogService().logError ("Procesamiento de excepción JMS" getName(), excepción()); throw new SendFailedException("Error en el envío del mensaje JMS" } try { sender.close() } catch (JMSException ignorar) { getClient().getLogService().logInfo(getName() "no se pudo cerrar el remitente. El procesamiento continuará."); proporcionar un rendimiento y una calidad de servicio satisfactorios. La especificación JMS define opciones de configuración y las implementa en todas las soluciones comerciales. Sin embargo, el método exacto de configuración varía según el proveedor. Configuración La arquitectura y los sistemas que creamos son versátiles y potentes. Sin embargo, con algunas piezas móviles, es necesario configurarlas y engancharlas de la forma correcta. Lo que sigue es una descripción general, algunos errores potenciales e instrucciones prácticas para configurar correctamente MQSeries como servidor JMS. Para MQSeries, primero debe configurar un servidor JNDI para recuperar la configuración específica de la implementación, en este caso JMS Connection Factory. Hay muchas formas diferentes de lograr esto, pero una opción general adecuada es un servidor de Protocolo ligero de acceso a directorios (LDAP). Elegimos utilizar Qualcomm SLAPD. Una vez que el servidor esté instalado y en ejecución, puede utilizar la herramienta de administración MQSeries (JMSAdmin.bat) para configurar el servidor y utilizarlo como repositorio de objetos MQ. Consulte Recursos para obtener enlaces a libros útiles que cubren este proceso. Además, durante la instalación, es importante prestar especial atención a la documentación de IBM para configurar JMS sobre IBM MQSeries.
Este proceso implica la creación de algunas colas y otros objetos que son específicos del uso de JMS y no forman parte de una instalación estándar de MQSeries. Una vez que haya completado la configuración de los servidores JNDI/LDAP y JMS, estará listo para configurar el cliente. El primer paso es comprender cómo interactúa JMS con la implementación estándar MQSeries de IBM. El cliente Java de MQSeries puede interactuar en uno de dos modos: cliente y modo de enlace. El modo cliente solo está disponible a través de subprogramas Java y el modo de enlace depende de una DLL o biblioteca de objetos en la máquina cliente. Debido a la naturaleza de la implementación, sólo se puede utilizar el modo de enlace cuando se utiliza un servidor LDAP para información de conexión JMS. (No está claro por qué existe esta restricción, pero existe). Por lo tanto, almacene los inicios de sesión y las contraseñas de los usuarios en una ubicación global (com.ibm.mq.MQEnvironment.class) en lugar de pasarlos durante la conexión. Para resolver estos problemas de proveedores, creamos una subclase de la clase estándar JmsProvider (llamada MQSeriesProvider). Lo único que logrará esta clase es anular cómo crear la conexión. En lugar de llamar a newQueueConnection = getConnectionFactory().createQueueConnection(getUserName(), getPassword)); como en el Listado 1, tenemos que llamar a newQueueConnection = getConnectionFactory().createQueueConnection(). Finalmente, debe agregar elementos específicos de JMS, como por ejemplo. cola, administrador de colas, fábrica de colas, etc.) proporcionados al cliente. Ahora, la razón para usar LDAP y JNDI se vuelve obvia: usamos un servidor LDAP para almacenar estos elementos y un archivo externo para guardar las claves de esos objetos LDAP. El servidor LDAP puede actuar como un servidor JNDI y responder a consultas de nombres devolviendo objetos almacenados. Así es como funciona el código del Listado 2. El nombre del elemento JMS se puede obtener de una variable estática de la clase (para el nombre predeterminado) o de un archivo externo (con nombres distintos al predeterminado). En definitiva, se solicita al servidor LDAP el objeto almacenado en la clave (de la que estamos hablando) y se devuelve el objeto JMS que nos interesa (en este caso). Nuestra solución basada en JMS facilita la creación de un entorno de configuración unificado, multiplataforma y entre proveedores mediante el uso de componentes existentes. Nuestro código ahora es lo más independiente posible de las configuraciones específicas de la plataforma y del proveedor. Aplicación Hay dos componentes clave en una aplicación: el transmisor y el receptor. El remitente inicia un demonio que sondea el directorio en busca de archivos que deben archivarse, mientras que el receptor simplemente espera a que se entregue un mensaje JMS y archiva los archivos contenidos en ese mensaje. La API JMS nos permite definir estos componentes sin preocuparnos por la implementación JMS específica que se utiliza. El remitente consta de tres componentes principales: JMSProvider, que se utiliza para crear una conexión. ConnectionPool, que se utiliza para obtener conexiones inactivas existentes (lo llamamos JMSConnection); un programa de sondeo que monitorea los archivos que deben transmitirse. Al iniciar, utilice JMSProvider para crear algunas conexiones listas para el servidor JMS. Estas conexiones se colocan en un grupo y se inicia un sondeador. Cuando el sondeador detecta que es necesario transferir un archivo, crea un hilo separado para procesar el archivo. (Puede crear un hilo independiente para crear mensajes y realizar operaciones de transmisión bifurcando. Es muy simple describir el proceso. Pero en aplicaciones reales, la agrupación se usa a menudo en combinación con bucles, lo que garantiza que pocos creen nuevos hilos, reutilicen hilos en cambio.
Sin embargo, ese proceso es bastante complejo y demasiada explicación distraería la atención del tema central de este artículo: JMS. ) En un hilo separado, el encuestador obtiene la JMSConnection del grupo de conexiones, la usa para crear un BytesMessage y coloca el contenido binario del archivo en ese mensaje. Finalmente, el mensaje encuentra el receptor y se envía al servidor JMS, que luego devuelve la JMSConnection al ConnectionPool. Algunos de los pasos de este proceso de envío se muestran en la Figura 2 a continuación. Figura 2. Proceso del remitente El receptor es un componente más simple: inicia algunos FileListeners para esperar a que los mensajes se coloquen en la cola del receptor. El código del Listado 4 a continuación muestra el proceso de configuración de FileListener. La clase de la Figura 6 es en realidad responsable de recuperar mensajes de la cola y archivarlos. JMS garantiza que la cola envía cada mensaje exactamente una vez, por lo que podemos iniciar de forma segura muchos subprocesos FileListener diferentes y saber que cada mensaje (y por lo tanto cada archivo) se procesa solo una vez. Esta garantía es otra ventaja importante de utilizar una solución basada en JMS. Desarrollar dicha funcionalidad (como la funcionalidad basada en FTP) en una solución de diseño propio es costoso y propenso a errores.
Listado 4: De la clase ar.jms.file.receive.FileListener public void startOn(Queue queue) { setQueue(queue); createConnection(); try { createReceiver(); / esto inicia el escucha de cola } catch (excepción JMSException) { // Maneja la excepción } } public void createReceiver() throws javax.jms.JMSException { try { QueueReceiver receptor = getSession().createReceiver(getQueue() receptor); setMessageListener (this); } catch (excepción JMSException) { // Manejar la excepción } } public void createSession() lanza JMSException { setSession(getConnection().createQueueSession(false, Session.AUTO_ACKNOWLEDGE) } public void createConnection() { while (!hasConnection()) { try { setConnection(getClient().createConnection()); catch (excepción JMSException) { // Las conexiones se interrumpen periódicamente en Internet, inicia sesión e intenta nuevamente. ; } catch (java.lang.InterruptedException ignorado) { } } } } Escribe el código de procesamiento del mensaje en forma de devolución de llamada. La devolución de llamada es el método que JMS llama automáticamente cuando el mensaje se pasa al FileListener. El código para este mensaje se muestra en el Listado 5 a continuación.
Listado 5. De la clase ar.jms.file.receive.FileListener public void onMessage(Mensaje de mensaje) { BytesMessage byteMessage = ((BytesMessage) mensaje); OutputStream stream = new BufferedOutputStream( new FileOutputStream(getFilenameFor(message))); ] buffer = new byte[getFileBufferSize()]; int length = 0; try { while ((length = byteMessage.readBytes(buffer)) != -1) { stream.write(buffer, 0, length } secuencia). close (); } catch (excepción JMSException) { // Manejar la JMSException } catch (excepción IOException) { // Manejar la IOException } } Un truco para recordar al configurar receptores: asegúrese de iniciar estos FileListeners después de que se hayan iniciado todos los FileListeners. El hilo original continúa ejecutándose. Esto es necesario porque algunas implementaciones JMS inician QueueListener en el hilo del demonio. Por lo tanto, si el único subproceso en ejecución es el subproceso del demonio, la máquina virtual Java (JVM) puede cerrarse prematura e inesperadamente. El Listado 6 a continuación muestra un código simple para evitar que esto suceda. Listado 6. Mantenga al menos un hilo que no sea demonio ejecutándose public static void main(String[] args) { ReceiverClient newReceiverClient = new ReceiverClient(); Esto evita que la VM salga antes de tiempo try { Thread.sleep(1000 } catch (InterruptedException ex) { } } } Conclusión Después de la implementación inicial de este proyecto, agregamos algunas características como la compresión de mensajes, cuando no se puede alcanzar la ubicación Instantánea recuperación automática, intermediación de mensajes federados, seguridad, registro sólido, administración y más. Agregar estas capacidades es fácil debido al modelo abierto proporcionado por JMS y porque nuestra arquitectura es robusta. Se necesitaron seis semanas para construir todo el sistema y reemplazar rápidamente el sistema existente, que requiere mucha mano de obra, que el cliente había estado utilizando. Hoy en día, el sistema ha superado todos los estándares de referencia y ha corregido errores heredados del sistema original. Este proyecto no sólo superó las expectativas del cliente, sino que también demostró que JMS es una solución viable no sólo para aplicaciones pequeñas orientadas a mensajes, sino también para operaciones de transferencia de datos de misión crítica a gran escala.