Monitorizando Pool de Hilos en Java con MBeans

This blog entry is also available in English

Cuando trabajamos con aplicaciones Java que utilizan pools de hilos nativos, una de las limitaciones que podemos encontrar es la falta de herramientas integradas para monitorizar el comportamiento de estos pools. Aunque bibliotecas como ThreadPoolExecutor en Java ofrecen potentes funcionalidades para gestionar hilos, no exponen directamente métricas para herramientas de monitorización como JMX.

En este blog, comparto cómo poder aprovechar los MBeans para exponer métricas personalizadas de un pool de hilos. Esto nos permitirá monitorizar en tiempo real aspectos como el número de hilos activos, el tamaño del pool y el tamaño de la cola de tareas de los mismos.


¿Por qué usar MBeans?

Los MBeans (Managed Beans) son un mecanismo integrado en Java (estándar) para exponer información sobre el comportamiento y estado de una aplicación. Usar MBeans tiene varias ventajas:

  • Monitorización en Tiempo Real: Podemos consultar las métricas desde herramientas como JConsole, VisualVM, Glowroot, Dynatrace, etc.

  • Flexibilidad: Nos permite exponer métricas de cualquier componente que no tenga soporte nativo para ello.

  • Interoperabilidad: Se integra con sistemas de monitorización avanzados como Prometheus o Grafana, los cuales son un estándar en paradigma Cloud.


Implementación de MBeans para Pools de Hilos

Vamos a construir un ejemplo donde registramos un MBean que monitoriza un ThreadPoolExecutor. Este MBean expondrá tres métricas clave:

  • Hilos Activos: Número de hilos actualmente ejecutando tareas.

  • Tamaño del Pool: Número total de hilos en el pool.

  • Longitud de la Cola: Número de tareas en espera en la cola.

Código

El siguiente fragmento de código implementa el registro de un MBean para monitorizar el pool de hilos en un portlet:

    private static final String MBEAN_NAME = "com.liferay.mcv.example.parallel:type=ThreadPool,name=ParallelThreadsPool";

    @Override
    public void init() throws PortletException {
        super.init();
        registerMBean();
    }

    @Override
    public void destroy() {
        super.destroy();
        executorService.shutdown();
    }

    private void registerMBean() {
        try {
            MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
            ObjectName objectName = new ObjectName(MBEAN_NAME);
            ThreadPoolMetrics metrics = new ThreadPoolMetrics(executorService);
            if (!mBeanServer.isRegistered(objectName)) {
                mBeanServer.registerMBean(metrics, objectName);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static class ThreadPoolMetrics implements ThreadPoolMetricsMBean {
        private final ThreadPoolExecutor executor;

        public ThreadPoolMetrics(ThreadPoolExecutor executor) {
            this.executor = executor;
        }

        @Override
        public int getActiveThreads() {
            return executor.getActiveCount();
        }

        @Override
        public int getPoolSize() {
            return executor.getPoolSize();
        }

        @Override
        public int getQueueSize() {
            return executor.getQueue().size();
        }
    }

    public interface ThreadPoolMetricsMBean {
        int getActiveThreads();
        int getPoolSize();
        int getQueueSize();
    }
}

Análisis del Código

  1. Registro del MBean: El método registerMBean utiliza el MBeanServer de la JVM para registrar un objeto que expone métricas del pool de hilos. Esto permite que herramientas de monitorización puedan acceder a estas métricas.

  2. Interfaz del MBean: La interfaz ThreadPoolMetricsMBean define los métodos que expondrá el MBean. Cada método corresponde a una métrica específica.

  3. Métricas Expuestas:

    • getActiveThreads: Devuelve el número de hilos actualmente en ejecución.

    • getPoolSize: Devuelve el tamaño total del pool.

    • getQueueSize: Devuelve el número de tareas pendientes en la cola.

En el siguiente repositorio GitHub podéis encontrar el ejemplo completo con el que poder desplegar el portlet de ejemplo en Liferay DXP 7.4 Q4.0:

https://github.com/marcialcv/sample-workspace-7.4/blob/mbean-parallel-threads/modules/custom-portlet/src/main/java/com/liferay/mcv/example/parallel/CustomPortlet.java 

 

Consultando las Métricas

En el ejemplo del portlet, implemento un action para ir añadiendo 10 hilos con cada invocación, los cuales imprimen por pantalla un mensaje durante 30s. Se permitirán añadir un máximo de 100 hilos y a partir de ahí, se irán encolando hasta un máximo de 200.

Explicado el ejemplo, tras registrarse el MBean (durante el despliegue del Portlet), podemos utilizar herramientas como Glowroot o VisualVM para inspeccionar sus métricas haciendo los siguientes pasos:

  1. Nos conectamos a la JVM donde se ejecuta nuestra aplicación Java ( como Liferay DXP o tu SpringBoot app ;) )

  2. Visualiza los "MBeans" y localiza el MBean bajo el nombre: com.liferay.mcv.example.parallel:type=ThreadPool,name=ParallelThreadsPool
    (Con Glowroot será necesario añadir en gauges el MBean a la configuración previamente, desde la Consola)

  3. Jugando con el action del portlet, podemos comprobar las métricas expuestas en tiempo real, así como crear alertas basadas en estas métricas si usamos Glowroot:

     


Conclusión

Implementar MBeans para monitorizar pools de hilos es una solución elegante y eficiente cuando trabajamos con mecanismos que no exponen métricas por defecto. Esta técnica no solo es útil para pools de hilos, sino también para cualquier recurso compartido que necesite ser monitorizado.

Con esta solución, puedes mejorar la visibilidad de tus aplicaciones y anticiparte a posibles cuellos de botella, llevando la monitorización de tus sistemas a un nuevo nivel.