Compartir vía


Solución de problemas de Azure Service Bus

En este artículo se exponen las técnicas de investigación de errores, la simultaneidad y los errores comunes de los tipos de credenciales en la biblioteca cliente Java de Azure Service Bus y los pasos para resolver estos errores.

Habilitación y configuración del registro

El SDK de Azure para Java ofrece un escenario de registro coherente que ayuda a solucionar los errores de la aplicación y a agilizar su resolución. Los registros generados capturan el flujo de una aplicación antes de alcanzar el estado terminal para ayudar a encontrar el problema raíz. Para obtener instrucciones sobre el registro, consulte Configuración del registro en el SDK de Azure para Java e Introducción a la resolución de problemas.

Además de habilitar el registro, cambiar el nivel de registro a VERBOSE o DEBUG ofrece información sobre el estado de la biblioteca. En las secciones siguientes se muestran las configuraciones de log4j2 y logback de ejemplo para reducir los excesivos mensajes cuando se habilita el registro detallado.

Configuración de Log4J 2

Consulte los pasos siguientes para configurar Log4J 2:

  1. Agregue las dependencias en el pom.xml con las del ejemplo de registro pom.xml, en la sección "Dependencias obligatorias para Log4j2".
  2. Agregue log4j2.xml a la carpeta src/main/resources.

Configuración de logback

Consulte los pasos siguientes para configurar logback:

  1. Agregue las dependencias en el pom.xml con las del ejemplo de registro pom.xml, en la sección "Dependencias obligatorias para logback".
  2. Agregue logback.xml a la carpeta src/main/resources.

Habilitación del registro de transporte de AMQP

Si no es suficiente habilitar el registro de cliente para diagnosticar los problemas, puede habilitar el registro en un archivo de la biblioteca AMQP subyacente, Qpid Proton-J. Qpid Proton-J usa java.util.logging. Puede habilitar el registro mediante la creación de un archivo de configuración con el contenido que aparece en la sección siguiente. O bien, configure proton.trace.level=ALL y las opciones de configuración que desee para la implementación de java.util.logging.Handler. Para conocer las clases de implementación y sus opciones, consulte Paquete java.util.logging en la documentación del SDK de Java 8.

Para realizar un seguimiento de los marcos de transporte de AMQP, cree la variable de entorno PN_TRACE_FRM=1.

Archivo logging.properties de ejemplo

El siguiente archivo de configuración registra la salida de nivel TRACE de Proton-J en el archivo proton-trace.log:

handlers=java.util.logging.FileHandler
.level=OFF
proton.trace.level=ALL
java.util.logging.FileHandler.level=ALL
java.util.logging.FileHandler.pattern=proton-trace.log
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.SimpleFormatter.format=[%1$tF %1$tr] %3$s %4$s: %5$s %n

Reducción de registros

Una forma de reducir los registros es cambiar el nivel de detalle. Otra manera consiste en agregar filtros que excluyan los registros de los paquetes de nombres del registrador como com.azure.messaging.servicebus o com.azure.core.amqp. Para obtener ejemplos, consulte los archivos XML en las secciones Configurar Log4J 2 y Configurar logback.

Al enviar un error, los mensajes de registro de las clases de los siguientes paquetes son interesantes:

  • com.azure.core.amqp.implementation
  • com.azure.core.amqp.implementation.handler
    • La excepción es que puede omitir el mensaje onDelivery en ReceiveLinkHandler.
  • com.azure.messaging.servicebus.implementation

Simultaneidad en ServiceBusProcessorClient

ServiceBusProcessorClient permite a la aplicación configurar el número de llamadas al controlador de mensajes que deben producirse simultáneamente. Esta configuración permite procesar varios mensajes en paralelo. En un ServiceBusProcessorClient que consume mensajes de una entidad que no es de sesión, la aplicación puede configurar la simultaneidad deseada mediante la API maxConcurrentCalls. En una entidad habilitada para sesión, la simultaneidad deseada es maxConcurrentSessions veces maxConcurrentCalls.

Si la aplicación observa menos llamadas simultáneas al controlador de mensajes que la simultaneidad configurada, puede deberse a que el grupo de subprocesos no tiene el tamaño adecuado.

ServiceBusProcessorClient usa subprocesos residentes del grupo de subprocesos global boundedElastic de Reactor para invocar el controlador de mensajes. El número máximo de subprocesos simultáneos de este grupo tiene un límite determinado. De forma predeterminada, este límite es diez veces el número de núcleos de CPU disponibles. Para que ServiceBusProcessorClient admita eficazmente la simultaneidad deseada de la aplicación (maxConcurrentCalls o maxConcurrentSessions veces maxConcurrentCalls), debe tener un valor de límite de grupo boundedElastic mayor que la simultaneidad deseada. Puede invalidar el límite predeterminado creando la propiedad del sistema reactor.schedulers.defaultBoundedElasticSize.

