Contents

在Couchbase中的异步批量操作

1. 简介

在我们关于在 Spring 应用程序中使用Couchbase 的教程的后续部分中,我们探讨了 Couchbase SDK 的异步特性以及如何使用它来批量执行持久性操作,从而使我们的应用程序能够实现 Couchbase 资源的最佳使用。

1.1. CrudService接口

首先,我们扩充我们的通用CrudService接口以包含批处理操作:

public interface CrudService<T> {
    ...

    List<T> readBulk(Iterable<String> ids);
    void createBulk(Iterable<T> items);
    void updateBulk(Iterable<T> items);
    void deleteBulk(Iterable<String> ids);
    boolean exists(String id);
}

1.2. CouchbaseEntity接口

我们为要持久化的实体定义一个接口:

public interface CouchbaseEntity {
    String getId();
    
    void setId(String id);
    
}

1.3. AbstractCrudService

然后我们将在一个通用抽象类中实现这些方法中的每一个。该类派生自我们在上一教程中使用的 PersonCrudService类,开头如下:

public abstract class AbstractCrudService<T extends CouchbaseEntity> implements CrudService<T> {
    private BucketService bucketService;
    private Bucket bucket;
    private JsonDocumentConverter<T> converter;
    public AbstractCrudService(BucketService bucketService, JsonDocumentConverter<T> converter) {
        this.bucketService = bucketService;
        this.converter = converter;
    }
    protected void loadBucket() {
        bucket = bucketService.getBucket();
    }
    
    ...
}

2. AsyncBucket接口

Couchbase SDK 提供了用于执行异步操作的AsyncBucket接口。给定一个Bucket实例,你可以通过*async()*方法获取它的异步版本:

AsyncBucket asyncBucket = bucket.async();

3. 批量操作

为了使用AsyncBucket接口执行批处理操作,我们使用了*RxJava *库。

3.1. 批量读取

这里我们实现了readBulk方法。首先我们使用 RxJava 中的AsyncBucketflatMap机制将文档异步检索到Observable中,然后我们使用 RxJava 中的toBlocking机制将它们转换为实体列表:

@Override
public List<T> readBulk(Iterable<String> ids) {
    AsyncBucket asyncBucket = bucket.async();
    Observable<JsonDocument> asyncOperation = Observable
      .from(ids)
      .flatMap(new Func1<String, Observable<JsonDocument>>() {
          public Observable<JsonDocument> call(String key) {
              return asyncBucket.get(key);
          }
    });
    List<T> items = new ArrayList<T>();
    try {
        asyncOperation.toBlocking()
          .forEach(new Action1<JsonDocument>() {
              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 的 flatMap构造来实现createBulk方法。

由于批量突变请求的生成速度比生成响应的速度要快,有时会导致过载情况,因此每当遇到BackpressureException时,我们都会以指数延迟重试:

@Override
public void createBulk(Iterable<T> items) {
    AsyncBucket asyncBucket = bucket.async();
    Observable
      .from(items)
      .flatMap(new Func1<T, Observable<JsonDocument>>() {
          @SuppressWarnings("unchecked")
          @Override
          public Observable<JsonDocument> 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<T> items) {
    AsyncBucket asyncBucket = bucket.async();
    Observable
      .from(items)
      .flatMap(new Func1<T, Observable<JsonDocument>>() {
          @SuppressWarnings("unchecked")
          @Override
          public Observable<JsonDocument> 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<String> ids) {
    AsyncBucket asyncBucket = bucket.async();
    Observable
      .from(ids)
      .flatMap(new Func1<String, Observable<JsonDocument>>() {
          @SuppressWarnings("unchecked")
          @Override
          public Observable<JsonDocument> 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

最后,我们编写了一个 Spring 服务PersonCrudService,它为Person实体扩展了AbstractCrudService

由于所有的 Couchbase 交互都在抽象类中实现,实体类的实现很简单,因为我们只需要确保注入所有依赖项并加载桶:

@Service
public class PersonCrudService extends AbstractCrudService<Person> {
    @Autowired
    public PersonCrudService(
      @Qualifier("TutorialBucketService") BucketService bucketService,
      PersonDocumentConverter converter) {
        super(bucketService, converter);
    }
    @PostConstruct
    private void init() {
        loadBucket();
    }
}