Ecriture d'un expulseur personnalisé

WebSphere eXtreme Scale permet l'écriture d'une implémentation d'expulseur personnalisé.

Vous devez créer un expulseur personnalisé qui implémente l'interface d'expulseur et suit les conventions courantes de plug-in eXtreme Scale. L'interface se présente comme suit :
public interface Evictor
{
    void initialize(BackingMap map, EvictionEventCallback callback);
    void activate();
    void apply(LogSequence sequence);
    void deactivate();
    void destroy();
}
L'interface EvictionEventCallback comporte les méthodes suivantes :
public interface EvictionEventCallback
{
    void evictMapEntries(List evictorDataList) throws ObjectGridException;
    void evictEntries(List keysToEvictList) throws ObjectGridException;
    void setEvictorData(Object key, Object data);
    Object getEvictorData(Object key);
}

Les méthodes EvictionEventCallback sont utilisées par un plug-in Evictor pour rappeler l'infrastructure eXtreme Scale comme suit :

A la fin d'une transaction, eXtreme Scale appelle la méthode apply de l'interface Evictor. Tous les verrous de transaction obtenus à la fin de la transaction ne sont plus maintenus. Eventuellement, plusieurs unités d'exécution peuvent appeler la méthode apply simultanément et chaque unité d'exécution peut effectuer sa propre transaction. Etant donné que les verrous de transaction sot déjà libérés à la fin de la transaction, la méthode apply doit offrir sa propre synchronisation pour faire en sorte que la méthode apply soit sécurisée pour les unités d'exécution.

L'implémentation de l'interface EvictorData et l'utilisation de la méthode evictMapEntries à la place de la méthode evictEntries se justifient par la fermeture d'une fenêtre de temps potentielle. Examinez la séquence d'événements suivante :
  1. La transaction 1 se termine et appelle la méthode apply avec un objet LogSequence qui supprime l'entrée de mappe pour la clé 1.
  2. La transaction 2 se termine et appelle la méthode apply avec un objet LogSequence qui insère une nouvelle entrée de mappe pour la clé 1. En d'autres termes, la transaction 2 recrée l'entrée de mappe supprimée par la transaction 1.

L'expulseur s'exécutant de façon asynchrone depuis les unités d'exécution qui exécutent les transactions, il est possible que lorsque l'expulseur décide d'expulser la clé 1, il expulse l'entrée de mappe qui existait avant la fin de la transaction 1 ou l'entrée de mappe qui a été recréée par la transaction 2. Pour éviter les fenêtres de temps et s'assurer de la version de l'entrée de mappe de la clé 1 que l'expulseur a voulu expulser, implémentez l'interface EvictorData par l'objet transmis à la méthode setEvictorData. Utilisez la même instance EvictorData pour la durée de vie d'une entrée de mappe. Lorsque cette entrée de mappe est supprimées et est ensuite recréée par une autre transaction, l'expulseur doit utiliser une nouvelle instance de l'implémentation EvictorData. En utilisant l'implémentation EvictorData et la méthode evictMapEntries, l'expulseur peut vérifier que l'entrée de mappe est expulsée à condition uniquement que l'entrée de cache associée à l'entrée de mappe contienne l'instance EvictorData correcte.

Les interfaces Evictor et EvictonEventCallback permettent à une application de connecter un expulseur qui implémente un algorithme défini par l'utilisateur pour l'expulsion. Le fragment de code ci-dessous illustre comment implémenter la méthode initialize de l'interface Evictor :
import com.ibm.websphere.objectgrid.BackingMap;
import com.ibm.websphere.objectgrid.plugins.EvictionEventCallback;
import com.ibm.websphere.objectgrid.plugins.Evictor;
import com.ibm.websphere.objectgrid.plugins.LogElement;
import com.ibm.websphere.objectgrid.plugins.LogSequence;
import java.util.LinkedList;
// Variables d'instance
private BackingMap bm;
private EvictionEventCallback evictorCallback;
private LinkedList queue;
private Thread evictorThread;
public void initialize(BackingMap map, EvictionEventCallback callback)
{
    bm = map;
    evictorCallback = callback;
    queue = new LinkedList();
    // générez une unité d'exécution d'expulseur
    evictorThread = new Thread( this );
    String threadName = "MyEvictorForMap−" + bm.getName();
    evictorThread.setName( threadName );
    evictorThread.start();
}

