Иногда у нас есть большой объект, возможно, с множеством атрибутов или с некоторыми двоичными данными, и мы хотели бы сказать Infinispan, что он должен реплицировать только определенную часть объекта в кластере. Как правило, мы хотим воспроизвести только ту часть, которую мы только что обновили. Именно здесь вступают в игру интерфейсы DeltaAware и Delta. Предоставляя реализации этих интерфейсов, мы можем определить точную репликацию. Когда мы прилагаем некоторые усилия для такого улучшения, мы также хотели бы ускорить маршалинг объектов и демаршаллинг. Поэтому мы собираемся определить наши собственные экстернализаторы — чтобы избежать медленной сериализации Java по умолчанию.
Следующие фрагменты кода собраны в полный пример по адресу
https://github.com/mgencur/infinispan-examples/tree/master/partial-state-transfer.Этот проект содержит файл readme с инструкциями о том, как создать и запустить пример. Он основан на быстром запуске кластерного кэша в Infinispan.
Реализация интерфейса DeltaAware
Итак, давайте посмотрим на наш основной объект. Для этого упражнения я определил класс Bicycle, который состоит из множества компонентов, таких как frame, fork, tailShock и т. Д. Этот объект хранится в кэше как значение под определенным (не важным) ключом. В нашем сценарии может случиться так, что мы обновляем только определенные компоненты велосипеда, и в этом случае мы хотим повторить только эти изменения компонентов.
Здесь важны следующие методы (описание взято из javadocs):
commit () — указывает, что все собранные на сегодняшний день дельты были извлечены (посредством
вызова delta ()) и могут быть отброшены. Часто используется в качестве оптимизации, если
дельта не нужна, но
желательна очистка и сброс
внутреннего состояния.
delta () — извлекает изменения, внесенные в реализации, в эффективном формате, который
можно легко и дешево сериализовать и десериализовать. Этот метод будет
вызываться только один раз для каждого набора изменений, поскольку предполагается, что
внутренний журнал изменений любой
реализации стирается и сбрасывается после генерации
и отправки дельты вызывающей стороне.
Мы также должны определить сеттеры и геттеры для наших членов. Методы установки, помимо прочего, отвечают за регистрацию изменений в журнале изменений, который впоследствии будет использоваться для восстановления состояния объекта. Externalizer для этого класса необходим только при использовании хранилищ кэша. Ради простоты я не упоминаю об этом здесь.
public class Bicycle implements DeltaAware, Cloneable {
//bicycle attributes
String frame, fork, rearShock, crank, bottomBracket, shifters, cogSet, chain,
frontDerailleur, rearDerailleur, rims, hubs, tires, pedals, brake, handlebar, stem;
private BicycleDelta delta;
@Override
public void commit() {
delta = null;
}
@Override
public Delta delta() {
Delta toReturn = getDelta();
delta = null; // reset
return toReturn;
}
...
public void setFrame(String frame) {
getDelta().registerComponentChange("frame", frame);
this.frame = frame;
}
public String getFrame() {
return frame;
}
...
}
Реализация интерфейса Delta
Фактический объект, который будет реплицирован по всему кластеру, является реализацией интерфейса Delta. Давайте посмотрим на класс. Во-первых, нам нужно поле, которое будет содержать изменения — changeLog. Во-вторых, нам нужно определить метод merge (). Этот метод должен быть реализован так, чтобы Infinispan знал, как объединить существующий объект с входящими изменениями. Параметр этого метода представляет объект, который уже хранится в кэше, входящие изменения будут применены к этому объекту. Мы используем отражение здесь, чтобы применить изменения к реальному объекту, но это не обязательно. Мы могли бы легко вызвать методы установки. Преимущество использования отражения в том, что мы можем установить эти поля в цикле.
Другой частью является метод registerComponentChange (). Это вызывается объектом класса Bicycle — для записи изменений этого объекта. Название этого метода не важно.
Определение нашего собственного экстерьера
Хорошо, так что остаётся определение экстерьера для реализации Delta. Мы реализуем интерфейс AdvancedExternalizer и говорим, что только объект changeLog должен быть маршалирован и демаршаллирован при передаче данных по проводам. Полная (почти) реализация интерфейса Delta заключается в следующем.
public class BicycleDelta implements Delta {
private HashMap<String, String> changeLog = new HashMap<String, String>();
@Override
public DeltaAware merge(DeltaAware d) {
Bicycle other;
if (d != null && (d instanceof Bicycle)) {
other = (Bicycle) d;
}
else {
other = new Bicycle();
}
Class<?> hugeClass = other.getClass();
try {
if (changeLog != null) {
for (Entry<String, String> o : changeLog.entrySet()) {
Field x = hugeClass.getDeclaredField(o.getKey());
if (!x.isAccessible()) x.setAccessible(true);
x.set(other, o.getValue());
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return other;
}
public void registerComponentChange(String compName, String compDescription) {
changeLog.put(compName, compDescription);
}
...
public static class Externalizer implement AdvancedExternalizer<BicycleDelta> {
@Override
public Set<Class<? extends BicycleDelta>> getTypeClasses() {
return Util.<Class<? extends BicycleDelta>>asSet(BicycleDelta.class);
}
@Override
public void writeObject(ObjectOutput output, BicycleDelta object) throws IOException {
output.writeObject(object.changeLog);
}
@Override
public BicycleDelta readObject(ObjectInput input) throws IOException, ClassNotFoundException {
BicycleDelta delta = new BicycleDelta();
delta.changeLog = (HashMap<String, String>) input.readObject();
return delta;
}
@Override
public Integer getId() {
return 33; //put some random value here to identify the externalizer (must not be in reserved value ranges)
}
}
}
Расскажите Infinispan о дополнительном экстерьере
Нам также необходимо настроить Infinispan для использования нашего специального экстерьера для маршалинга / демаршаллинга наших объектов. Мы можем сделать это, например, программно, вызвав .addAdvancedExternalizer () в построителе конфигурации сериализации.
new DefaultCacheManager( GlobalConfigurationBuilder.defaultClusteredBuilder() .serialization() .addAdvancedExternalizer(new BicycleDelta.Externalizer()) .build(), new ConfigurationBuilder() .clustering() .cacheMode(CacheMode.REPL_SYNC) .transaction() .transactionMode(TransactionMode.TRANSACTIONAL) .autoCommit(true) .lockingMode(LockingMode.OPTIMISTIC) .transactionManagerLookup(new JBossStandaloneJTAManagerLookup()) .locking() .isolationLevel(IsolationLevel.READ_COMMITTED) .build() );
Вы можете видеть, что мы также настраиваем транзакции здесь. Это не обязательно, однако. Мы просто стремимся предоставить более богатый пример, исключить транзакционное поведение очень легко.
И тут начинается часть «использования». Заключите вызовы кеша транзакцией, извлеките объект велосипеда из кеша, внесите некоторые изменения и зафиксируйте их.
try {
tm.begin();
//retrieve the bicycle from the cache, we suppose the bicycle already exists and
//only trying to apply some changes to it, only these changes will be replicated
Bicycle toChange = cache.get(BIKE1);
System.out.println("Updating components: frame, fork");
toChange.setFrame("New Frame");
toChange.setFork("New Fork");
//store the bicycle back to the cache
cache.put(BIKE1, toChange);
tm.commit();
} catch (Exception e) {
try {
if (tm != null) {
tm.rollback();
}
} catch (Exception ex) {
}
}
Вот и все. То, что в итоге передается по проводам, это просто объект changeLog. Фактический объект велосипеда реконструирован из поступающих обновлений.
Если все это кажется вам слишком сложным, у меня есть хорошие новости. Infinispan предоставляет одну реализацию интерфейса DeltaAware, которая называется AtomicHashMap (пакет org.infinispan.atomic). Если эта карта используется в качестве значения в парах ключ / значение, хранящихся в кэше, то только операции добавления / получения / удаления этой карты во время транзакции копируются на другие узлы. Такие классы, как Bicycle и BicycleDelta, тогда не нужны. Даже регистрация внешнего устройства для AtomicHashMap не требуется, это делается автоматически при регистрации внутренних внешних устройств. Однако может потребоваться класс, имитирующий объект реального мира, а не просто карту. Это тот случай, когда ваши собственные реализации интерфейсов DeltaAware и Delta являются единственным способом.