mirror of
https://github.com/KeymonSoft/jRDC-Multi.git
synced 2026-04-18 21:29:29 +00:00
- VERSION 5.09.14
-feat: Implementación robusta de monitoreo de pool de conexiones y peticiones activas -Este commit resuelve problemas críticos en el monitoreo del pool de conexiones (C3P0) y el conteo de peticiones activas por base de datos, mejorando significativamente la visibilidad y fiabilidad del rendimiento del servidor jRDC2-Multi. -Problemas Identificados y Resueltos: -1. **Métricas de `BusyConnections` y `TotalConnections` inconsistentes o siempre en `0` en el `Manager` y `query_logs`:** * **Problema**: Anteriormente, la métrica `busy_connections` en `query_logs` a menudo reportaba `0` o no reflejaba el estado real. De manera similar, el panel de `Manager?command=totalcon` consistentemente mostraba `BusyConnections: 0` y `TotalConnections` estancadas en `InitialPoolSize`, a pesar de que Oracle sí reportaba conexiones activas. Esto generaba confusión sobre el uso real y la expansión del pool. * **Solución**: Se modificó la lógica en los *handlers* (`DBHandlerJSON.bas` y `DBHandlerB4X.bas`) para capturar la métrica `BusyConnections` directamente del pool de C3P0 **inmediatamente después de que el *handler* adquiere una conexión** (`con = Connector.GetConnection(finalDbKey)`). Este valor se pasa explícitamente a la subrutina `Main.LogQueryPerformance` para su registro en `query_logs` y para ser consumido por `Manager.bas` a través de `RDCConnector.GetPoolStats`. Esto garantiza que el valor registrado y reportado refleje con precisión el número de conexiones activas en el instante de su adquisición. Pruebas exhaustivas confirmaron que C3P0 sí reporta conexiones ocupadas y sí expande `TotalConnections` hasta `MaxPoolSize` cuando la demanda lo exige. -2. **Contador `handler_active_requests` no decrementaba correctamente:** * **Problema**: El contador de peticiones activas por base de datos (`GlobalParameters.ActiveRequestsCountByDB`) no mostraba un decremento consistente, resultando en un conteo que solo aumentaba o mostraba valores erráticos en los logs. * **Solución**: * Se aseguró la declaración `Public ActiveRequestsCountByDB As Map` en `GlobalParameters.bas`. * Se garantizó su inicialización como un `srvr.CreateThreadSafeMap` en `Main.AppStart` para un manejo concurrente seguro de los contadores. * En `DBHandlerJSON.bas`, la `dbKey` (obtenida del parámetro `dbx` del JSON) ahora se resuelve *antes* de incrementar el contador, asegurando que el incremento y el decremento se apliquen siempre a la misma clave de base de datos correcta. * Se implementó una coerción explícita a `Int` (`.As(Int)`) para todas las operaciones de lectura y escritura (`GetDefault`, `Put`) en `GlobalParameters.ActiveRequestsCountByDB`, resolviendo problemas de tipo que causaban inconsistencias y el fallo en el decremento. * La lógica de decremento en `Private Sub CleanupAndLog` (presente en ambos *handlers*) se hizo más robusta, verificando que el contador sea mayor que cero antes de decrementar para evitar valores negativos. -Beneficios de estos Cambios: * **Monitoreo Preciso y Fiable**: Las métricas `busy_connections` y `handler_active_requests` en `query_logs` y el panel `Manager` ahora son totalmente fiables, proporcionando una visión clara y en tiempo real del uso del pool de conexiones y la carga de peticiones activas por base de datos. * **Diagnóstico Mejorado**: La visibilidad interna del estado del pool de C3P0 durante las pruebas confirma que la configuración de `RDCConnector` es correcta y que el pool se expande y contrae según lo esperado por la demanda. * **Robustez del Código**: La gestión de contadores de peticiones activas es ahora consistente, thread-safe y a prueba de fallos de tipo, mejorando la estabilidad general del servidor bajo carga.
This commit is contained in:
180
DBHandlerB4X.bas
180
DBHandlerB4X.bas
@@ -35,100 +35,136 @@ End Sub
|
||||
|
||||
' Método principal que maneja cada petición HTTP que llega a este servlet.
|
||||
Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||
' === INICIO DE LA LÓGICA DINÁMICA ===
|
||||
' Extrae la URI completa de la petición (ej. /DB1/endpoint).
|
||||
' === INICIO DE LA LÓGICA DINÁMICA (Extracción de dbKey de la URL) ===
|
||||
Dim URI As String = req.RequestURI
|
||||
' Variable para almacenar la "llave" o identificador de la base de datos (ej. "DB1").
|
||||
Dim dbKey As String
|
||||
|
||||
' Comprueba si la URI tiene contenido y empieza con "/".
|
||||
Dim dbKey As String ' Usamos dbKey para consistencia con tu código original.
|
||||
If URI.Length > 1 And URI.StartsWith("/") Then
|
||||
' Extrae la parte de la URI que viene después del primer "/".
|
||||
dbKey = URI.Substring(1)
|
||||
' Si la llave contiene más "/", se queda solo con la primera parte.
|
||||
' Esto permite URLs como /DB1/clientes o /DB2/productos, extrayendo "DB1" o "DB2".
|
||||
dbKey = URI.Substring(1) '[DBHandlerB4X.bas.txt, 51]
|
||||
If dbKey.Contains("/") Then
|
||||
dbKey = dbKey.SubString2(0, dbKey.IndexOf("/"))
|
||||
dbKey = dbKey.SubString2(0, dbKey.IndexOf("/")) '[DBHandlerB4X.bas.txt, 51]
|
||||
End If
|
||||
Else
|
||||
' Si la URI está vacía o es "/", usa "DB1" como la base de datos por defecto.
|
||||
dbKey = "DB1"
|
||||
dbKey = "DB1" '[DBHandlerB4X.bas.txt, 51]
|
||||
End If
|
||||
dbKey = dbKey.ToUpperCase '[DBHandlerB4X.bas.txt, 52]
|
||||
|
||||
' Convierte la llave a mayúsculas para que no sea sensible a mayúsculas/minúsculas (ej. "db1" se convierte en "DB1").
|
||||
dbKey = dbKey.ToUpperCase
|
||||
|
||||
' Verifica si la llave de la base de datos extraída existe en la configuración de conectores.
|
||||
If Main.Connectors.ContainsKey(dbKey) = False Then
|
||||
' Si no existe, crea un mensaje de error claro.
|
||||
Dim ErrorMsg As String = $"Invalid DB key specified in URL: '${dbKey}'. Valid keys are: ${Main.listaDeCP}"$
|
||||
' Registra el error en el log del servidor.
|
||||
Log(ErrorMsg)
|
||||
' Envía una respuesta de error 400 (Bad Request) al cliente en formato de texto plano.
|
||||
SendPlainTextError(resp, 400, ErrorMsg)
|
||||
' Termina la ejecución de este método.
|
||||
If Main.Connectors.ContainsKey(dbKey) = False Then '[DBHandlerB4X.bas.txt, 52]
|
||||
Dim ErrorMsg As String = $"Invalid DB key specified in URL: '${dbKey}'. Valid keys are: ${Main.listaDeCP}"$ '[DBHandlerB4X.bas.txt, 52]
|
||||
Log(ErrorMsg) '[DBHandlerB4X.bas.txt, 52]
|
||||
SendPlainTextError(resp, 400, ErrorMsg) '[DBHandlerB4X.bas.txt, 52]
|
||||
' Aquí no se necesita CleanupAndLog, ya que el contador no se ha incrementado
|
||||
' y no se ha obtenido ninguna conexión del pool aún.
|
||||
Return
|
||||
End If
|
||||
' === FIN DE LA LÓGICA DINÁMICA ===
|
||||
|
||||
Log("********************* " & dbKey & " ********************")
|
||||
' Guarda el tiempo de inicio para medir la duración de la petición.
|
||||
Dim start As Long = DateTime.Now
|
||||
' Variable para almacenar el nombre del comando SQL a ejecutar.
|
||||
Dim q As String
|
||||
' Obtiene el stream de entrada de la petición, que contiene los datos enviados por el cliente.
|
||||
Dim in As InputStream = req.InputStream
|
||||
' Obtiene el parámetro "method" de la URL (ej. ?method=query2).
|
||||
Dim method As String = req.GetParameter("method")
|
||||
' Obtiene el conector correspondiente a la base de datos seleccionada.
|
||||
Connector = Main.Connectors.Get(dbKey)
|
||||
' Declara la variable para la conexión a la base de datos.
|
||||
Dim con As SQL
|
||||
Try
|
||||
' Obtiene una conexión del pool de conexiones.
|
||||
con = Connector.GetConnection(dbKey)
|
||||
Log("Metodo: " & method)
|
||||
' Determina qué función ejecutar basándose en el parámetro "method".
|
||||
Log("********************* " & dbKey & " ********************") '[DBHandlerB4X.bas.txt, 53]
|
||||
|
||||
Dim start As Long = DateTime.Now '[___new 3.txt, 203]
|
||||
|
||||
' --- INICIO: Conteo de peticiones activas para esta dbKey (Incrementar) ---
|
||||
Dim currentActiveRequests As Int = GlobalParameters.ActiveRequestsCountByDB.GetDefault(dbKey, 0) '[___new 3.txt, 205]
|
||||
GlobalParameters.ActiveRequestsCountByDB.Put(dbKey, currentActiveRequests + 1) '[___new 3.txt, 205]
|
||||
Dim requestsBeforeDecrement As Int = currentActiveRequests + 1 '[___new 3.txt, 207]
|
||||
' --- FIN: Conteo de peticiones activas ---
|
||||
|
||||
' Declaraciones de variables con alcance en toda la subrutina para la limpieza.
|
||||
Dim q As String = "unknown_b4x_command" ' Nombre del comando para el log, con valor por defecto.
|
||||
Dim con As SQL ' La conexión a la BD, se inicializará más tarde.
|
||||
Dim duration As Long ' La duración de la petición, calculada antes del log.
|
||||
Dim poolBusyConnectionsForLog As Int = 0 ' Contiene el número de conexiones ocupadas del pool.
|
||||
|
||||
Try ' --- INICIO: Bloque Try que envuelve la lógica principal del Handler ---
|
||||
Dim in As InputStream = req.InputStream '[DBHandlerB4X.bas.txt, 53]
|
||||
Dim method As String = req.GetParameter("method") '[DBHandlerB4X.bas.txt, 53]
|
||||
Connector = Main.Connectors.Get(dbKey) '[DBHandlerB4X.bas.txt, 54]
|
||||
|
||||
con = Connector.GetConnection(dbKey) ' La conexión a la BD se obtiene aquí. [DBHandlerB4X.bas.txt, 54]
|
||||
|
||||
' <<<< ¡BUSY_CONNECTIONS YA SE CAPTURABA BIEN! >>>>
|
||||
If Connector.IsInitialized Then
|
||||
Dim poolStats As Map = Connector.GetPoolStats '[___new 3.txt, 204]
|
||||
If poolStats.ContainsKey("BusyConnections") Then
|
||||
poolBusyConnectionsForLog = poolStats.Get("BusyConnections") ' Capturamos el valor.
|
||||
End If
|
||||
End If
|
||||
' <<<< ¡FIN DE CAPTURA! >>>>
|
||||
|
||||
Log("Metodo: " & method) '[DBHandlerB4X.bas.txt, 54]
|
||||
|
||||
If method = "query2" Then
|
||||
' Ejecuta una consulta usando el protocolo más nuevo (B4XSerializator).
|
||||
q = ExecuteQuery2(dbKey, con, in, resp)
|
||||
q = ExecuteQuery2(dbKey, con, in, resp) '[DBHandlerB4X.bas.txt, 54]
|
||||
If q = "error" Then ' Si ExecuteQuery2 devolvió un error de validación.
|
||||
duration = DateTime.Now - start
|
||||
CleanupAndLog(dbKey, "error_in_" & method, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||
Return
|
||||
End If
|
||||
'#if VERSION1
|
||||
Else if method = "query" Then
|
||||
' Protocolo antiguo: descomprime el stream y ejecuta la consulta.
|
||||
in = cs.WrapInputStream(in, "gzip")
|
||||
q = ExecuteQuery(dbKey, con, in, resp)
|
||||
q = ExecuteQuery(dbKey, con, in, resp) '[DBHandlerB4X.bas.txt, 55]
|
||||
If q = "error" Then
|
||||
duration = DateTime.Now - start
|
||||
CleanupAndLog(dbKey, "error_in_" & method, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||
Return
|
||||
End If
|
||||
Else if method = "batch" Then
|
||||
' Protocolo antiguo: descomprime el stream y ejecuta un lote de comandos.
|
||||
in = cs.WrapInputStream(in, "gzip")
|
||||
q = ExecuteBatch(dbKey, con, in, resp)
|
||||
q = ExecuteBatch(dbKey, con, in, resp) '[DBHandlerB4X.bas.txt, 55]
|
||||
If q = "error" Then
|
||||
duration = DateTime.Now - start
|
||||
CleanupAndLog(dbKey, "error_in_" & method, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||
Return
|
||||
End If
|
||||
'#end if
|
||||
Else if method = "batch2" Then
|
||||
' Ejecuta un lote de comandos usando el protocolo más nuevo.
|
||||
q = ExecuteBatch2(dbKey, con, in, resp)
|
||||
q = ExecuteBatch2(dbKey, con, in, resp) '[DBHandlerB4X.bas.txt, 55]
|
||||
If q = "error" Then
|
||||
duration = DateTime.Now - start
|
||||
CleanupAndLog(dbKey, "error_in_" & method, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||
Return
|
||||
End If
|
||||
Else
|
||||
' Si el método es desconocido, lo registra y envía un error.
|
||||
Log("Unknown method: " & method)
|
||||
SendPlainTextError(resp, 500, "unknown method")
|
||||
Log("Unknown method: " & method) '[DBHandlerB4X.bas.txt, 56]
|
||||
SendPlainTextError(resp, 500, "unknown method") '[DBHandlerB4X.bas.txt, 56]
|
||||
q = "unknown_method_handler" ' Aseguramos un valor para q en el log.
|
||||
duration = DateTime.Now - start
|
||||
CleanupAndLog(dbKey, q, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||
Return
|
||||
End If
|
||||
Catch
|
||||
' Si ocurre cualquier error en el bloque Try, lo captura.
|
||||
Log(LastException)
|
||||
' Envía un error 500 (Internal Server Error) al cliente con el mensaje de la excepción.
|
||||
SendPlainTextError(resp, 500, LastException.Message)
|
||||
End Try
|
||||
' Asegura que la conexión a la BD se cierre y se devuelva al pool.
|
||||
If con <> Null And con.IsInitialized Then con.Close
|
||||
' Registra en el log el comando ejecutado, cuánto tiempo tardó y la IP del cliente.
|
||||
Log($"Command: ${q}, took: ${DateTime.Now - start}ms, client=${req.RemoteAddress}"$)
|
||||
|
||||
' *** NUEVO: Insertar el log en la base de datos SQLite ***
|
||||
Dim duration As Long = DateTime.Now - start
|
||||
Try
|
||||
Main.SQL1.ExecNonQuery2("INSERT INTO query_logs (query_name, duration_ms, timestamp, db_key, client_ip) VALUES (?, ?, ?, ?, ?)", _
|
||||
Array As Object(q, duration, DateTime.Now, dbKey, req.RemoteAddress))
|
||||
Catch
|
||||
Log("Error al guardar log de query en SQLite (DBHandlerB4X): " & LastException.Message)
|
||||
End Try
|
||||
Catch ' --- CATCH: Maneja errores generales de ejecución o de SQL ---
|
||||
Log(LastException) '[DBHandlerB4X.bas.txt, 56]
|
||||
SendPlainTextError(resp, 500, LastException.Message) '[DBHandlerB4X.bas.txt, 56]
|
||||
q = "error_in_b4x_handler" ' Aseguramos un valor para q en el log si hay excepción.
|
||||
End Try ' --- FIN: Bloque Try principal ---
|
||||
|
||||
' --- Lógica de logging y limpieza final (para rutas de ejecución normal o después de Catch) ---
|
||||
duration = DateTime.Now - start '[DBHandlerB4X.bas.txt, 57]
|
||||
Log($"Command: ${q}, took: ${duration}ms, client=${req.RemoteAddress}"$) '[DBHandlerB4X.bas.txt, 57]
|
||||
CleanupAndLog(dbKey, q, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||
|
||||
End Sub
|
||||
|
||||
' --- NUEVA SUBRUTINA: Centraliza el logging y la limpieza ---
|
||||
Private Sub CleanupAndLog(dbKey As String, qName As String, durMs As Long, clientIp As String, handlerReqs As Int, poolBusyConns As Int, conn As SQL)
|
||||
' 1. Llama a la subrutina centralizada para registrar el rendimiento.
|
||||
Main.LogQueryPerformance(qName, durMs, dbKey, clientIp, handlerReqs, poolBusyConns) '[___new 3.txt, 207]
|
||||
|
||||
' <<<< ¡CORRECCIÓN CLAVE AQUÍ! >>>>
|
||||
' 2. Decrementa el contador de peticiones activas para esta dbKey de forma más robusta.
|
||||
Dim currentCount As Int = GlobalParameters.ActiveRequestsCountByDB.GetDefault(dbKey, 0)
|
||||
If currentCount > 0 Then
|
||||
GlobalParameters.ActiveRequestsCountByDB.Put(dbKey, currentCount - 1)
|
||||
Else
|
||||
' Si el contador ya está en 0 o negativo, registramos una advertencia y lo aseguramos en 0.
|
||||
Log($"ADVERTENCIA: Intento de decrementar ActiveRequestsCountByDB para ${dbKey} que ya estaba en ${currentCount}. Asegurando a 0."$)
|
||||
GlobalParameters.ActiveRequestsCountByDB.Put(dbKey, 0)
|
||||
End If
|
||||
' <<<< ¡FIN DE CORRECCIÓN CLAVE! >>>>
|
||||
|
||||
' 3. Asegura que la conexión a la BD siempre se cierre y se devuelva al pool.
|
||||
If conn <> Null And conn.IsInitialized Then conn.Close
|
||||
End Sub
|
||||
|
||||
' Ejecuta una consulta única usando el protocolo V2 (B4XSerializator).
|
||||
|
||||
Reference in New Issue
Block a user