Partager via


Latence et débit du réseau

Trois problèmes majeurs concernent l’utilisation optimale du réseau :

  • Latence du réseau
  • Saturation du réseau
  • Implications du traitement des paquets

Cette section présente une tâche de programmation nécessitant l’utilisation de RPC, puis conçoit deux solutions : une mal écrite et une bien écrite. Les deux solutions sont ensuite examinées et leur impact sur les performances du réseau est abordé.

Avant de discuter des deux solutions, les sections suivantes décrivent et précisent les problèmes de performances liés au réseau.

Latence réseau

La bande passante réseau et la latence réseau sont des termes distincts. Les réseaux avec une bande passante élevée ne garantissent pas une faible latence. Par exemple, un chemin réseau parcourant une liaison satellite a souvent une latence élevée, même si le débit est très élevé. Il n’est pas rare qu’un aller-retour réseau parcourant un lien satellite ait cinq ou plus secondes de latence. L’implication d’un tel délai est celle-ci : une application conçue pour envoyer une demande, attendre une réponse, envoyer une autre demande, attendre une autre réponse, et ainsi de suite, attend au moins cinq secondes pour chaque échange de paquets, quelle que soit la vitesse du serveur. Malgré la vitesse croissante des ordinateurs, les transmissions satellites et les médias réseau sont basés sur la vitesse de la lumière, qui reste généralement constante. Par conséquent, il est peu probable que des améliorations de la latence des réseaux satellites existants se produisent.

Saturation du réseau

Certaines saturations se produisent dans de nombreux réseaux. Les réseaux les plus simples à saturer sont des liens de modem lents, tels que les modems analogiques standard 56k. Toutefois, les liaisons Ethernet avec de nombreux ordinateurs sur un segment unique peuvent également devenir saturées. Il en va de même pour les réseaux étendus avec une bande passante faible ou un lien surchargé, tel qu’un routeur ou un commutateur qui peut gérer une quantité limitée de trafic. Dans ce cas, si le réseau envoie plus de paquets que son lien le plus faible peut gérer, il supprime les paquets. Pour éviter la congestion des mises à l’échelle de la pile TCP Windows lorsque des paquets supprimés sont détectés, ce qui peut entraîner des retards significatifs.

Implications du traitement des paquets

Lorsque des programmes sont développés pour des environnements de niveau supérieur tels que RPC, COM et même Windows Sockets, les développeurs oublient généralement le travail en arrière-plan de chaque paquet envoyé ou reçu. Lorsqu’un paquet arrive du réseau, une interruption de la carte réseau est prise en service par l’ordinateur. Ensuite, un appel de procédure différée (DPC) est mis en file d’attente et doit passer par les pilotes. Si une forme de sécurité est utilisée, le paquet peut être déchiffré ou le hachage de chiffrement vérifié. Un certain nombre de vérifications de validité doivent également être effectuées à chaque état. Seul le paquet arrive à la destination finale : le code du serveur. L’envoi de nombreux petits blocs de données entraîne une surcharge de traitement des paquets pour chaque petit segment de données. L’envoi d’un grand segment de données a tendance à consommer beaucoup moins de temps processeur tout au long du système, même si le coût d’exécution pour de nombreux petits blocs par rapport à un bloc volumineux peut être le même pour l’application serveur.

Exemple 1 : Un serveur RPC mal conçu

Imaginez une application qui doit accéder aux fichiers distants, et la tâche à portée de main consiste à concevoir une interface RPC pour manipuler le fichier distant. La solution la plus simple consiste à mettre en miroir les routines de fichiers Studio pour les fichiers locaux. Cela peut entraîner une interface trompeusement propre et familière. Voici un fichier .idl abrégé :

typedef [context_handle] void *remote_file;
... .
interface remote_file
{
    remote_file remote_fopen(file_name);
    void remote_fclose(remote_file ...);
    size_t remote_fread(void *, size_t, size_t, remote_file ...);
    size_t remote_fwrite(const void *, size_t, size_t, remote_file ...);
    size_t remote_fseek(remote_file ..., long, int);
}

Cela semble assez élégant, mais en fait, il s’agit d’une recette honorée pour la performance sinistre. Contrairement à l’opinion populaire, l’appel de procédure distante n’est pas simplement un appel de procédure locale avec un fil entre l’appelant et l’appelé.

Pour voir comment cette recette brûle les performances, envisagez un fichier 2K, où 20 octets sont lus à partir du début, puis 20 octets à partir de la fin, et découvrez comment cela fonctionne. Côté client, les appels suivants sont effectués (de nombreux chemins de code sont omis pour la concision) :

rfp = remote_fopen("c:\\sample.txt");
remote_read(...);
remote_fseek(...);
remote_read(...);
remote_fclose(rfp);

