WebSphere eXtreme Scale permite escribir una implementación de desalojo personalizada.
public interface Evictor
{
void initialize(BackingMap map, EvictionEventCallback callback);
void activate();
void apply(LogSequence sequence);
void deactivate();
void destroy();
}
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);
}
Los métodos EvictionEventCallback son utilizados por un plug-in Evictor para realizar una devolución de llamada a la infraestructura eXtreme Scale del modo siguiente:
Después de que se complete una transacción, eXtreme Scale llama al método de aplicación de la interfaz Evictor. Ya no se conserva ningún bloqueo de transacción que se adquirieron al completarse la transacción. Varias hebras podrán potencialmente llamar al método de aplicación al mismo tiempo, y cada hebra podrá completar su propia transacción. Como los bloqueos de transacción se liberaron al completarse la transacción, el método de aplicación debe proporcionar su propia sincronización para garantizar la seguridad de las hebras en el método de aplicación.
Puesto que el desalojador se ejecuta de forma asíncrona en las hebras que ejecutan las transacciones, es posible que, cuando el desalojador decida desalojar la clave 1, desaloje la entrada de correlación que existió antes de la finalización de la transacción 1, o desaloje la entrada de correlación recreada por la transacción 2. Para eliminar las ventanas de sincronización y eliminar la duda sobre qué versión de la entrada de correlación de la clave 1 va a eliminar el desalojador, implemente la interfaz EvictorData mediante el objeto que se pasa al método setEvictorData. Use la misma instancia de EvictorData para la vida de una entrada de correlación. Cuando dicha entrada de correlación se elimina y después otra transacción la recrea, el desalojador debe utilizar una instancia nueva de implementación de EvictorData. Al utilizar la implementación de EvictorData y el método evictMapEntries, el desalojador puede garantizar que se desaloje la entrada de correlación sólo si la entrada de memoria caché asociada con la entrada de correlación contiene la instancia de EvictorData correcta.
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 de instancia
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();
// generar hebra de desalojador
evictorThread = new Thread( this );
String threadName = "MyEvictorForMap−" + bm.getName();
evictorThread.setName( threadName );
evictorThread.start();
}
Este código guarda las referencias a los objetos de devolución de llamada y correlación en variables de instancia de modo que estén disponibles para los métodos de aplicación y destrucción. En este ejemplo, se utiliza una lista enlazada que se usa como cola FIFO (primero en entrar, primero en salir) para implementar un algoritmo LRU (menos utilizado recientemente). Se genera una hebra y se mantiene una referencia a la hebra como variable de instancia. Al mantener esta referencia, el método destroy puede interrumpir y terminar la hebra generada.
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 )
{
// proceso de inserción aquí. Añadir en la parte delantera de la
cola LRU.
EvictorData data = new EvictorData(key);
evictorCallback.setEvictorData(key, data);
queue.addFirst( data );
}
else if ( type == LogElement.UPDATE || type == LogElement.FETCH || type == LogElement.TOUCH )
{
// proceso de actualización aquí. Mover el objeto EvictorData a
// la parte delantera de la cola.
EvictorData data = evictorCallback.getEvictorData(key);
queue.remove(data);
queue.addFirst(data);
}
else if ( type == LogElement.DELETE || type == LogElement.EVICT )
{
// proceso de eliminación aquí. Eliminar el objeto EvictorData
// de la cola.
EvictorData data = evictorCallback.getEvictorData(key);
if ( data == EvictionEventCallback.KEY_NOT_FOUND )
{
// Presuponga lo siguiente: la hebra del desalojador asíncrona
// desalojó la entrada de correlación antes de que esta hebra
// pudiera procesar la solicitud LogElement. Probablemente
// no tenga que hacer nada cuando esto ocurra.
}
else
{
// Clave encontrada. Procese los datos del desalojador.
if ( data != null )
{
// Ignore valor null devuelto por el método de eliminación ya que la
// hebra de desalojador generada puede haberlo eliminado de la cola.
// Se necesita este código en caso de que no fuera la hebra
// de desalojador la que produjo este LogElement.
queue.remove( data );
}
else
{
// En función de cómo escriba el desalojador, puede que no
// exista esta posibilidad o puede que indique un defecto en el desalojador
// debido a una lógica de sincronización de hebras no adecuada.
}
}
}
}
}
El proceso de inserción del método de aplicación maneja la creación de un objeto de datos de desalojador que se pasa al método setEvictorData de la interfaz EvictionEventCallback. Como este desalojador ilustra una implementación LRU, EvictorData también se añade a la parte delantera de la cola creada por el método de inicialización. El proceso de actualización del método de aplicación actualiza el objeto de datos de desalojador creado por una invocación previa del método de aplicación (por ejemplo, por el proceso de inserción del método de aplicación). Como este desalojador es una implementación LRU, necesita mover el objeto EvictorData de la posición actual en la cola a la parte delantera de ésta. La hebra del desalojador generada elimina el último objeto EvictorData de la cola porque este último objeto representa la entrada menos utilizada recientemente. Se presupone que el objeto EvictorData tiene un método getKey de modo que la hebra del desalojador conoce las claves de las entradas que debe desalojar. Tenga en cuenta que en este ejemplo se ignoran los requisitos de sincronización para que las hebras del código sean seguras. Un desalojador personalizado real es más complicado porque se ocupa de los cuellos de botella de rendimiento y sincronización que se producen como resultado de los puntos de sincronización.
// El método destroy interrumpe la hebra generada por el método initialize.
public void destroy()
{
evictorThread.interrupt();
}
// Método de ejecución de la hebra generada por el método de inicialización.
public void run()
{
// Repetir en bucle hasta que el método destroy interrumpa esta hebra.
boolean continueToRun = true;
while ( continueToRun )
{
try
{
// Inactivo durante un tiempo antes de efectuar un barrido en la cola.
// La propiedad sleepTime es una propiedad
// del desalojador que puede establecerse.
Thread.sleep( sleepTime );
int queueSize = queue.size();
// Desalojar entradas si el tamaño de la cola sobrepasa
// el tamaño máximo. El tamaño máximo es
// otra propiedad del desalojador.
int numToEvict = queueSize − maxSize;
if ( numToEvict > 0 )
{
// Eliminar de la parte final de la cola ya que es la
// entrada menos utilizada recientemente.
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 cola está vacía.
queueSize = 0;
}
}
// Solicitar desalojo si la lista de claves no está vacía.
if ( ! evictList.isEmpty() )
{
evictorCallback.evictMapEntries( evictList );
}
}
}
catch ( InterruptedException e )
{
continueToRun = false;
}
} // fin bucle while
} // fin de método de ejecución.
public interface RollbackEvictor
{
void rollingBack( LogSequence ls );
}
El método de aplicación sólo se llama si se confirma una transacción. Si se retrotrae una transacción y el desalojador implementa la interfaz RollbackEvictor, se invoca al método rollingBack. Si no se implementa la interfaz RollbackEvictor y se retrotrae la transacción, el método de aplicación y el método rollingBack no se llaman.