Асинхронные пакетные операции в Couchbase
1. Вступление
В этом дополнении к нашему руководству поusing Couchbase in a Spring application мы исследуем асинхронную природу Couchbase SDK и то, как его можно использовать для выполнения операций сохранения в пакетном режиме, что позволяет нашему приложению оптимально использовать ресурсы Couchbase.
1.1. CrudService Интерфейс
Во-первых, мы добавляем в наш общий интерфейсCrudService пакетные операции:
public interface CrudService {
...
List readBulk(Iterable ids);
void createBulk(Iterable items);
void updateBulk(Iterable items);
void deleteBulk(Iterable ids);
boolean exists(String id);
}
1.2. CouchbaseEntity Интерфейс
Мы определяем интерфейс для сущностей, которые мы хотим сохранить:
public interface CouchbaseEntity {
String getId();
void setId(String id);
}
1.3. AbstractCrudService Класс
Затем мы реализуем каждый из этих методов в общем абстрактном классе. Этот класс является производным от классаPersonCrudService, который мы использовали вthe previous tutorial, и начинается следующим образом:
public abstract class AbstractCrudService implements CrudService {
private BucketService bucketService;
private Bucket bucket;
private JsonDocumentConverter converter;
public AbstractCrudService(BucketService bucketService, JsonDocumentConverter converter) {
this.bucketService = bucketService;
this.converter = converter;
}
protected void loadBucket() {
bucket = bucketService.getBucket();
}
...
}
2. Асинхронный интерфейс сегмента
Couchbase SDK предоставляет интерфейсAsyncBucket для выполнения асинхронных операций. Учитывая экземплярBucket, вы можете получить его асинхронную версию с помощью методаasync():
AsyncBucket asyncBucket = bucket.async();
3. Пакетные операции
Для выполнения пакетных операций с использованием интерфейсаAsyncBucket мы используем библиотекуRxJava.
3.1. Пакетное чтение
Здесь мы реализуем методreadBulk. Сначала мы используем механизмAsyncBucket иflatMap в RxJava для асинхронного извлечения документов вObservable<JsonDocument>, затем мы используем механизмtoBlocking вRxJava для преобразования их в список сущностей:
@Override
public List readBulk(Iterable ids) {
AsyncBucket asyncBucket = bucket.async();
Observable asyncOperation = Observable
.from(ids)
.flatMap(new Func1>() {
public Observable call(String key) {
return asyncBucket.get(key);
}
});
List items = new ArrayList();
try {
asyncOperation.toBlocking()
.forEach(new Action1() {
public void call(JsonDocument doc) {
T item = converter.fromDocument(doc);
items.add(item);
}
});
} catch (Exception e) {
logger.error("Error during bulk get", e);
}
return items;
}
3.2. Пакетная вставка
Мы снова используем конструкциюRxJava’s flatMap для реализации методаcreateBulk.
Поскольку массовые запросы на мутацию создаются быстрее, чем могут быть сгенерированы их ответы, что иногда приводит к состоянию перегрузки, мы устанавливаем повторную попытку с экспоненциальной задержкой всякий раз, когда встречаетсяBackpressureException:
@Override
public void createBulk(Iterable items) {
AsyncBucket asyncBucket = bucket.async();
Observable
.from(items)
.flatMap(new Func1>() {
@SuppressWarnings("unchecked")
@Override
public Observable call(final T t) {
if(t.getId() == null) {
t.setId(UUID.randomUUID().toString());
}
JsonDocument doc = converter.toDocument(t);
return asyncBucket.insert(doc)
.retryWhen(RetryBuilder
.anyOf(BackpressureException.class)
.delay(Delay.exponential(TimeUnit.MILLISECONDS, 100))
.max(10)
.build());
}
})
.last()
.toBlocking()
.single();
}
3.3. Пакетное обновление
Мы используем аналогичный механизм в методеupdateBulk:
@Override
public void updateBulk(Iterable items) {
AsyncBucket asyncBucket = bucket.async();
Observable
.from(items)
.flatMap(new Func1>() {
@SuppressWarnings("unchecked")
@Override
public Observable call(final T t) {
JsonDocument doc = converter.toDocument(t);
return asyncBucket.upsert(doc)
.retryWhen(RetryBuilder
.anyOf(BackpressureException.class)
.delay(Delay.exponential(TimeUnit.MILLISECONDS, 100))
.max(10)
.build());
}
})
.last()
.toBlocking()
.single();
}
3.4. Пакетное удаление
И мы пишем методdeleteBulk следующим образом:
@Override
public void deleteBulk(Iterable ids) {
AsyncBucket asyncBucket = bucket.async();
Observable
.from(ids)
.flatMap(new Func1>() {
@SuppressWarnings("unchecked")
@Override
public Observable call(String key) {
return asyncBucket.remove(key)
.retryWhen(RetryBuilder
.anyOf(BackpressureException.class)
.delay(Delay.exponential(TimeUnit.MILLISECONDS, 100))
.max(10)
.build());
}
})
.last()
.toBlocking()
.single();
}
4. PersonCrudServiceс
Наконец, мы пишем службу SpringPersonCrudService, которая расширяет нашAbstractCrudService для объектаPerson.
Поскольку все взаимодействие Couchbase реализовано в абстрактном классе, реализация для класса сущности тривиальна, так как нам нужно только убедиться, что все наши зависимости введены и наш контейнер загружен:
@Service
public class PersonCrudService extends AbstractCrudService {
@Autowired
public PersonCrudService(
@Qualifier("TutorialBucketService") BucketService bucketService,
PersonDocumentConverter converter) {
super(bucketService, converter);
}
@PostConstruct
private void init() {
loadBucket();
}
}
5. Заключение
Исходный код, показанный в этом руководстве, доступен вgithub project.
Вы можете узнать больше о Couchbase Java SDK на официальном сайтеCouchbase developer documentation site.