août 05

Aggrégateur JMX

By Jean-Philippe Briend Java, JMX, Spring Add comments

JMX (Java Management Extensions) est très pratique pour le monitoring d’une application.
Malheureusement, lorsque l’application grandit et que, par exemple, des traitements nocturnes apparaissent, ces derniers tournent dans des machines virtuelles distinctes.

A ce moment là, JMX pose problème pour la remontée d’informations.

L’idée que je vais vous proposer est la suivante :

  • Une JVM hébergeant un MBeanServer tourne en continu (éventuellement hébergé dans un serveur d’application).
  • Chacun des traitements se connectera à cette JVM en utilisant un JMX Connector et invoquera à distance des Notifications JMX à destination du MBeanServer.

Grâce à cette architecture, les ports des JVM ‘clientes’ ne sont plus un problème : on laisse le mécanisme par défaut de JMX se débrouiller pour en trouver un libre.

Les personnes souhaitant monitorer ces traitements n’ont qu’à lancer JConsole et se connecter sur la JVM ‘serveur’ qui héberge le MBeanServer.

Le schéma suivant présente le principe :

Cliquez pour agrandir

JMX Notifier va être un objet faisant appel au NotificationPublisher de JMX.
Ce NotificationPublisher envoi des notifications au serveur MBean.

Le JMX Notifier va être exposé par des JMX Connectors aux clients et sera proxyfié.
Le traitement peut alors utiliser de façon invisible cet objet pour envoyer des évènements à la JVM distante.

Passons maintenant au code !

JMX Notifier (serveur) :

@ManagedResource(objectName="batch:name=BatchJMX", description="JMX Notifier")
public class JmxNotifierImpl implements JmxNotifier, NotificationPublisherAware {

  protected NotificationPublisher publisher;

  @ManagedOperation(description="Annonce Début batch.")
  @ManagedOperationParameters(
  @ManagedOperationParameter(name="id", description= "Batch ID."))
  public void annonceDebut(String id) {
    super.publisher.sendNotification(
                    new Notification("Debut du Batch " + id,
                                           this,
                                           0,
                                           Calendar.getInstance().getTimeInMillis()));
  }

  public void setNotificationPublisher(NotificationPublisher publisher) {
    this.publisher = publisher;
  }
}


On notera l’utilisation de l’interface NotificationPublisherAware afin de se faire injecter le NotificationPublisher par Spring.

JMX Notifier Service (client) :

public class JmxNotifierServiceImpl implements InitializingBean {
	
  private String objectName;
  private String url;
  private JMXConnector jmxConnector;
  private JmxNotifier notifier;
	
  public void afterPropertiesSet() {
    try {
      ObjectName objectName = new ObjectName(this.objectName);
      JMXServiceURL jmxUrl = new JMXServiceURL(this.url);
      this.jmxConnector = JMXConnectorFactory.connect(jmxUrl);
      MBeanServerConnection mbsc = this.jmxConnector.getMBeanServerConnection();

      this.notifier = (JmxNotifier)MBeanServerInvocationHandler.newProxyInstance(
                                                                                           mbsc,
                                                                                           objectName,
                                                                                           JmxNotifier.class,
                                                                                           false);
    } catch (MalformedObjectNameException e) {
      System.err.println("Warning : could not connect to 
                                     JMX server. JMX Notifications will not be sent.");
      System.err.println("Reason : " + e.getMessage());
    } catch (MalformedURLException e) {
      System.err.println("Warning : could not connect to
                                     JMX server. JMX Notifications will not be sent.");
      System.err.println("Reason : " + e.getMessage());
    } catch (IOException e) {
     System.err.println("Warning : could not connect to
                                     JMX server. JMX Notifications will not be sent.");
     System.err.println("It seems that remote server is
                                     not up and not running.");
    }
  }
}

  public void close() throws IOException {
    if ( this.jmxConnector != null ) {
      this.jmxConnector.close();
  }

  public void annonceDebu(String id) {
    if ( this.notifier != null ) {
      this.notifier.annonceDebut(id);
    }
  }
}


Une meilleure gestion des exceptions levées permettrait de gérer les cas où le serveur distant n’est pas disponible.

Côté serveur, il va vous falloir la configuration Spring suivante :

<context:annotation-config />

<bean id="jmxNotifier" class="JmxNotifierImpl" />

<bean id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean" />

<bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean">
	<property name="locateExistingServerIfPossible" value="true" />
</bean>

<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean">
  <property name="objectName" value="connector:name=rmi"/>
  <property name="serviceUrl" value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/standaloneJmxServer"/>
  <property name="threaded" value="true"/>
	<property name="daemon" value="true"/>
</bean>
  
<bean class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
	<property name="autodetect" value="true" />
	<property name="namingStrategy" ref="namingStrategy" />
	<property name="assembler" ref="assembler" />
</bean>

<bean id="attributeSource" class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>

<bean id="assembler" class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
	<property name="attributeSource" ref="attributeSource" />
</bean>

<bean id="namingStrategy" class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
	<property name="attributeSource" ref="attributeSource" />
</bean>

Les points marquants sont la présence de la MBeanServerFactory, la ConnectorServerFactoryBean et la RMIRegistryFactoryBean.

Et c’est tout !
Votre code n’a plus qu’à appeler JMXNotifierService.
Après, à vous d’enrichir les 2 JMX Notifier en fonction du fonctionnel de votre application.

Les équipes responsables du monitoring n’ont qu’à connecter leur JConsole sur la JVM contenant le MBeanServer.

Leave a Reply


Creative Commons License
Blog Infin-It par Infin-It est mis à disposition selon les termes de la licence Creative Commons Paternité-Pas d'Utilisation Commerciale 2.0 France.