Le code précédent enregistre les références à la mappe et aux objets de rappel dans des variables d'instance de façon à les rendre disponibles pour les méthodes apply et destroy. Dans cet exemple, une liste liée est créée qui est utilisée comme file d'attente premier entré dernier sorti pour l'implémentation d'un algorithme LRU. Une unité d'exécution est générée et une référence à l'unité d'exécution est conservée en tant que variable d'instance. En conservant cette référence, la méthode destroy peut interrompre et mettre fin à l'unité d'exécution générée.

En ignorant les exigences de synchronisation afin de sécuriser les unités d'exécution de code, le fragment de code ci-dessous illustre comment la méthode apply de l'interface Evictor peut être implémentée :
import com.ibm.websphere.objectgrid.BackingMap;
import com.ibm.websphere.objectgrid.plugins.EvictionEventCallback;
import com.ibm.websphere.objectgrid.plugins.Evictor;
import com.ibm.websphere.objectgrid.plugins.EvictorData;
import com.ibm.websphere.objectgrid.plugins.LogElement;
import com.ibm.websphere.objectgrid.plugins.LogSequence;

public void apply(LogSequence sequence)
{
    Iterator iter = sequence.getAllChanges();
    while(iter.hasNext())
    {
        LogElement elem = (LogElement)iter.next();
        Object key = elem.getKey();
        LogElement.Type type = elem.getType();
        if ( type == LogElement.INSERT )
        {
            // n'insérez pas le traitement ici par l'ajout en tête de file d'attente LRU.
            EvictorData data = new EvictorData(key);
            evictorCallback.setEvictorData(key, data);
            queue.addFirst( data );
        }
        else if ( type == LogElement.UPDATE || type == LogElement.FETCH || type == LogElement.TOUCH )
        {
            // ne mettez pas à jour le traitement ici en déplaçant l'objet EvictorData au début
            // de la file d'attente.
            EvictorData data = evictorCallback.getEvictorData(key);
            queue.remove(data);
            queue.addFirst(data);
        }
        else if ( type == LogElement.DELETE || type == LogElement.EVICT )
        {
            // ne supprimez pas le traitement ici en supprimant l'objet EvictorData
            // de la file d'attente.
            EvictorData data = evictorCallback.getEvictorData(key);
            if ( data == EvictionEventCallback.KEY_NOT_FOUND )
            {
                // Hypothèse ici que votre unité d'exécution d'expulseur asynchrone
                // a expulsé l'entrée de mappe avant que cette unité d'exécution
                // traite la demande de LogElement. Vous devez probablement
                // n'effectuer aucune action lorsque cela se produit.
            }
            else
            {
                // La clé a été trouvée. Traitez les données de l'expulseur.
                if ( data != null )
                {
                    // Ignorez les valeurs null renvoyées par la méthode remove car l'unité
                    // d'exécution de l'expulseur générée l'a peut-être déjà supprimé de la file d'attente.
                    // Mais nous avons besoin de ce code au cas où cet objet LogElement n'a pas été
                    // provoqué par l'unité d'exécution.
                    queue.remove( data );
                }
                else
                {
                    // En fonction de l'écriture de l'expulseur, cette possibilité peut ne pas
                    // exister ou peut signaler une erreur de l'expulseur en raison d'une
                    // logique de synchronisation d'unités d'exécution inadaptée.
                }
            }
        }
    }
}

