WebSphere eXtreme Scale permite que você grave uma implementação de despejo customizada.
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);
}
Os métodos EvictionEventCallback são usados por um plug-in Evictor para retornar à estrutura eXtreme Scale como a seguir:
Após uma transação ser concluída, o eXtreme Scale chama o método apply da interface do Evictor. Todos os bloqueios de transação que foram adquiridos pela transação concluída não estão mais suspensos. Provavelmente, vários encadeamentos podem chamar o método apply ao mesmo tempo e cada encadeamento pode concluir sua própria transação. Como os bloqueios de transação já foram liberados pela transação concluída, o método apply deve fornecer sua própria sincronização para assegurar que o método apply seja seguro em encadeamento.
Como o evictor é executado assincronicamente a partir de encadeamentos que executam transações, é possível que quando o evictor decida liberar a chave 1, ele possa liberar a entrada do mapa existente antes da conclusão da transação 1 ou possa liberar a entrada do mapa que foi recriada pela transação 2. Para eliminar janelas de cronometragens e eliminar a incerteza quanto à qual versão da entrada do mapa da chave 1 o evictor pretendia liberar, implemente a interface EvictorData pelo objeto transmitido ao método setEvictorData. Utilize a mesma instância EvictorData durante a existência de uma entrada do mapa. Quando essa entrada do mapa for excluída e, em seguida, recriada por outra transação, o evictor deverá utilizar uma nova instância da implementação de EvictorData. Utilizando a implementação de EvictorData e utilizando o método evictMapEntries, o evictor pode assegurar que a entrada do mapa seja liberada apenas se a entrada de cache associada à entrada do mapa contiver a instância EvictorData correta.
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;
// Instance variables
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();
// spawn do encadeamento do evictor
evictorThread = new Thread( this );
String threadName = "MyEvictorForMap−" + bm.getName();
evictorThread.setName( threadName );
evictorThread.start();
}
O código anterior salva as referências ao mapa e objetos de retorno de chamada em variáveis da instância para que fiquem disponíveis para os métodos apply e destroy. Neste exemplo, uma lista vinculada é criada para utilização como uma fila de primeiro a entrar e primeiro a sair para implementação de um algoritmo LRU (Least Recently Used). É efetuado spawn de um encadeamento e uma referência ao encadeamento é mantida como uma variável de instância. Mantendo esta referência, o método destroy pode interromper e terminar o encadeamento no qual foi efetuado spawn.
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 )
{
// do insert processing here by adding to front of LRU queue.
EvictorData data = new EvictorData(key);
evictorCallback.setEvictorData(key, data);
queue.addFirst( data );
}
else if ( type == LogElement.UPDATE || type == LogElement.FETCH || type == LogElement.TOUCH )
{
// do update processing here by moving EvictorData object to
// front of queue.
EvictorData data = evictorCallback.getEvictorData(key);
queue.remove(data);
queue.addFirst(data);
}
else if ( type == LogElement.DELETE || type == LogElement.EVICT )
{
// do remove processing here by removing EvictorData object
// from queue.
EvictorData data = evictorCallback.getEvictorData(key);
if ( data == EvictionEventCallback.KEY_NOT_FOUND )
{
// Assumption here is your asynchronous evictor thread
// evicted the map entry before this thread had a chance
// to process the LogElement request. So you probably
// need to do nothing when this occurs.
}
else
{
// Key was found. So process the evictor data.
if ( data != null )
{
// Ignore null returned by remove method since spawned
// evictor thread may have already removed it from queue.
// But we need this code in case it was not the evictor
// thread that caused this LogElement to occur.
queue.remove( data );
}
else
{
// Depending on how you write you Evictor, this possibility
// may not exist or it may indicate a defect in your evictor
// due to improper thread synchronization logic.
}
}
}
}
}
O processamento de inserção no método apply geralmente manipula a criação de um objeto de dados do evictor transmitido para o método setEvictorData da interface EvictionEventCallback. Como este evictor ilustra a implementação de um LRU, o EvictorData também é incluído na frente da fila que foi criada pelo método initialize. O processamento de atualização no método apply geralmente atualiza o objeto de dados do evictor criado por alguma chamada anterior do método apply (por exemplo, pelo processamento de inserção do método apply). Como este evictor é a implementação de um LRU, ele precisa mover o objeto EvictorData de sua posição de fila atual para a frente da fila. O encadeamento do evictor no qual foi efetuado spawn remove o último objeto EvictorData na fila, porque o último elemento da fila representa a entrada least recently used. A suposição é que o objeto EvictorData contenha um método getKey para que o encadeamento do evictor saiba quais são as chaves das entradas que precisam ser liberadas. Observe que este exemplo está ignorando os requisitos de sincronização para tornar o código seguro em encadeamento. Um evictor customizado real é mais complicado, porque lida com sincronização e gargalos de desempenho que ocorrem como resultado dos pontos de sincronização.
// Destroy method simply interrupts the thread spawned by the initialize method.
public void destroy()
{
evictorThread.interrupt();
}
// Here is the run method of the thread that was spawned by the initialize method.
public void run()
{
// Loop until destroy method interrupts this thread.
boolean continueToRun = true;
while ( continueToRun )
{
try
{
// Sleep for a while before sweeping over queue.
// The sleepTime is a good candidate for a evictor
// property to be set.
Thread.sleep( sleepTime );
int queueSize = queue.size();
// Evict entries if queue size has grown beyond the
// maximum size. Obviously, maximum size would
// be another evictor property.
int numToEvict = queueSize − maxSize;
if ( numToEvict > 0 )
{
// Remove from tail of queue since the tail is the
// least recently used entry.
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 )
{
// The queue is empty.
queueSize = 0;
}
}
// Request eviction if key list is not empty.
if ( ! evictList.isEmpty() )
{
evictorCallback.evictMapEntries( evictList );
}
}
}
catch (InterruptedException e)
{
continueToRun = false;
}
} // end while loop
} // end run method.
public interface RollbackEvictor
{
void rollingBack( LogSequence ls );
}
O método apply será chamado apenas de uma transação for confirmada. Se for efetuado rollback de uma transação e a interface RollbackEvictor for implementada pelo evictor, o método rollingBack será chamado. Se a interface RollbackEvictor não for implementada e for efetuado rollback da transação, o método apply e o método rollingBack não serão chamados.