3 votes

Choix de conception pour le service de fichiers à haute performance

Je suis en train de développer une application sous Linux qui devra supporter environ 250 connexions et transmettre de gros fichiers sur des sockets TCP dans la gamme de taille 100MB+. Le but est de régler le débit plutôt que la latence. Je veux garder des connexions Ethernet 2x1Gbit saturées à tout moment. Celles-ci seront liées à un canal.

Il est prévu que l'application soit occupée en permanence et qu'elle envoie des données aussi vite que possible. Les connexions resteront actives la plupart du temps et, contrairement à HTTP, elles ne seront pas interrompues aussi souvent.

J'ai examiné les différentes options telles que epoll, sendfile api etc. pour les hautes performances et aio (qui semble trop immature et risqué IMHO).

Je me suis également intéressé à l'api boost asio qui utilise epoll en dessous. Je l'ai déjà utilisé mais pas pour une application de haute performance comme celle-ci.

J'ai plus de 4 cœurs de processeur disponibles et je peux donc en faire usage.

Cependant, j'ai lu que Boost Asio n'est pas très bon avec les fils multiples à cause d'un certain verrouillage dans la conception du réacteur. Est-ce que cela risque d'être un problème pour moi ?

Si je dispose de plusieurs cœurs de processeur, dois-je créer autant de threads ou de processus bifurqués et les configurer pour qu'ils fonctionnent sur chaque cœur de processeur ?

Qu'en est-il du verrouillage, etc. J'aimerais avoir des suggestions de conception. Je soupçonne que mon principal goulot d'étranglement sera l'entrée/sortie de disque, mais néanmoins... Je veux une bonne conception dès le départ sans avoir à retravailler beaucoup plus tard.

Des suggestions ?

9voto

Dummy00001 Points 6088

Je suis en train de développer une application sous Linux qui devra supporter environ 250 connexions et transmettre de gros fichiers sur des sockets TCP dans la gamme de taille 100MB+. Le but est de régler le débit plutôt que la latence. Je veux garder des connexions ethernet 2x1Gbit saturées à tout moment. Celles-ci seront liées à un canal.

Les entrées-sorties sur disque sont généralement plus lentes que sur le réseau. 250 clients, ce n'est rien pour les processeurs modernes.

Et la taille des fichiers n'est pas importante. La vraie question est de savoir si la quantité totale de données s'adapte à la RAM ou non - et si la RAM peut être étendue pour que les données s'y adaptent. Si les données rentrent dans la RAM, alors ne vous embêtez pas à sur-optimiser : un serveur bête à un seul fil avec sendfile() ferait l'affaire.

Le SSD doit être envisagé pour le stockage, surtout si la lecture des données est la priorité.

On s'attend à ce que l'application soit occupée en permanence et qu'elle envoie des données aussi rapidement que possible. Les connexions resteront actives la plupart du temps et, contrairement à HTTP, elles ne seront pas interrompues aussi souvent.

"Aussi vite que possible" est une recette pour un désastre. Je suis responsable d'au moins un tel désastre multithread qui ne peut tout simplement pas évoluer en raison de la quantité de recherches sur disque qu'il entraîne.

En général, on peut vouloir avoir quelques (par exemple, 4) fils de lecture de disque par stockage qui appelleraient read() o sendfile() pour les très gros blocs afin que le système d'exploitation ait la possibilité d'optimiser les entrées-sorties. Peu de threads sont nécessaires car on veut être optimiste sur le fait que certaines données peuvent être servies à partir du cache IO du système d'exploitation en parallèle.

N'oubliez pas de définir également un tampon d'envoi de socket important. Dans votre cas, il est également utile de vérifier la possibilité d'écrire sur la socket : si le client ne peut pas recevoir aussi vite que vous pouvez lire/envoyer, il est inutile de lire. Le canal réseau de votre serveur est peut-être gros, mais les cartes réseau/disques des clients ne le sont pas.

J'ai examiné les différentes options telles que epoll, sendfile api etc. pour les hautes performances et aio (qui semble trop immature et risqué IMHO).

Pratiquement tous les serveurs FTP utilisent désormais sendfile() . Oracle utilise AIO et Linux est leur principale plate-forme.

J'ai également examiné l'api boost asio qui utilise epoll en dessous. Je l'ai déjà utilisé mais pas pour une application de haute performance comme celle-ci.

IIRC c'est seulement pour les douilles. IMO : tout utilitaire qui facilite la manipulation des sockets est bon.

J'ai plus de 4 cœurs de processeur disponibles, je peux donc en faire usage.

Le TCP est accéléré par les NIC et l'IO des disques est en grande partie réalisée par les contrôleurs eux-mêmes. Idéalement, votre application devrait être inactive et attendre l'entrée en communication du disque.

