Solución de problemas de Azure Service Bus

En este artículo se tratan las técnicas de investigación de errores, la simultaneidad y los errores comunes en la biblioteca cliente de Azure Service Bus Java. Use esta guía para identificar las causas principales y aplicar los pasos de mitigación para resolver Azure Service Bus problemas con mayor rapidez.

Habilitación y configuración del registro

El SDK de Azure para Java ofrece un sistema de registro coherente para ayudar a solucionar errores de aplicaciones y agilizar su resolución. Los registros que genera capturan el flujo de una aplicación antes de alcanzar el estado de terminal para ayudar a localizar el problema raíz. Para obtener instrucciones sobre el registro, consulte Configuración del registro en el SDK de Azure para Java y Visión general de la solución de problemas.

Además de habilitar el registro, establecer el nivel de registro en VERBOSE o DEBUG proporciona información sobre el estado de la biblioteca. En las siguientes secciones se muestran ejemplos de configuraciones de log4j2 y logback para reducir los mensajes excesivos cuando se habilita el registro verboso.

Configuración de Log4J 2

Siga estos pasos 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 necesarias para Log4j2".
  2. Agregue log4j2.xml a la carpeta src/main/resources.

Configuración de logback

Siga estos pasos para configurar la devolución de sesión:

  1. Agregue las dependencias de la pom.xml mediante las del ejemplo de registro pom.xml, en la sección "Dependencias necesarias para la devolución de sesión".
  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 se muestra en la sección siguiente. O bien, establezca proton.trace.level=ALL y las opciones de configuración que desee para la implementación de java.util.logging.Handler. Para ver las clases de implementación y sus opciones, consulte Package java.util.logging en la documentación del SDK de Java 8.

Para realizar un seguimiento de los marcos de transporte de AMQP, establezca 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 de las secciones Configure Log4J 2 y Configure 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 se puede ignorar el mensaje de onDelivery en ReceiveLinkHandler.
  • com.azure.messaging.servicebus.implementation

Simultaneidad en ServiceBusProcessorClient

ServiceBusProcessorClient permite configurar cuántas llamadas al controlador de mensajes se producen simultáneamente. Esta configuración permite procesar varios mensajes en paralelo. Para un ServiceBusProcessorClient que consume mensajes de una entidad sin sesión, puede configurar la concurrencia deseada mediante la API maxConcurrentCalls. Para una entidad con sesiones habilitadas, la concurrencia deseada es maxConcurrentSessions veces maxConcurrentCalls.

Si 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 de demonio 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 el ServiceBusProcessorClient admita eficazmente la simultaneidad deseada de la aplicación (maxConcurrentCalls o maxConcurrentSessions veces maxConcurrentCalls), es necesario que el valor máximo del grupo boundedElastic sea superior a la simultaneidad deseada. Puede invalidar el límite predeterminado estableciendo la propiedad del sistema reactor.schedulers.defaultBoundedElasticSize.

Ajuste el pool de hilos y la asignación de CPU según cada caso. 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. Limite la simultaneidad deseada por ServiceBusProcessorClient instancia a aproximadamente 20-30. Perfila y mide tu caso de uso específico, y ajusta los aspectos de concurrencia en consecuencia. Para escenarios de carga alta, considere la posibilidad de ejecutar varias instancias de ServiceBusProcessorClient en las que cada instancia se compila a partir de una nueva instancia de ServiceBusClientBuilder. Además, considere la posibilidad de ejecutar cada 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 mensajes.

Tenga en cuenta que establecer un valor alto para el límite del grupo en un host con pocos núcleos de CPU tiene 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, use dos o más núcleos de vCPU. No seleccione nada menos de 1 núcleo de vCPU al ejecutar Java aplicación en entornos contenedorizados. Para obtener recomendaciones detalladas sobre el recursos, consulte Incluir aplicaciones Java en contenedores).

Cuello de botella de uso compartido de conexiones

