响应式WebClient未发出响应

14
我有一个关于Spring Reactive WebClient的问题... 几天前,我决定尝试使用Spring Framework中的新反应式内容,并为个人目的制作了一个小项目来爬取数据(对一个网页进行多次请求并组合结果)。
我开始使用新的反应式WebClient进行请求,但我发现的问题是客户端没有为每个请求发出响应。听起来很奇怪。以下是我获取数据所做的内容:
private Mono<String> fetchData(String uri) {
    return this.client
            .get()
            .uri(uri)
            .header("X-Fsign","SW9D1eZo")
            .retrieve()
            .bodyToMono(String.class)
            .timeout(Duration.ofSeconds(35))
            .log("category", Level.ALL, SignalType.ON_ERROR, SignalType.ON_COMPLETE, SignalType.CANCEL, SignalType.REQUEST);
}

调用 fetchData 的函数:

public Mono<List<Stat>> fetch() {
    return fetchData(URL)
            .map(this::extractUrls)
            .doOnNext(System.out::println)
            .doOnNext(s-> System.out.println("all ids are "+s.size()))
            .flatMapIterable(q->q)
            .map(s -> s.substring(7, 15))
            .map(s -> "http://d.flashscore.com/x/feed/d_hh_" + s + "_en_1") // list of N-length urls
            .flatMap(this::fetchData)
            .map(this::extractHeadToHead)
            .collectList();
}

和订阅者:

    FlashScoreService bean = ctx.getBean(FlashScoreService.class);
    bean.fetch().subscribe(s->{
        System.out.println("finished !!! " + s.size()); //expecting same N-length list size
    },Throwable::printStackTrace);

问题在于,如果我发出的请求稍微多一点,超过了100个,我就无法收到所有的响应,没有抛出错误,也没有返回错误响应代码,并且订阅方法被调用时的大小与请求数不同。
我的请求基于字符串列表(url)进行,当所有响应都被发出后,我应该将它们全部作为列表接收,因为我使用了collectList()。当我执行100个请求时,我期望收到100个响应的列表,但实际上我有时会收到100个,有时会收到96等……可能是某些地方默默地失败了。 这很容易复现,这是我的github项目link
样本输出:
all ids are 176
finished !!! 171

请给我一些建议,如何调试或者我做错了什么。感谢您的帮助。
更新:
日志显示,如果我传递了126个url,例如:
onNext(ReactorClientHttpResponse{request=[GET/some_url],status=200}) is called 121 times. May be here is the problem.
onComplete() is called 126 times which is the exact same length of the passed list of urls

但是如何可能在不调用onNext()或onError()的情况下完成一些请求呢?(在Mono中成功和错误)

我认为问题不在WebClient,而是在其他地方。环境或服务器阻止了请求,但我可能应该收到一些错误日志。

附言:感谢您的帮助!


你添加的日志运算符应该显示大量信息;你能分享一下那些少数情况下会发生什么吗? - Brian Clozel
请查看我的更新。谢谢。 - Nikolay Rusev
1个回答

6

这是一个棘手的问题。调试接收到的实际HTTP帧,似乎我们确实没有收到一些请求的响应。通过Wireshark进一步调试,看起来远程服务器正在请求使用 FIN, ACK TCP数据包结束连接,并且客户端确认了它。问题在于,此连接仍然被从池中取出,在第一个 FIN, ACK TCP数据包之后发送另一个GET请求。

也许远程服务器在服务一定数量的请求后关闭连接;在任何情况下,这都是完全合法的行为。请注意,我无法始终复现此问题。

解决方法

您可以在客户端上禁用连接池;这将会更慢,但显然不会触发此问题。为此,请使用以下内容:

this.client = WebClient.builder()
                .clientConnector(new ReactorClientHttpConnector(new Consumer<HttpClientOptions.Builder>() {
                    @Override
                    public void accept(HttpClientOptions.Builder builder) {
                        builder.disablePool();
                    }
                }))
                .build();

潜在问题

根本问题是当TCP连接无法发送响应并关闭时,HTTP客户端不应该进行onComplete操作。或者更好的方式是,在关闭连接时HTTP客户端不应该重用此连接。我将在进一步了解后回报此处。


首先感谢您的支持,我非常感激。所有使用Wireshark进行调试和数据包检查的工作,我都非常感激!我是整个Spring生态系统的忠实粉丝,你们做得太棒了!关于我的问题...是的,它并不总是可以重现,这对我来说是最大的担忧。感谢您的解释和解决方法,这将对我有所帮助。我应该将问题标记为已回答,并将继续在您的Jira上跟踪这个小错误。再次感谢! - Nikolay Rusev
已提交[SPR-15784](https://jira.spring.io/browse/SPR-15784)。@NikolayRusev - Abhijit Sarkar
我已经创建了 https://github.com/reactor/reactor-netty/issues/138 - 这应该是实际的根本问题。 - Brian Clozel
使用reactor-netty-0.6.4时,没有HttpClientOptions.Builder;方法签名为public ReactorClientHttpConnector(Consumer<? super HttpClientOptions> clientOptions)。但是我按照您展示的使用了Consumer<HttpClientOptions>,看起来它正在工作。 - Abhijit Sarkar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接