Cependant, j'ai lu que le boost asio n'est pas très bon avec des fils multiples à cause d'un certain verrouillage dans la conception du réacteur. Est-ce que cela risque d'être un problème pour moi ?

Vérifiez le libevent comme alternative. Le nombre limité de fils dont vous aurez probablement besoin uniquement pour sendfile() . Et le nombre doit être limité, car sinon vous détruisez le débit avec les recherches sur le disque.

Si je dispose de plusieurs cœurs de processeur, dois-je créer autant de threads ou de processus bifurqués et les configurer pour qu'ils fonctionnent sur chaque cœur de processeur ?

Non. Les disques sont les plus affectés par les recherches. (Ai-je répété cela suffisamment de fois ?) Et si vous aviez de nombreux fils de lecture autonomes, vous perdriez la possibilité de contrôler les entrées-sorties qui sont envoyées au disque.

Considérez le pire des cas. Tous les read()s doit aller sur le disque == plus de threads, plus de recherches sur le disque.

Considérez le meilleur cas. Tous les read()s sont servis à partir du cache == aucune entrée/sortie. Vous travaillez alors à la vitesse de la RAM et n'avez probablement pas besoin de threads (la RAM est plus rapide que le réseau).

Qu'en est-il du verrouillage, etc. J'aimerais avoir des suggestions de conception. Je pense que mon principal goulot d'étranglement sera l'entrée/sortie du disque, mais néanmoins...

C'est une question dont la réponse est très très longue et qui n'a pas sa place ici (et je n'ai pas le temps de l'écrire). Et cela dépend aussi largement de la quantité de données que vous allez servir, du type de stockage que vous utilisez et de la manière dont vous accédez au stockage.

Si nous prenons le SSD comme stockage, alors n'importe quelle conception stupide (comme démarrer un fil pour chaque client) fonctionnerait bien. Si vous avez de vrais supports rotatifs en arrière-plan, vous devez alors découper et mettre en file d'attente les demandes d'E/S des clients, en essayant d'une part d'éviter d'affamer les clients et d'autre part de planifier les E/S de manière à provoquer le moins de recherches possible.

Personnellement, j'aurais commencé par un simple design monofilaire avec poll() (ou boost.asio ou libevent) dans la boucle principale. Si les données sont mises en cache, il n'y a aucun intérêt à démarrer un nouveau thread. Si les données doivent être récupérées sur le disque, la conception monofilaire permet d'éviter les recherches. Remplir le tampon de la socket avec les données lues et passer en mode POLLOUT pour savoir quand le client a consommé les données et est prêt à recevoir le prochain morceau. Cela signifie que j'aurais au moins trois types de sockets dans la boucle principale : socket d'écoute, socket client dont j'attends la requête, socket client dont j'attends qu'il redevienne accessible en écriture.

Je veux un bon design dès le départ, sans avoir à le retravailler par la suite.

Ah... de beaux rêves......

2voto

caf Points 114951

sendfile() est définitivement la solution à adopter si vous envoyez de grandes quantités de données séquentielles à partir de fichiers sur disque. epoll() ne sera probablement pas particulièrement utile - elle est surtout utile lorsque vous avez affaire à de grandes quantités d'eau. numéros de connexions. 250 n'est pas très grand du tout, donc le simple bon vieux select() o poll() sera probablement tout aussi bon.

1voto

Timo Geusch Points 16952

À mon avis, votre principal problème sera les E/S de disque - le service de fichiers n'est généralement pas lié au processeur, donc plusieurs cœurs ne vous aideront pas nécessairement. Les choses s'aggravent un peu si vous servez beaucoup de fichiers différents, comme vous semblez l'impliquer ; à ce stade, les lectures simultanées sur le disque vont vous causer de gros problèmes.

J'essaierais de mettre en mémoire cache autant de données que possible et de les servir afin d'accélérer les choses.

0voto

MarkR Points 37178

Faites la chose la plus simple qui puisse fonctionner, car il semble que même cette solution n'ait pas de problèmes de performance que vous puissiez corriger dans votre code.

Un seul fil par client, c'est une bonne chose, cela simplifie la programmation au maximum. Idéalement, n'écrivez pas du tout un serveur de fichiers personnalisé, mais utilisez un serveur déjà existant - http, rsync, etc. Le protocole Rsync est bon pour de nombreux petits fichiers car il supporte le pipelining.

Le fait d'avoir 250 fils n'est vraiment pas un problème - en fait, 1000 serait bien aussi.

Selon que les fichiers tiennent dans la mémoire vive et que la vitesse d'entrée-sortie est élevée, il se peut que vous ayez un goulot d'étranglement sur le réseau. Si votre réseau n'est que de 1-2Gbit/sec, il est probable que votre stockage puisse le battre sur les IO séquentielles, le réseau sera donc un goulot d'étranglement.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X