Todos los clientes que cree a partir de una instancia compartida ServiceBusClientBuilder comparten la misma conexión con el espacio de nombres 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 convertirse en 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. Cuando se comparte una conexión, los clientes colocan su trabajo en la cola de trabajo de este hilo de E/S compartido, y el progreso de cada cliente depende de que su trabajo en la cola se complete a tiempo. 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 acaba teniendo una gran cantidad de trabajo pendiente que procesar, los síntomas son similares a los de una escasez de CPU. 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 utiliza el patrón de nombres reactor-executor-* para el subproceso de conexión E/S. Cuando la aplicación experimenta el cuello de botella de la conexión compartida, puede reflejarse en el uso de 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 cola de trabajo larga en la instantánea de memoria durante el periodo de saturación podría indicar que el hilo de E/S compartido está sobrecargado con tareas 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 enviados o tamaño de carga, use una instancia de generador independiente para 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.

Los clientes se detienen al usar el punto de conexión personalizado de Application Gateway

La dirección del punto de conexión personalizado hace referencia a una dirección de punto de conexión HTTPS proporcionada por la aplicación que puede resolver para Service Bus o configurar para enrutar el tráfico a Service Bus. Azure Application Gateway facilita la creación de un front-end 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 TLS 1.2 como versión mínima y hay directivas anteriores que usan TLS 1.0 como versión mínima. Aplica una directiva TLS al front-end HTTPS.

Actualmente, el SDK de Service Bus no reconoce determinadas terminacións TCP remotas por el front-end de Application Gateway, que usa TLS 1.0 como versión mínima. Por ejemplo, si el front-end envía paquetes TCP FIN y ACK para cerrar la conexión cuando actualiza sus propiedades, el SDK no puede detectar estos paquetes. Por lo tanto, ya no se vuelve a conectar y los clientes ya no pueden enviar ni recibir mensajes. Esta detención solo se produce cuando se usa TLS 1.0 como versión mínima. Para mitigar este problema, use una directiva de seguridad que establezca TLS 1.2 o posterior como versión mínima para el front-end de Application Gateway.

La compatibilidad con TLS 1.0 y 1.1 en todos los servicios de Azure ya está anunciada para finalizar el 31 de octubre de 2024, por lo que la transición a TLS 1.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 se mantiene durante el periodo del bloqueo establecido en el nivel del recurso. Si el bloqueo de mensaje no se renueva antes de que expire, el agente de Service Bus libera el mensaje para que esté disponible para otros receptores. Si la aplicación intenta completar o abandonar un mensaje después de la expiración del bloqueo, se produce un error en la llamada API con el error 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 antes de que expire. 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 la siguiente lista se describen algunos de los patrones de uso o entornos de host que pueden provocar 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 establecida en el nivel de 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 establece explícitamente, el valor predeterminado es de 5 minutos.

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

  • El entorno de host tiene problemas de red ocasionales (por ejemplo, errores o interrupciones transitorios de la red) que impiden 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 precisa (por ejemplo, el reloj sesga), lo que retrasa la tarea de renovación del bloqueo y lo 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 dos escenarios siguientes pueden provocar este problema:

  • Un patrón de aplicación común que aumenta la probabilidad de un error de pérdida de bloqueo implica programar tareas de renovación de bloqueo de larga duración, por ejemplo, tareas con duraciones que abarcan varias horas. Como se mencionó anteriormente, varios factores fuera del control de un cliente de Service Bus pueden interferir con la renovación correcta del bloqueo, por lo que los diseños de aplicaciones deben evitar asumir la renovación garantizada durante períodos prolongados. Para evitar tener que volver a procesar operaciones de larga duración, considere la posibilidad de dividir el trabajo en fragmentos más pequeños o implementar la lógica de punto de control idempotente.

El número de tareas de renovación de bloqueo en el cliente es igual a los valores de parámetro maxMessages o maxConcurrentCalls establecidos para 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 todavía se puede perder aunque solo se ejecuten algunas tareas de renovación de bloqueo. Si ejecuta la aplicación Java en un contenedor, use dos o más núcleos de vCPU. No seleccione nada menos de 1 núcleo de vCPU al ejecutar Java aplicaciones 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 renovar continuamente el bloqueo de sesión antes de que expire. En el caso de un recurso habilitado para sesión, las particiones subyacentes a veces se mueven para lograr el equilibrio de 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 abandonar un mensaje después de perder el bloqueo de sesión, se produce un error en la llamada API con el error com.azure.messaging.servicebus.ServiceBusException: The session lock was lost. Request a new session receiver.

Pasos siguientes

Si la guía de solución de problemas de este artículo no ayuda a resolver problemas al usar la SDK de Azure para las bibliotecas cliente de Java, presente un problema en el SDK de Azure para Java GitHub repositorio.