Ayant récemment travaillé sur un projet qui nécessitait plus d'interactions IO que je ne le faisais auparavant, j'avais l'impression de vouloir regarder au-delà des bibliothèques régulières (Commons IO, en particulier) et d'en aborder d'autres problèmes de profondeur d'E/S. En tant que test académique, j'ai décidé d'implémenter un téléchargeur HTTP multi-thread. L'idée est simple: fournir une URL à télécharger, et le code va télécharger le fichier. Pour augmenter les vitesses de téléchargement, le fichier est fragmenté et chaque bloc est téléchargé simultanément (en utilisant l'en-tête HTTP Range: bytes=x-x
) pour utiliser autant de bande passante que possible.Performance de téléchargement de fichiers multithread Java
J'ai un prototype fonctionnel, mais comme vous l'avez peut-être deviné, ce n'est pas exactement l'idéal. Au moment où je démarre manuellement 3 threads "téléchargeur" qui téléchargent chacun 1/3 du fichier. Ces threads utilisent une instance "d'écriture de fichier" commune et synchronisée pour écrire réellement les fichiers sur le disque. Lorsque tous les threads sont terminés, le "writer de fichier" est terminé et tous les flux ouverts sont fermés. Quelques extraits de code pour vous donner une idée:
Le fil de démarrage:
ExecutorService downloadExecutor = Executors.newFixedThreadPool(3);
...
downloadExecutor.execute(new Downloader(fileWriter, download, start1, end1));
downloadExecutor.execute(new Downloader(fileWriter, download, start2, end2));
downloadExecutor.execute(new Downloader(fileWriter, download, start3, end3));
Chaque thread « téléchargeur » télécharge un morceau (tampon) et utilise le « écrivain de fichier » pour écrire sur le disque:
int bytesRead = 0;
byte[] buffer = new byte[1024*1024];
InputStream inStream = entity.getContent();
long seekOffset = chunkStart;
while ((bytesRead = inStream.read(buffer)) != -1)
{
fileWriter.write(buffer, bytesRead, seekOffset);
seekOffset += bytesRead;
}
Le "écrivain de fichier", écrit sur le disque à l'aide d'un RandomAccessFile
à seek()
et write()
les morceaux sur le disque:
public synchronized void write(byte[] bytes, int len, long start) throws IOException
{
output.seek(start);
output.write(bytes, 0, len);
}
Tout bien considéré, cette approche semble fonctionner. Cependant, cela ne fonctionne pas très bien. J'apprécierais quelques conseils/aide/opinions sur les points suivants. Très appréciée.
- L'utilisation du processeur de ce code est à travers le toit. Pour ce faire, il utilise la moitié de mon processeur (50% de chacun des 2 cœurs), ce qui est exponentiellement plus que les outils de téléchargement comparables qui ne font que très peu de stress sur le processeur. Je suis un peu mystifié quant à l'origine de cette utilisation du processeur, car je ne m'attendais pas à cela.
- Habituellement, il semble y avoir 1 des 3 threads qui est en retard de de manière significative. Les 2 autres threads se terminent, après quoi il prend le troisième thread (qui semble être le premier thread avec le premier morceau) 30 secondes ou plus pour terminer. Je peux voir à partir du gestionnaire de tâches que le processus javaw fait encore de petites écritures d'E/S, mais je ne sais pas vraiment pourquoi cela se produit (je devine les conditions de course?).
- Malgré le fait que j'ai choisi un tampon assez volumineux (1 Mo), j'ai l'impression que le
InputStream
ne remplit pratiquement jamais le tampon, ce qui provoque plus d'écritures IO que je ne le souhaite. J'ai l'impression que dans ce scénario, il serait préférable de garder un minimum d'accès aux IO, mais je ne sais pas avec certitude si c'est la meilleure approche. - Je réalise que Java n'est peut-être pas le langage idéal pour faire quelque chose comme ça, mais je suis convaincu qu'il y a beaucoup plus de performance à avoir que ce que j'obtiens dans mon implémentation actuelle. La NIO mérite-t-elle d'être explorée dans ce cas?
Note: J'utilise Apache HTTPClient faire l'interaction HTTP, qui est où le entity.getContent()
vient de (au cas où quelqu'un se demande).
trouvé un bon sujet connexe ici: http://stackoverflow.com/questions/921262/how-to-download-and-save-a-file-from-internet-using-java POUVAIT que un essai ce soir quand je rentre à la maison :) – tmbrggmn
Mise à jour: l'utilisation élevée du processeur était due à une boucle while() lors de l'appel à la méthode ExecutorService isTerminated(). Doh! – tmbrggmn
Je pense que beaucoup dépend également des configurations réseau et des cartes d'interface réseau (physiques). Même si vous avez plusieurs threads qui travaillent sur le téléchargement du même fichier, NIC, qui est responsable de sérialiser les octets, peut devenir le goulot d'étranglement !! – TriCore