Solución de problemas de Azure Service Bus
En este artículo se tratan las técnicas de investigación de errores, la simultaneidad, los errores comunes para los tipos de credenciales de la biblioteca cliente de Java de Azure Service Bus y los pasos de mitigación para resolver estos errores.
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 generados capturan el flujo de una aplicación antes de alcanzar el estado terminal para ayudar a localizar el problema raíz. Para obtener instrucciones sobre el registro, consulte Configuración del registro en el Azure SDK 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:
- Agregue las dependencias en su pom.xml utilizando las del ejemplo de log pom.xml, en la sección "Dependencias necesarias para Log4j2".
- Agregue
log4j2.xml a la carpetasrc/main/resources.
Configuración de logback
Siga estos pasos para configurar la devolución de sesión:
- Agregue las dependencias en el pom.xml con las del ejemplo de registro pom.xml, en la sección "Dependencias obligatorias para logback".
- Agregue
logback.xml a la carpetasrc/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
enReceiveLinkHandler
.
- La excepción es que se puede ignorar el mensaje de
com.azure.messaging.servicebus.implementation
Simultaneidad en ServiceBusProcessorClient
ServiceBusProcessorClient
permite a la aplicación configurar cuántas llamadas al controlador de mensajes deben producirse simultáneamente. Esta configuración permite procesar varios mensajes en paralelo. Para que un ServiceBusProcessorClient
consuma mensajes de una entidad sin sesión, la aplicación puede configurar la simultaneidad deseada a través de la API de 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 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
.
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 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 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 nada 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 de ServiceBusClientBuilder
compartida 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 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. 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 utiliza el patrón de nombres reactor-executor-*
para el subproceso de conexión E/S. 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 de número total de mensajes recibidos enviados o tamaño de carga, debe usar 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 se puede resolver en Service Bus o configurada 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 TLSv1.2 como versión mínima, también existen directivas antiguas con TLSv1.0 como versión mínima. El front-end HTTPS tendrá aplicada una directiva TLS.
En este momento, el SDK de Service Bus no reconoce determinadas finalizaciones TCP remotas por el 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 ACK para cerrar la conexión cuando se actualizan sus 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 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 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 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 ha activado 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 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 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 los mensajes expiran mientras están en el búfer de prefetch, la aplicación podría adquirir mensajes cuyo bloqueo haya 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:
- La aplicación ejecuta demasiados clientes receptores que comparten la misma conexión. Para obtener más información, consulte la sección Cuello de botella de uso compartido de conexiones.
- La aplicación ha configurado
ServiceBusReceiverClient.receiveMessages
oServiceBusProcessorClient
para tener valores demaxMessages
omaxConcurrentCalls
grandes. Para obtener más información, consulte la sección Simultaneidad en ServiceBusProcessorClient.
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, se recomienda usar dos o más núcleos de vCPU. No se recomienda seleccionar nada 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 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 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.