Imaginez maintenant que le serveur est séparé du client par un lien satellite avec une durée d’aller-retour de cinq secondes. Chacun de ces appels doit attendre une réponse avant de continuer, ce qui signifie un minimum absolu pour l’exécution de cette séquence de 25 secondes. Compte tenu de la récupération de seulement 40 octets, il s’agit d’une performance scandaleusement lente. Les clients de cette application seraient furieux.

Imaginez maintenant que le réseau est saturé, car la capacité d’un routeur quelque part dans le chemin du réseau est surchargé. Cette conception force le routeur à gérer au moins 10 paquets si nous n’avons pas de sécurité (une pour chaque demande et une pour chaque réponse). Ça aussi, ce n’est pas bon.

Cette conception force également le serveur à recevoir cinq paquets et à envoyer cinq paquets. Là encore, pas une très bonne implémentation.

Exemple 2 : Un serveur RPC mieux conçu

Nous allons revoir l’interface décrite dans l’exemple 1 et voir si nous pouvons améliorer la situation. Il est important de noter que rendre ce serveur vraiment bon nécessite une connaissance du modèle d’utilisation pour les fichiers donnés : ces connaissances ne sont pas supposées pour cet exemple. Par conséquent, il s’agit d’un serveur RPC mieux conçu, mais pas d’un serveur RPC conçu de manière optimale.

L’idée de cet exemple est de réduire autant d’opérations distantes en une seule opération que possible. La première tentative est la suivante :

typedef [context_handle] void *remote_file;
typedef struct
{
    long position;
    int origin;
} remote_seek_instruction;
... .
interface remote_file
{
    remote_fread(file_name, void *, size_t, size_t, [in, out] remote_file ..., BOOL CloseWhenDone, remote_seek_instruction *...);
    size_t remote_fwrite(file_name, const void *, size_t, size_t, [in, out] remote_file ..., BOOL CloseWhenDone, remote_seek_instruction *...);
}

Cet exemple réduit toutes les opérations en lecture et écriture, ce qui permet une ouverture facultative sur la même opération, ainsi qu’une fermeture et une recherche facultatives.

Cette même séquence d’opération, lorsqu’elle est écrite sous forme abrégée, ressemble à ceci :

remote_read("c:\\sample.txt", ..., &rfp, FALSE, NULL);
remote_read(NULL, ..., &rfp, TRUE, seek_to_20_bytes_before_end);

Lorsque vous envisagez le serveur RPC mieux conçu, lors du deuxième appel, le serveur vérifie que l'file_name est NULL et utilise le fichier ouvert stocké dans rfp. Il voit ensuite des instructions de recherche et positionne le pointeur de fichier 20 octets avant la fin avant qu’il ne lise. Lorsque vous avez terminé, il reconnaît l’indicateur CloseWhenDone est défini sur TRUE, et ferme le fichier, puis ferme rfp.

Sur le réseau à latence élevée, cette meilleure version prend 10 secondes pour se terminer (2,5 fois plus rapidement) et nécessite le traitement de seulement quatre paquets ; deux réceptions du serveur, et deux envois du serveur. Les supplémentaires sis et annuler l’exécution du serveur sont négligeables par rapport à tout le reste.

Si l’ordre causal est spécifié correctement, l’interface peut même être effectuée de façon asynchrone et les deux appels peuvent être envoyés en parallèle. Lorsque l’ordre causal est utilisé, les appels sont toujours distribués dans l’ordre, ce qui signifie sur le réseau à latence élevée qu’un délai de cinq secondes est subi, même si le nombre de paquets envoyés et reçus est le même.

Nous pouvons réduire cela encore plus en créant une méthode qui prend un tableau de structures, chaque membre du tableau décrivant une opération de fichier particulière ; une variante distante d’E/S de nuages de points/de collecte. L’approche se décharge tant que le résultat de chaque opération ne nécessite pas de traitement supplémentaire sur le client ; en d’autres termes, l’application va lire les 20 octets à la fin, quelle que soit la première lecture de 20 octets.

Toutefois, si un traitement doit être effectué sur les 20 premiers octets après les avoir lus pour déterminer l’opération suivante, la réduction de tout en une opération ne fonctionne pas (au moins pas dans tous les cas). L’élégance de RPC est qu’une application peut avoir les deux méthodes dans l’interface et appeler l’une ou l’autre méthode en fonction du besoin.

En général, lorsque le réseau est impliqué, il est préférable de combiner autant d’appels sur un seul appel que possible. Si une application a deux activités indépendantes, utilisez des opérations asynchrones et laissez-les s’exécuter en parallèle. Pour l’essentiel, conservez le pipeline complet.