web-dev-qa-db-fra.com

Mécanisme de contre-pression dans Spring Web-Flux

Je suis un démarreur dans Spring Web-Flux. J'ai écrit un contrôleur comme suit:

@RestController
public class FirstController 
{
    @GetMapping("/first")
    public Mono<String> getAllTweets() 
    {
        return Mono.just("I am First Mono")
    }
}

Je sais que l'un des avantages réactifs est Backpressure, et qu'il peut équilibrer la demande ou le taux de réponse. Je veux réaliser comment avoir un mécanisme de contre-pression dans Spring Web-Flux.

21
Sam

Contre-pression dans WebFlux

Afin de comprendre le fonctionnement de Backpressure dans la mise en œuvre actuelle du framework WebFlux, nous devons récapituler ici la couche de transport utilisée par défaut. Comme nous pouvons nous en souvenir, la communication normale entre le navigateur et le serveur (la communication entre serveurs est généralement identique) s'effectue via la connexion TCP. WebFlux utilise également ce transport pour la communication entre un client et le serveur. Ensuite, pour comprendre le sens du terme contrôle de la contre-pression, nous devons récapituler ce que signifie la contre-pression du point de vue de la spécification des flux réactifs.

La sémantique de base définit la manière dont la transmission des éléments de flux est régulée par contre-pression.

Nous pouvons donc conclure de cette affirmation que dans les flux réactifs, la contre-pression est un mécanisme qui régule la demande par la transmission (notification) du nombre d'éléments pouvant être consommés par le destinataire; Et ici nous avons un point délicat. Le TCP a une abstraction d'octets plutôt qu'une abstraction d'éléments logiques. Ce que nous voulons habituellement en disant contrôle de contre-pression est le contrôle du nombre d'éléments logiques envoyés/reçus vers/depuis le réseau. Même si le TCP a son propre contrôle de flux (voir la signification ici et l'animation ), ce contrôle de flux est toujours utilisé pour les octets plutôt que pour les éléments logiques .

Dans l'implémentation actuelle du module WebFlux, la contrepression est régulée par le contrôle de flux de transport, mais elle n'expose pas la demande réelle du destinataire. Pour enfin voir le flux d'interaction, veuillez vous reporter au diagramme suivant:

enter image description here

Pour des raisons de simplicité, le diagramme ci-dessus montre la communication entre deux microservices où le membre de gauche envoie des flux de données et le module de droite consomme ce flux. La liste numérotée suivante fournit une brève explication de ce diagramme:

  1. C’est le framework WebFlux qui prend bien en charge la conversion des éléments logiques en octets et retour, ainsi que leur transfert/réception vers/depuis le TCP (réseau).
  2. Ceci est un début de traitement de longue durée de l'élément qui demande les éléments suivants une fois le travail terminé.
  3. Ici, bien qu'il n'y ait pas de demande de la logique métier, les octets de file de mise en file d'attente WebFlux provenant du réseau sans accusé de réception (il n'y a pas de demande de la logique métier).
  4. En raison de la nature du contrôle de flux TCP, le service A peut toujours envoyer des données au réseau.

Comme on peut le voir sur le diagramme ci-dessus, la demande exposée par le destinataire est différente de la demande de l'expéditeur (demande ici en éléments logiques). Cela signifie que la demande des deux est isolée et fonctionne uniquement pour l'interaction WebFlux <-> Logique commerciale (Service) et expose moins la contre-pression pour l'interaction Service A <-> Service B.

Tout cela signifie que le contrôle de la contrepression n'est pas aussi équitable que prévu dans WebFlux.

Mais je veux toujours savoir comment contrôler la contre-pression

Si nous voulons toujours avoir un contrôle injuste de la contre-pression dans WebFlux, nous pouvons le faire avec le soutien d'opérateurs de Project Reactor tels que limitRate() . L'exemple suivant montre comment nous pouvons utiliser cet opérateur:

@PostMapping("/tweets")
public Mono<Void> postAllTweets(Flux<Tweet> tweetsFlux) {

    return tweetService.process(tweetsFlux.limitRate(10))
                       .then();
}

Comme nous pouvons le voir dans l'exemple, l'opérateur limitRate() permet de définir le nombre d'éléments à pré-extraire simultanément. Cela signifie que même si l'abonné final demande des éléments Long.MAX_VALUE, L'opérateur limitRate le scinde en morceaux et ne permet pas de consommer plus que cela à la fois. La même chose que nous pouvons faire avec le processus d'envoi d'éléments:

@GetMapping("/tweets")
public Flux<Tweet> getAllTweets() {

    return tweetService.retreiveAll()
                       .limitRate(10);
}

L'exemple ci-dessus montre que même si WebFlux demande plus de 10 éléments à la fois, la fonction limitRate() limite la demande à la taille de lecture anticipée et empêche de consommer simultanément plus que le nombre spécifié d'éléments.

Une autre option consiste à implémenter son propre Subscriber ou à étendre le BaseSubscriber à partir de Project Reactor. Par exemple, voici un exemple naïf de la façon dont nous pouvons le faire:

class MyCustomBackpressureSubscriber<T> extends BaseSubscriber<T> {

    int consumed;
    final int limit = 5;

    @Override
    protected void hookOnSubscribe(Subscription subscription) {
        request(limit);
    }

    @Override
    protected void hookOnNext(T value) {
        // do business logic there 

        consumed++;

        if (consumed == limit) {
            consumed = 0;

            request(limit);
        }
    }
}

Contre-pression équitable avec protocole RSocket

Pour obtenir une contre-pression d'éléments logiques à travers les limites du réseau, nous avons besoin d'un protocole approprié à cette fin. Heureusement, il en existe un appelé protocole RScoket . RSocket est un protocole au niveau de l’application qui permet de transférer la demande réelle à travers les limites du réseau. Il existe une implémentation RSocket-Java de ce protocole qui permet de configurer un serveur RSocket. Dans le cas d'une communication serveur à serveur, la même bibliothèque RSocket-Java fournit également une implémentation client. Pour en savoir plus sur l'utilisation de RSocket-Java, veuillez vous reporter aux exemples suivants ici . Pour la communication navigateur-serveur, il existe une implémentation RSocket-JS qui permet de câbler la communication en continu entre le navigateur et le serveur via WebSocket.

Structures connues sur RSocket

De nos jours, il existe quelques frameworks, construits sur le protocole RSocket.

Proteus

L'un des frameworks est un projet Proteus qui offre des microservices complets construits sur RSocket. En outre, Proteus étant bien intégré au cadre Spring, nous pouvons maintenant réaliser un contrôle de contre-pression équitable (voir exemples )

Lectures supplémentaires

45
Oleh Dokuka