Debe ajustar el grupo de subprocesos y la asignación de CPU uno por uno. Sin embargo, al invalidar el límite del grupo, como punto de partida, se limitan los subprocesos simultáneos a aproximadamente 20-30 por núcleo de CPU. Se recomienda limitar la simultaneidad deseada por instancia de ServiceBusProcessorClient a aproximadamente 20-30. Haga un perfil y analice el caso práctico específico y ajuste los elementos de simultaneidad como corresponda. En situaciones de cargas altas, considere la posibilidad de ejecutar varias instancias de ServiceBusProcessorClient en las que cada una se compila a partir de una nueva instancia de ServiceBusClientBuilder. Además, piense también en ejecutar cada uno ServiceBusProcessorClient en un host dedicado (por ejemplo, un contenedor o una máquina virtual) para que el tiempo de inactividad de un host no afecte al procesamiento general de los mensajes.

Tenga en cuenta que elegir un valor alto para el límite de grupo en un host con pocos núcleos de CPU tendría efectos adversos. Algunas señales de recursos de CPU bajos o un grupo con demasiados subprocesos en menos CPU dan lugar a: tiempos de espera frecuentes, bloqueos perdidos, interbloqueos o menor rendimiento. Si ejecuta la aplicación Java en un contenedor, se recomienda usar dos o más núcleos de vCPU. No se recomienda seleccionar menos de 1 núcleo de vCPU al ejecutar la aplicación Java en entornos en contenedores. Para obtener recomendaciones detalladas sobre el recursos, consulte Incluir aplicaciones Java en contenedores).

Cuello de botella de uso compartido de conexiones

Todos los clientes creados a partir de una instancia compartida de ServiceBusClientBuilder comparten la misma conexión con el espacio de nombres de Service Bus.

El uso de una conexión compartida permite realizar operaciones de multiplexación entre clientes en una conexión, pero el uso compartido también puede formar un cuello de botella si hay muchos clientes o los clientes generan una carga elevada. Cada conexión tiene un subproceso de E/S asociado. Al compartir la conexión, los clientes ponen su trabajo en la cola de trabajo de este subproceso de E/S compartido y el progreso de cada cliente depende de la finalización de su trabajo en la cola. El subproceso de E/S controla el trabajo en cola en serie. Es decir, si la cola de trabajo del subproceso de E/S de una conexión compartida termina con una gran cantidad de trabajo pendiente que cubrir, los síntomas son similares a los de una CPU baja. Esta condición se describe en la sección anterior sobre simultaneidad: por ejemplo, los clientes se detienen, se agota el tiempo de espera, se pierden bloqueos o se produce ralentización en la ruta de recuperación.

El SDK de Service Bus usa el patrón de nomenclatura reactor-executor-* para el subproceso de E/S de conexión. Cuando la aplicación tiene un cuello de botella de la conexión compartida, es posible que se refleje en el uso de la CPU del subproceso de E/S. Además, en el volcado de montón o en la memoria activa, el objeto ReactorDispatcher$workQueue es la cola de trabajo del subproceso de E/S. Una larga cola de trabajo en la instantánea de memoria mientras dura el cuello de botella podría indicar que el subproceso de E/S compartido está sobrecargado con trabajos pendientes.

Por lo tanto, si la carga de la aplicación en un punto de conexión de Service Bus es razonablemente alta en términos del número total de mensajes recibidos y enviados o el tamaño de la carga, debe usar una instancia de generador independiente por cada cliente que compile. Por ejemplo, en cada entidad, cola o tema, puede crear un nuevo ServiceBusClientBuilder y compilar un cliente a partir de ella. En el caso de una carga extremadamente alta en una entidad específica, es posible que quiera crear varias instancias de cliente para esa entidad o ejecutar clientes en varios hosts (por ejemplo, contenedores o máquinas virtuales) para equilibrar la carga.

Detención de los clientes al usar el punto de conexión personalizado de Application Gateway

La dirección del punto de conexión personalizado hace referencia a la dirección de un punto de conexión HTTPS facilitada por la aplicación que se puede resolver en Service Bus o configurada para redirigir el tráfico a Service Bus. Azure Application Gateway facilita la creación de un front-end basado en HTTPS que reenvía el tráfico a Service Bus. Puede configurar el SDK de Service Bus para que una aplicación use una dirección IP de front-end de Application Gateway como punto de conexión personalizado para conectarse a Service Bus.

Application Gateway ofrece varias directivas de seguridad que admiten diferentes versiones del protocolo TLS. Hay directivas predefinidas que aplican TLSv1.2 como versión mínima y también existen directivas antiguas con TLSv1.0 como versión mínima. El front-end basado en HTTPS tendrá aplicada una directiva de TLS.