Le traitement des insertions de la méthode apply permet généralement de gérer la création d'un objet de données d'expulseur transmis à la méthode setEvictorData de l'interface EvictionEventCallback. Etant donné que cet expulseur illustre une implémentation LRU, les données de l'expulseur sont également ajoutées au début de la file d'attente qui a été créée par la méthode initialize. Le traitement des mises à jour de la méthode apply permet généralement de mettre à jour l'objet de données de l'expulseur créé par une invocation antérieure de la méthode apply (par exemple par le traitement d'une insertion de la méthode apply). Etant donné que cet expulseur illustre une implémentation LRU, il doit déplacer l'objet EvictorData de sa position actuelle dans la file d'attente au début de la file d'attente. L'unité d'exécution d'expulseur générée supprime le dernier objet EvictorData dans la file d'attente car ce dernier élément représente l'entrée la moins récemment utilisée. Il est supposé que l'objet EvictorData est associé à une méthode getKey de sorte que l'unité d'exécution d'expulseur connaît les clés des entrées à expulser. Gardez à l'esprit que cet exemple ignore les exigences de synchronisation afin de sécuriser l'unité d'exécution de code. Un expulseur personnalisé réel est plus complexe car il doit gérer la synchronisation et les goulots d'étranglement qui résultent des points de synchronisation.

Les fragments de code ci-dessous illustrent la méthode destroy et la méthode run de l'unité d'exécution générée par la méthode initialize :
// La méthode destroy interrompt simplement l'unité d'exécution générée par la méthode initialize.
public void destroy() {
    evictorThread.interrupt();
}

// Voici la méthode run de l'unité d'exécution générée par la méthode initialize.
public void run()
{
    // Exécutez en boucle jusqu'à ce que la méthode destroy interrompe cette unité d'exécution.
    boolean continueToRun = true;
    while ( continueToRun )
    {
        try
        {
            // Mettez en veille quelques instants avant analyse de la file d'attente.
            // sleepTime est une bonne pratique pour la définition
            // de la propriété d'un expulseur.
            Thread.sleep( sleepTime );
            int queueSize = queue.size();
            // Expulsez les entrées si la taille de la file d'attente dépasse
            // la taille maximale autorisée. Apparemment, la taille maximale
            // correspond à une autre propriété de l'expulseur.
            int numToEvict = queueSize − maxSize;
            if ( numToEvict > 0 )
            {
                // Supprimez de la fin de la file d'attente car la fin correspond
                // à l'entrée la moins récemment utilisée.
                List evictList = new ArrayList( numToEvict );
                while( queueSize > ivMaxSize )
                {
                    EvictorData data = null;
                    try
                    {
                        EvictorData data = (EvictorData) queue.removeLast();
                        evictList.add( data );
                        queueSize = queue.size();
                    }
                    catch ( NoSuchElementException nse )
                    {
                        // La file d'attente est vide.
                        queueSize = 0;
                    }
                }
                // Demandez l'expulsion si la liste de clés n'est pas vide.
                if ( ! evictList.isEmpty() )
                {
                    evictorCallback.evictMapEntries( evictList );
                }
            }
        }
        catch (InterruptedException e)
        {
            continueToRun = false;
        }
    } // mettez fin à la boucle while
} // mettez fin à la méthode run.

Interface RollBackEvictor facultative

L'interface com.ibm.websphere.objectgrid.plugins.RollbackEvictor peut être implémentée en option par un plug-in Evictor. En implémentant cette interface, un expulseur peut être appelé non seulement lors de la validation des transactions mais aussi lors de l'annulation des transactions.
public interface RollbackEvictor
{
    void rollingBack( LogSequence ls );
}

La méthode apply est appelée uniquement si une transaction est validée. Si une transaction est annulée et si l'interface RollbackEvictor est implémentée par l'expulseur, la méthode rollingBack est appelée. Si l'interface RollbackEvictor n'est pas implémentée et si la transaction s'annule, la méthode apply et la méthode rollingBack ne sont pas appelées.