web-dev-qa-db-fra.com

Puis-je utiliser la méthode block () de Flux renvoyée par WebClient de Spring5?

J'ai créé l'application de démonstration Spring Boot 2.0 qui contient deux applications qui communiquent à l'aide de WebClient. Et je souffre qu'ils arrêtent souvent de communiquer lorsque j'utilise la méthode block () de Flux à partir de la réponse du WebClient. Je veux utiliser List not Flux pour certaines raisons.

L'application côté serveur est comme ça. Il renvoie simplement l'objet Flux.

@GetMapping
public Flux<Item> findAll() {
    return Flux.fromIterable(items);
}

Et l'application côté client (ou côté BFF) est comme ça. J'obtiens Flux du serveur et le convertis en List en appelant la méthode block ().

@GetMapping
public List<Item> findBlock() {
    return webClient.get()
        .retrieve()
        .bodyToFlux(Item.class)
        .collectList()
        .block(Duration.ofSeconds(10L));
}

Bien que cela fonctionne bien au début, findBlock () ne répondra pas et expirera après plusieurs accès. Lorsque je modifie la méthode findBlock () pour renvoyer Flux en supprimant collectList () et block (), cela fonctionne bien. Ensuite, je suppose que la méthode block () provoque ce problème.
Et, lorsque je modifie la méthode findAll () pour renvoyer List, rien ne change.

Le code source de l'ensemble de l'exemple d'application est ici.
https://github.com/cero-t/webclient-example

"ressource" est l'application serveur et "avant" est l'application client. Après avoir exécuté les deux applications, lorsque j'accède à localhost: 8080, cela fonctionne bien et je peux recharger à tout moment, mais lorsque j'accède à localhost: 8080/block, cela semble bien fonctionner mais après plusieurs rechargements, il ne répondra pas.


Soit dit en passant, lorsque j'ajoute une dépendance "spring-boot-starter-web" au pom.xml des applications "front" (et non des ressources), ce qui signifie que j'utilise Tomcat, ce problème ne se produit jamais. Ce problème est-il dû au serveur Netty?

Tout conseil serait grandement apprécié.

7
Shin Tanimoto

Tout d'abord, permettez-moi de souligner que l'utilisation de Flux.fromIterable(items) n'est conseillée que si items a été récupérée de la mémoire, aucune E/S n'est impliquée. Sinon, il est probable que vous utilisiez une API de blocage pour l'obtenir - et cela peut casser votre application réactive. Dans ce cas, il s'agit d'une liste en mémoire, donc pas de problème. Notez que vous pouvez également aller Flux.just(item1, item2, item3).

L'utilisation des éléments suivants est la plus efficace:

@GetMapping("/")
public Flux<Item> findFlux() {
  return webClient.get()
    .retrieve()
    .bodyToFlux(Item.class);
}

Item les instances seront lues/écrites, décodées/encodées à la volée de manière très efficace.

En revanche, ce n'est pas la voie préférée:

@GetMapping("/block")
public List<Item> findBlock() {
  return webClient.get()
    .retrieve()
    .bodyToFlux(Item.class)
    .collectList()
    .block(Duration.ofSeconds(10L));
}

Dans ce cas, votre application frontale met en mémoire tampon la liste complète des éléments avec collectList mais bloque également l'un des rares threads du serveur disponible. Cela peut entraîner de très mauvaises performances car votre serveur peut être bloqué en attente de ces données et ne peut pas traiter d'autres demandes en même temps.

Dans ce cas particulier, c'est pire, car l'application casse totalement. En regardant la console, nous pouvons voir ce qui suit:

WARN 3075 --- [ctor-http-nio-7] io.netty.util.concurrent.DefaultPromise  : An exception was thrown by reactor.ipc.netty.channel.PooledClientContextHandler$$Lambda$532/356589024.operationComplete()

reactor.core.Exceptions$BubblingException: Java.lang.IllegalArgumentException: Channel [id: 0xab15f050, L:/127.0.0.1:59350 - R:localhost/127.0.0.1:8081] was not acquired from this ChannelPool
    at reactor.core.Exceptions.bubble(Exceptions.Java:154) ~[reactor-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]

Ceci est probablement lié à un problème de pool de connexion client réacteur-netty qui devrait être corrigé dans 0.7.4.RELEASE. Je ne connais pas les détails de cela, mais je soupçonne que l'ensemble du pool de connexions est corrompu car les réponses HTTP ne sont pas lues correctement à partir des connexions client.

Ajouter spring-boot-starter-web fait que votre application utilise Tomcat, mais elle transforme principalement votre application Spring WebFlux en une application Spring MVC (qui prend désormais en charge certains types de retour réactifs, mais a un modèle d'exécution différent). Si vous souhaitez tester votre application avec Tomcat, vous pouvez ajouter spring-boot-starter-Tomcat à votre POM et ceci utilisera Tomcat avec Spring WebFlux.

6
Brian Clozel