Contexte

Je cherchais une solution pour traiter rapidement un très gros flux d’événements afin d’implémenter un moteur d’alerte. La première idée qui vient en tête serait d’enregistrer dans une base de données mes événements puis ensuite d’écrire un certain nombre de requêtes afin de détecter lorsqu’une alerte sur un service est levée. Le problème est que cela représente un énorme volume d’information, et que donc le chemin « base-de-données-application » n’est pas le bon.

Introduction à Esper

Esper (http://www.espertech.com/products/esper.php) est un moteur open-source de gestion de flux d’événement (ESP en anglais, ce qui signifie Event Stream Processing) ainsi qu’un moteur de corrélation d’événement (si serveur A tombe et serveur B tombe alors le service « paniers » ne marchera plus par exemple…). Un support commercial est disponible ainsi qu’une version avancée lorsque des besoins de haute-disponibilités s’ajoutent à votre projet. Le tout est fourni par la société EsperTech basée en Californie.

L’idée d’Esper est de détecter des événements en temps réel et ensuite de déclencher des actions lorsqu’un événement, ou plusieurs événements se produisent. Le traitement d’alerte en temps réel fait parti du cœur de logiciel financier complexe, mais aussi d’outils très puissant de supervision de réseau et applicative.

ESP (Events stream processing) et CEP (consolidated events processing) sont 2 domaines qui viennent de la recherche. Esper propose en fait un langage appelé Event Processing Language (EPL) pour décrire un filtre d’alerte, pour agréger des alertes et travailler sur des fenêtres temporelles. Je vais reprendre un des exemples de la doc d’Esper pour vous expliquer cela.
L’idée n’est pas de stocker des données, et ensuite de les analyser, selon des requêtes à écrire sur la base, ce qui sera lent. L’idée est de stocker des filtres, des alertes, puis d’appliquer ces filtres sur les données surveillées en temps réel. C’est un moteur de règle appliqué aux données. Esper est donc un système qui vous permet d’écrire avec la syntaxe EPL des requêtes et de déclencher ensuite en Java des événements dans votre code lorsque les filtres sont activés.

Exemple avec la surveillance de la charge CPU d’un serveur

Prenons un objet dans une application de surveillance de machine qui serait capable de demander à un démon distant la charge du CPU. Nous allons dire que nous voulons une alerte si pendant 10 minutes, le CPU reste au-delà de 90% de charge machine. Nous ne voulons pas d’alerte si pendant cette fenêtre de 10 minutes, le CPU passe une seule fois au dessus de 90%, mais si cela se reproduit disons 10 fois au moins, afin de détecter un problème de charge.

Cependant dans un premier temps, il faut mettre en place une alerte si le serveur interrogé ne répond pas du tout pendant disons 10 minutes. Pour communiquer entre mon serveur de surveillance et le démon distant, j’envois un message simple, et le démon distant me répond avec un message de réponse. Si je n’ai pas de corrélation avec un requestId pendant 10 minutes, alors je veux lever une alerte. Le fait d’attendre 10 minutes évitera de lever une alerte pour un problème réseau temporaire par exemple.

La requête est exprimée avec cette classe Java simple que j’envoie au démon :

Public class CPULoadRequest {
    public int requestId;
}

La réponse retournée par le démon contient un timestamp ainsi que la valeur courante de la charge CPU au moment où nous avons enregistré celle-ci :

Public class CPULoadResponse {
    public int responseId;
    public long timestamp ;
    public int cpuLoad ;
}

J’ai besoin de ces objets en Java car je vais m’en servir avec Esper pour exprimer une requête, qui correspond à ce que je veux surveiller.
En regardant les événements CPULoadRequest et CPULoadResponse, Esper sera capable de nous alerter lorsque mon seuil est déclenché. Si par exemple je veux détecter que je n’ai pas reçu de CPULoadResponse durant les 10 dernières minutes, je peux exprimer cela avec cette requête EPL :

every r=CPULoadRequest -> (timer:interval( 10 min) and not CPULoadResponse(responseId = r.requestId))

Le symbole « -> » signifie « suivi de ». Ici pour chaque CPULoadRequest suivi dans un intervalle de 10 minutes et dont on ne retrouve pas l’id, alors lever une alerte.

Pour être notifié de toutes les quotations boursières de Reuters supérieures à 45 des 60 dernières secondes on peut ainsi écrire :

  every StockTickEvent(symbol="RTR.L", price>45) where timer:within(60 seconds)

Voyons maintenant comment exprimer l’alerte suivante : je souhaite qu’une alerte soit générée si le CPU dépasse au moins 10 fois 90% de la charge machine pendant une fenêtre de 10 minutes.

select count(*)
  from CPULoadResponse(cpuLoad > 90).win:time(10 minutes)
  having count(*) >= 10

Le mot clé “win:time” déclare une fenêtre de temps glissante de 10 minutes. Ici on cherche à compter combien de CPULoadResponse supérieur à 90 sur les dix dernières minutes, et se répétant au moins 10 fois existent… Esper ne consulte pas de base de donnés pour retourner la valeur. Il se débrouille pour mettre en place de quoi enregistrer sur les 10 dernières minutes seulement, un petit bout de filtre qui correspond à ce que je demande. Vous voyez la puissance ? Nous ne stockons rien.

Mise en place avec du code

Pour représenter ce statement en Java voici à quoi va ressembler votre code :

EPStatement statement = service.createEQL(
     "select count(*) as cnt " +
     "from CPULoadResponse (cpuLoad > 90).win:time(10 minutes) " +
     "having count(*) >= 10";

Ensuite pour être notifié lorsque mon seuil est déclenché, je vais implémenter l’interface UpdateListener d’Esper et mon code sera appelé par Esper lorsque la condition est réalisée.

public class CPUListener implements UpdateListener {
    public void update(EventBean[] result, EventBean[] prior) {
      System.out.println("Number of CPU over 90% for the last 10 min is " + result[0].get("cnt") );
    }
  }

Pour enregistrer ma classe CPUListener dans Esper :

MyStmtObserver observer = new CPUListener ();
statement.setObserver(observer);

Et pour terminer il me reste à implémenter un peu de code pour créer des objets CPULoadRequest et les envoyer à mon démon distant avec par exemple :

    CPULoadRequest event = new CPULoadRequest() ;
    myService.sendToDaemin(event, daemonId) ; // Send this event to a specific daemon

Domaine d’application

Esper pourrait être utilisé dans la finance pour implémenter un logiciel d’alerte et de notification. Il pourrait servir aussi dans un site marchand pour surveiller les données applicatives comme le nombre d’achat sur un site marchand, l’encaissement par un site bancaire, l’envoi d’un bon de commande à un transporteur par exemple… Dans une architecture SOA Esper apporterait un moyen de modéliser les règles de surveillance du bon fonctionnement de différents services…

Conclusion:

Au lieu d’enregistrer des événements temporels et ensuite de les analyser, Esper permet à une application Java de décrire et d’enregistrer des requêtes d’alerte. Par sa structure et ce mode de fonctionnement, Esper est donc beaucoup plus adapté à gérer un grand nombre d’événement. Par ailleurs le mode d’exécution d’Esper est continu, dans le sens qu’on peut réellement parler de fenêtre temporelle, et pas de surveillance par pooling d’une base de données.

Spéciale dédicace à Gik pour ce billet 😉

Ressources supplémentaires :

Excellent article sur OnJava http://www.onjava.com/pub/a/onjava/2007/03/07/esper-event-stream-processing-and-correlation.html

Article dont je me suis largement inspiré pour écrire ce billet : http://www.theserverside.com/tt/articles/article.tss?l=ComplexEventProcessing. Auteurs : Thomas Bernhardt et Alexandre Vasseur.