En estos momentos, el SDK de Service Bus no reconoce determinadas finalizaciones remotas de TCP a través del front-end de Application Gateway, que usa TLSv1.0 como versión mínima. Por ejemplo, si el front-end envía TCP FIN, los paquetes de ACK para cerrar la conexión cuando se actualizan las propiedades, el SDK no puede detectarlo, por lo que no se volverá a conectar y los clientes ya no podrán enviar ni recibir mensajes. Este tipo de detención solo se produce cuando se usa TLSv1.0 como versión mínima. Para mitigarlo, use una directiva de seguridad con TLSv1.2 o posterior como versión mínima para el front-end de Application Gateway.

Ya se ha anunciado que la compatibilidad con TLSv1.0 y 1.1 en todos los servicios de Azure va a finalizar el 31 de octubre de 2024, por lo que se recomienda realizar la transición a TLSv1.2.

Se pierde el bloqueo de mensajes o sesión

Una suscripción de tema o cola de Service Bus tiene una duración de bloqueo fijada en el recurso. Cuando el cliente receptor extrae un mensaje del recurso, el agente de Service Bus aplica un bloqueo inicial al mensaje. El bloqueo inicial tarda lo que dura el bloqueo fijado en el recurso. Si el bloqueo de mensaje no se renueva antes de que expire, el agente de Service Bus liberará el mensaje para que esté disponible para otros receptores. Si la aplicación intenta completar o dejar un mensaje después de que expire el bloqueo, se producirá un error en la llamada API a com.azure.messaging.servicebus.ServiceBusException: The lock supplied is invalid. Either the lock expired, or the message has already been removed from the queue.

El cliente de Service Bus admite la ejecución de una tarea de renovación de bloqueo en segundo plano que renueva el bloqueo de mensajes continuamente cada vez que expira. De forma predeterminada, la tarea de renovación de bloqueo se ejecuta durante 5 minutos. Puede ajustar la duración de renovación del bloqueo mediante ServiceBusReceiverClientBuilder.maxAutoLockRenewDuration(Duration). Si pasa el valor Duration.ZERO, la tarea de renovación de bloqueo quedará deshabilitada.

En las listas siguientes se describen algunos de los patrones de uso o entornos de host que pueden causar el error de pérdida de bloqueo:

  • La tarea de renovación de bloqueo está deshabilitada y el tiempo de procesamiento de mensajes de la aplicación supera la duración del bloqueo fijada en el recurso.

  • El tiempo de procesamiento de mensajes de la aplicación supera la duración de la tarea de renovación de bloqueo configurada. Tenga en cuenta que, si la duración de renovación del bloqueo no se fija de forma explícita, lo predeterminado son 5 minutos.

  • La aplicación ha activado la característica Captura previa cambiando el valor de captura previa por un entero positivo mediante ServiceBusReceiverClientBuilder.prefetchCount(prefetch). Cuando la característica Captura previa está habilitada, el cliente recuperará el número de mensajes iguales a la captura previa de la entidad de Service Bus (cola o tema) y los almacenará en el búfer de captura previa en la memoria. Los mensajes permanecen en el búfer de captura previa hasta que se reciben en la aplicación. El cliente no amplía el bloqueo de los mensajes mientras están en el búfer de captura previa. Si el procesamiento de la aplicación tarda tanto que los bloqueos de mensajes expiran mientras permanecen en el búfer de captura previa, la aplicación podría obtener los mensajes con un bloqueo expirado. Para obtener más información, consulte ¿Por qué Captura previa no es la opción predeterminada?

  • El entorno de host tiene problemas de red ocasionales (por ejemplo, errores o interrupciones transitorios de red) que evitan que la tarea de renovación del bloqueo renueve el bloqueo a tiempo.

  • El entorno de host carece de suficientes CPU o tiene escasez intermitente de ciclos de CPU que retrasan la tarea de renovación de bloqueos a tiempo.

  • La hora del sistema host no es exacta (por ejemplo, el reloj está desfasado), lo que retrasa la tarea de renovación del bloqueo e impide que se ejecute a tiempo.

  • El subproceso de E/S de conexión está sobrecargado, lo que afecta a la capacidad de realizar llamadas de red de renovación de bloqueo a tiempo. Los siguientes dos escenarios pueden provocar este problema:

El número de tareas de renovación de bloqueo en el cliente es igual a los valores de parámetros maxMessages o maxConcurrentCalls establecidos en ServiceBusProcessorClient o ServiceBusReceiverClient.receiveMessages. Un gran número de tareas de renovación de bloqueos que realizan varias llamadas de red también puede tener un efecto adverso en la limitación del espacio de nombres de Service Bus.

