RxJava - 如何为长轮询创建服务器

时间:2015-09-17 13:58:17

标签: java reactive-programming rx-java

我有一个群集中有多个节点的应用程序。每个节点将日志文件写入其本地磁盘。我已经实现了一个日志搜索功能,可以在每个节点上搜索日志。从浏览器接收搜索请求的所有者节点将日志搜索作业提交给其他节点,然后其他节点将搜索结果传递给原始节点。客户端Web浏览器使用长轮询来从节点获取搜索结果。这似乎非常适合RxJava,因为每个节点都是一个事件流,客户端从所有节点获取一个统一的事件流。 (我们假设一个吝啬的运营团队不会让我们使用Splunk或其他一些商业日志记录解决方案。)

客户端轮询原始节点上的REST API,该API收集搜索结果。我对REST API的理想逻辑如下:

  • 服务器应使客户端最多等待15秒才能收到响应。
  • 如果在15秒内未生成任何结果,则响应可以为空。客户端将看到此信息并发送新的轮询请求。
  • 如果没有更多结果(即搜索结束),请向客户发送特殊回复,以指示其不再进行投票。
  • 如果生成了结果,请最多等待100分钟以获得其他结果,以节省额外投票的网络开销。
  • 客户不应该得到任何重复的结果。

我编写了以下示例代码来模拟这种情况:

public static void main(String[] args) throws Exception {
    ExecutorService executorService = Executors.newFixedThreadPool(5);

    /* Each searchTask represents the results of a search job running on a
     * node in the cluster */
    Subject<String,String> searchTask1 = PublishSubject.create();
    Subject<String,String> searchTask2 = PublishSubject.create();

    // Limit max number of search results
    Observable<String> searchResults = 
        Observable.merge(searchTask1, searchTask2).take( 1000 );

    /* Add a 100ms buffer window to collect nearby responses together.  
     * Filter out any empty buffers to eliminate unnecessary
     * responses to the browser. */
    BlockingObservable<List<String>> blocking = 
        searchResults.buffer(100, TimeUnit.MILLISECONDS)
            .filter(results -> !results.isEmpty()).toBlocking();
    Iterator<List<String>> it = blocking.getIterator();

    /* Each call to searchTask.onNext represents a search result pushed
     * to the owner node from another node.  This code would be called 
     * from the REST endpoint. */
    executorService.submit( () -> {
        searchTask1.onNext("1");
        try { Thread.sleep(1200); } catch ( Exception ignored ) { }
        searchTask1.onNext("2");
        searchTask1.onCompleted();
    });
    executorService.submit( () -> {
        searchTask2.onNext("a");
        try { Thread.sleep(500); } catch ( Exception ignored ) { }
        searchTask2.onNext("b");
        searchTask2.onCompleted();
    });

    executorService.submit( () -> {
        /* Each iteration of this loop represents a polling request from
         * the browser and the results that are sent back to it. */
        for ( int i = 0; i < 5; i++ ) { 
            it.forEachRemaining(results -> System.out.println(results));
        }
    });

    Thread.sleep(1500);
    System.out.println("exit");
}

for循环中的逻辑应该确保在最多15秒后始终将响应发送回客户端(即使响应为空)?

编辑:我用更多评论更新了示例代码,并显示了我当前的解决方案,但我仍然无法获得15秒的最大响应时间。我们的网络设备将关闭空闲时间过长的HTTP连接,因此我想保证客户端在最多15秒后始终得到响应。

1 个答案:

答案 0 :(得分:0)

我能找到的所有RxJava文章似乎都非常关注客户端代码而不是服务器端代码。然而,在与Observable运算符争论了一下之后,我想出了以下解决方案接近我想要的。心跳发生在每15秒而不是前一次结果后15秒。这意味着服务器可能在发送结果后立即向客户端发送心跳响应,但这对我来说足够接近。

我创建了一个15秒的间隔可观察对象,并将其与我已经拥有的searchResults可观察对象合并。我使用了一个主题,这样当结果流停止时我可以停止可观察的间隔(否则它会无限期地继续)。

    /* Add a heartbeat that ensures we don't wait too long between
     * sending responses and some network device kills our connection */
    final Observable<String> heartbeat =
        Observable.interval( 1, TimeUnit.SECONDS ).map(el -> "heartbeat");
    final PublishSubject<String> stopHeartbeat = PublishSubject.create();
    searchResults.subscribe( el -> {}, ex -> {}, () -> stopHeartbeat.onNext( null ) );
    final Observable<String> searchResultsWithHeartbeat =
        searchResults.mergeWith( heartbeat.takeUntil( stopHeartbeat ) );