如何根据发出的事件有条件地缓冲Grouped Observable / Flux?

时间:2017-05-12 01:52:11

标签: java rx-java reactive-programming rx-java2 project-reactor

我正在尝试根据以下信息编写反应流:

我们有一个实体事件流,其中每个事件包含其实体的ID以及INTENT或COMMIT的类型。假设具有给定ID的COMMIT将始终在具有相同ID的一个或多个INTENT之前。收到INTENT时,应按其ID和"缓冲区"进行分组。应该打开那个组。缓冲区应该关闭"当收到同一组的COMMIT或已配置的超时已失效时。应该发出生成的缓冲区。

请注意,在收到结束COMMIT之前可以接收多个INTENT。 (编辑:) bufferDuration应保证任何"打开"自从接收到打开缓冲区的INTENT(有或没有COMMIT)以来bufferDuration时间过去后,就会发出缓冲区。

我最近的尝试如下:

public EntityEventBufferFactory {
    private final Duration bufferDuration;

    public EntityEventBufferFactory(Duration bufferDuration) {
        this.bufferDuration = bufferDuration;
    }

    public Flux<List<EntityEvent>> createGroupBufferFlux(Flux<EntityEvent> eventFlux) {
        return eventFlux.groupBy(EntityEvent::getId)
            .map(groupedFlux -> createGroupBuffer(groupedFlux))
            .flatMap(Function.identity());
    }

    protected Flux<List<EntityEvent>> createGroupBuffer(Flux<EntityEvent> groupFlux) {
        return groupFlux.publish().buffer(groupFlux.filter(this::shouldOpenBufferOnEvent), createGroupBufferCloseSelector(groupFlux));
    }

    protected Function<EntityEvent, Publisher<EntityEvent>> createGroupBufferCloseSelector(Flux<EntityEvent> groupFlux) {
        return event -> Flux.firstEmitting(Flux.just(event).delay(bufferDuration), groupFlux.filter(this::shouldCloseBufferOnEvent).publish());
    }

    protected boolean shouldOpenBufferOnEvent(EntityEvent entityEvent) {
        return entityEvent.getEventType() == EventType.INTENT;
    }

    protected boolean shouldCloseBufferOnEvent(EntityEvent entityEvent) {
        return entityEvent.getEventType() == EventType.COMMIT;
    }
}

这是我试图通过的测试:

@Test
public void entityEventsCanBeBuffered() throws Exception {
    FluxProcessor<EntityEvent, EntityEvent> eventQueue = UnicastProcessor.create();

    Duration bufferDuration = Duration.ofMillis(250);

    Flux<List<EntityEvent>> bufferFlux = new EntityEventBufferFactory(bufferDuration).createGroupBufferFlux(eventQueue);
    bufferFactory.setBufferDuration(bufferDuration);

    List<List<EntityEvent>> buffers = new ArrayList<>();
    bufferFlux.subscribe(buffers::add);

    EntityEvent intent = new EntityEvent();
    intent.setId("SOME_ID");
    intent.setEventType(EventType.INTENT);

    EntityEvent commit = new EntityEvent();
    commit.setId(intent.getId());
    commit.setEventType(EventType.COMMIT);

    eventQueue.onNext(intent);
    eventQueue.onNext(commit);

    eventQueue.onNext(intent);
    eventQueue.onNext(commit);

    Thread.sleep(500);

    assertEquals(2, buffers.size());
    assertFalse(buffers.get(0).isEmpty());
    assertFalse(buffers.get(1).isEmpty());
}

通过这个测试,我得到了两个发射缓冲区,但它们都是空的。你会注意到,在挖掘之后,我不得不在某些点添加.publish(),以免从Reactor得到This processor allows only a single Subscriber的异常。这个问题的答案RxJava: "java.lang.IllegalStateException: Only one subscriber allowed!"是导致我采用这种方法的原因。

我目前正在使用Reactor,但我认为这会使用Observable和相同名称的方法与RxJava进行1对1的转换。

有什么想法吗?

2 个答案:

答案 0 :(得分:1)

我认为这是Rx groupBy的确定用例。来自文档:

  

根据指定的条件对发布者发出的项目进行分组,并将这些分组的项目作为GroupedFlowables发出。发出的GroupedPublisher在其生命周期内只允许一个订阅者,如果此订阅者在源终止之前取消,则具有相同密钥的源的下一次发射将触发新的GroupedPublisher发射。

在您的情况下,此标准是ID,并且在每个GroupedPublisher上发出takeUntil类型为COMMIT:

source
.groupBy(EntityEvent::getId)
.flatMap(group -> 
    group
    .takeUntil(Flowable.timer(10,TimeUnit.SECONDS))
    .takeUntil(this::shouldCloseBufferOnEvent)
    .toList())

编辑:增加时间条件。

答案 1 :(得分:0)

感谢Tassos Bassoukos的投入。以下Reactor代码适用于我:

public EntityEventBufferFactory {
    private final Duration bufferDuration;

    public EntityEventBufferFactory(Duration bufferDuration) {
        this.bufferDuration = bufferDuration;
    }

    @Override
    public Flux<List<EntityEvent>> create(Flux<EntityEvent> eventFlux) {
        return eventFlux.groupBy(EntityEvent::getId)
            .map(this::createGroupBuffer)
            .flatMap(Function.identity());
    }

    protected Mono<List<EntityEvent>> createGroupBuffer(Flux<EntityEvent> groupFlux) {
        return groupFlux.take(bufferDuration)
            .takeUntil(this::shouldCloseBufferOnEvent)
            .collectList();
    }

    protected boolean shouldCloseBufferOnEvent(EntityEvent EntityEvent) {
        return EntityEvent.getEventType() == EventType.COMMIT;
    }
}