Si el host no tiene recursos suficientes, el bloqueo aún puede perderse aunque solo se ejecuten algunas tareas de renovación de bloqueo. Si ejecuta la aplicación Java en un contenedor, se recomienda usar dos o más núcleos de vCPU. No se recomienda seleccionar menos de 1 núcleo de vCPU al ejecutar aplicaciones Java en entornos en contenedores. Para obtener recomendaciones detalladas sobre el recursos, consulte Incluir aplicaciones Java en contenedores).

Las mismas observaciones sobre los bloqueos también se aplican en una cola de Service Bus o una suscripción de tema que tenga habilitada la sesión. Cuando el cliente receptor se conecta a una sesión en el recurso, el agente aplica un bloqueo inicial a la sesión. Para mantener el bloqueo en la sesión, la tarea de renovación de bloqueo en el cliente debe dejar activada la renovación del bloqueo de sesión antes de que expire. En el caso de un recurso con la sesión habilitada, las particiones subyacentes a veces se mueven para equilibrar la carga entre los nodos de Service Bus, por ejemplo, cuando se agregan nuevos nodos para compartir la carga. Cuando esto sucede, se pueden perder bloqueos de sesión. Si la aplicación intenta completar o dejar un mensaje después de que se pierda el bloqueo de sesión, se producirá un error en la llamada API a com.azure.messaging.servicebus.ServiceBusException: The session lock was lost. Request a new session receiver.

Actualización a la versión 7.15.x o a la más reciente

Si tiene algún problema, intente resolverlo actualizando el SDK de Service Bus a su versión más reciente. La versión 7.15.x se ha rediseñado significativamente para corregir problemas de fiabilidad y rendimiento de mucho tiempo.

La versión 7.15.x y versiones posteriores reduce el salto de los subprocesos, elimina bloqueos, optimiza el código en rutas activas y reduce las asignaciones de memoria. Estos cambios mejoran el rendimiento entre 45 y 50 veces más en el ServiceBusProcessorClient.

La versión 7.15.x y posteriores también incluye varias mejoras de fiabilidad. Pone solución a varias condiciones de carrera (como la captura previa y los cálculos de créditos) y mejora el control de errores. Estos cambios dan lugar a una mejor fiabilidad cuando surgen problemas transitorios en varios tipos de cliente.

Uso de los clientes más recientes

La nueva plataforma subyacente con estas mejoras, en la versión 7.15.x y posteriores, se denomina V2-Stack. Esta versión incluye la generación anterior de la pila subyacente (la pila que usa la versión 7.14.x) y la nueva V2-Stack.

De forma predeterminada, algunos de tipos de cliente usan V2-Stack, mientras que otros necesitan la validación de V2-Stack. Puede realizar la validación o anulación de una pila específica (V2 o la generación anterior) para un tipo de cliente incluyendo valores com.azure.core.util.Configuration al compilar el cliente.

Por ejemplo, para la recepción de sesión basada en V2-Stack con ServiceBusSessionReceiverClient es necesario su validación, tal como se muestra en el ejemplo siguiente:

ServiceBusSessionReceiverClient sessionReceiver = new ServiceBusClientBuilder()
    .connectionString(Config.CONNECTION_STRING)
    .configuration(new com.azure.core.util.ConfigurationBuilder()
        .putProperty("com.azure.messaging.servicebus.session.syncReceive.v2", "true") // 'false' by default, opt-in for V2-Stack.
        .build())
    .sessionReceiver()
    .queueName(Config.QUEUE_NAME)
    .buildClient();

En la tabla siguiente figuran los tipos de cliente y los nombres de configuración correspondientes y se indica si el cliente puede usar de forma predeterminada V2-Stack en la versión 7.17.0 más reciente. Si un cliente no está en la pila V2-Stack de forma predeterminada, puede usar el ejemplo que se acaba de mostrar para validarla.

Tipo de cliente Nombre de la configuración ¿Está en la pila V2-Stack de forma predeterminada?
Remitente y cliente de administración com.azure.messaging.servicebus.sendAndManageRules.v2
Cliente receptor de reactor y de procesador sin sesión com.azure.messaging.servicebus.nonSession.asyncReceive.v2
Cliente receptor de procesador de sesión com.azure.messaging.servicebus.session.processor.asyncReceive.v2
Cliente receptor de reactor de sesión com.azure.messaging.servicebus.session.reactor.asyncReceive.v2
Cliente receptor sincrónico sin sesión com.azure.messaging.servicebus.nonSession.syncReceive.v2 no
Cliente receptor sincrónico con sesión com.azure.messaging.servicebus.session.syncReceive.v2 no

Aparte de usar com.azure.core.util.Configuration, tiene la opción de validar o anular estableciendo los mismos nombres de configuración mediante variables de entorno o propiedades del sistema.

Pasos siguientes

Si la guía de resolución de problemas de este artículo no le ayuda a resolver los problemas al usar bibliotecas cliente de Azure SDK para Java, le recomendamos que deje la incidencia en el repositorio GitHub de Azure SDK para Java.