RxJava:与数据获取并行执行授权

时间:2015-07-29 23:15:53

标签: java reactive-programming rx-java

我想组合两个observable:一个是检查用户是否有权获取数据,另一个是实际获取数据,我想与获取数据并行执行授权以减少总延迟。这是一个不平行的例子:

public static void main(String[] args) {
    long startTime = currentTimeMillis();

    Observable<Integer> result = isAuthorized().flatMap(isAuthorized -> {
        if (isAuthorized) return data();
        else throw new RuntimeException("unauthorized");
    });

    List<Integer> data = result.toList().toBlocking().single();
    System.out.println("took: " + (currentTimeMillis() - startTime) + "ms");
    System.out.println(data);
    assert data.size() == 10;
}

private static Observable<Boolean> isAuthorized() {
    return Observable.create( s -> {
        try { sleep(5000); } catch (Exception e) {} // simulate latency
        s.onNext(true);
        s.onCompleted();
    });
}

private static Observable<Integer> data() {
    return Observable.create(s -> {
        for (int i = 0; i < 10; i++) {
            try { sleep(1000); } catch (Exception e) {} // simulate long running job
            s.onNext(i);
        }
        s.onCompleted();
    });
}

执行此操作的总时间为15秒,如果对授权和数据获取的调用是并行的,则应为10秒。怎么做?理想情况下,我还希望在等待授权完成时,最多有多少数据项被缓存在内存中。

顺便说一句,我读过excellent answer about paralleling observables,但现在还没有解决我的问题。

2 个答案:

答案 0 :(得分:0)

要使用类型安全执行此操作,我建议为isAuthorized()发射和data()发射包装不可变类并合并流,然后减少和过滤以不发出任何内容(未授权)或数据(授权)。

static class AuthorizedData {

    final Boolean isAuthorized; //null equals unknown
    final Data data; //null equals unknown

    AuthOrData(Boolean isAuthorized, Data data) {
        this.isAuthorized = isAuthorized;
        this.data = data;
    }

}

Observable<Data> authorizedData =
  isAuthorized()
    .map(x -> new AuthorizedData(x, null))
    .subscribeOn(Schedulers.io())
    .mergeWith(
        data().map(x -> new AuthorizedData(null, x))
              .subscribeOn(Schedulers.io()))
    .takeUntil(a -> a.isAuthorized!=null && !a.isAuthorized)
    .reduce(new AuthorizedData(null, null), (a, b) -> {
        if (a.isAuthorized!=null && a.data != null)
           return a;
        else if (b.isAuthorized!=null)
           return new AuthorizedData(b.isAuthorized, a.data);
        else if (b.data!=null)
           return new AuthorizedData(a.isAuthorized, b.data);
        else 
           return a;
    })
    .filter(a -> a.isAuthorized!=null 
                 && a.isAuthorized && a.data!=null)
    .map(a -> a.data);
如果没有授权,上面的

authorizedData为空,否则是单个数据项的流。

上述takeUntil点是在发现用户未获得授权后立即取消订阅data()。如果data()可中断(可以关闭套接字或其他),这将非常有用。

答案 1 :(得分:0)

我找到了办法:

  1. 使用Schedulers.io()订阅两个observable并行运行它们。
  2. 使用isAuthorized()无限次重复repeat()次发射,因为它通常只发出一个布尔值。
  3. 要避免为每个项目调用授权服务,请使用cache()
  4. 使用项目流重复布尔值的Zip流,并使用布尔值决定是返回项还是抛出异常。
  5. 以下是解决方案:

    public static void main(String[] args) {
      long startTime = currentTimeMillis();
    
      Observable<Integer> result = Observable.defer(() -> {
        Observable<Boolean> p1 = isAuthorized().cache().repeat().subscribeOn(Schedulers.io());
        Observable<Integer> p2 = data().subscribeOn(Schedulers.io());
        return Observable.zip(p1, p2, (isAuthorized, item) -> {
          if (isAuthorized)
            return item;
          else
            throw new RuntimeException("unauthorized");
        });
      });
    
      List<Integer> data = result.toList().toBlocking().single();
      System.out.println("took: " + (currentTimeMillis() - startTime) + "ms");
      System.out.println(data);
      assert data.size() == 10;
    }
    
    private static Observable<Boolean> isAuthorized() {
      return Observable.create( s -> {
        try { sleep(5000); } catch (Exception e) {} // simulate latency
        s.onNext(true);
        s.onCompleted();
      });
    }
    
    private static Observable<Integer> data() {
      return Observable.range(1, 10)
          .doOnNext(i -> { try { sleep(1000); } catch (Exception e) {} });
    }
    

    从我观察到的这个解决方案中也避免了OutOfMemory错误。两个observable都开始同时发出,但如果授权服务较慢,则将收集数据项,直到填充内部缓冲区。然后RxJava将停止请求数据项,直到授权最终发出布尔值。

    当授权以负结果返回时,它也会从数据流中正确取消订阅。