- Transfert par fragments (Chunking)
Diviser les fichiers de grande taille en segments plus petits permet de réduire la charge mémoire du serveur et d'améliorer la résilience du réseau. Cette approche facilite également la reprise sur erreur en cas d'interruption.
Exemple d'implémentation pour la réception d'un fragment :
import org.springframework.web.multipart.MultipartFile;
import java.nio.file.*;
import java.io.IOException;
public void receiveChunk(MultipartFile payload, int segmentIndex, int totalSegments) throws IOException {
Path targetDir = Paths.get("/data/uploads");
Path targetFile = targetDir.resolve(payload.getOriginalFilename());
if (segmentIndex == 0) {
Files.createDirectories(targetDir);
Files.deleteIfExists(targetFile);
Files.createFile(targetFile);
}
long offset = (long) segmentIndex * SEGMENT_MAX_SIZE;
try (SeekableByteChannel channel = Files.newByteChannel(targetFile, StandardOpenOption.WRITE)) {
channel.position(offset);
channel.write(java.nio.ByteBuffer.wrap(payload.getBytes()));
}
if (segmentIndex == totalSegments - 1) {
// Déclencher la validation ou le traitement post-assemblage
}
}
- Exécution parallèle et concurrence
L'utilisation de pools de threads permet de traiter plusieurs segments simultanément, maximisant ainsi la bande passante réseau et accélérant le transfert global.
Exemple de division et d'envoi parallèle :
import java.util.concurrent.*;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.List;
public void dispatchParallelUpload(Path sourceFile) throws Exception {
int threadCount = 8;
ExecutorService pool = Executors.newFixedThreadPool(threadCount);
long fileSize = Files.size(sourceFile);
long partSize = fileSize / threadCount;
List<Future<?>> futures = new ArrayList<>();
for (int idx = 0; idx < threadCount; idx++) {
long startPos = idx * partSize;
long endPos = (idx == threadCount - 1) ? fileSize : startPos + partSize;
futures.add(pool.submit(new ChunkUploader(sourceFile, startPos, endPos)));
}
for (Future<?> f : futures) {
f.get(); // Attendre la fin de tous les segments
}
pool.shutdown();
}
- Traitement en flux continu (Streaming)
Le traitement en flux évite de charger l'intégralité du fichier en mémoire. Les données sont lues et écrites par petits blocs, ce qui est indispensable pour les fichiers dépassant la capacité de la RAM.
Exemple de téléchargement en flux :
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URI;
import java.nio.file.*;
public void downloadAsStream(String remoteUrl, Path localDestination) throws IOException {
HttpURLConnection connection = (HttpURLConnection) URI.create(remoteUrl).toURL().openConnection();
connection.setRequestMethod("GET");
try (InputStream networkStream = connection.getInputStream();
OutputStream fileStream = Files.newOutputStream(localDestination, StandardOpenOption.CREATE)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = networkStream.read(buffer)) != -1) {
fileStream.write(buffer, 0, bytesRead);
}
} finally {
connection.disconnect();
}
}
- Exploitation de l'API NIO
L'API NIO (New I/O) de Java offre des opérations de bas niveau plus performantes, notamment grâce au transfert zero-copy qui évite de copier les données entre l'espace noyau et l'espace utilisateur.
Exemple de copie optimisée via NIO :
import java.nio.channels.*;
import java.nio.file.*;
import java.io.IOException;
public void zeroCopyTransfer(Path origin, Path destination) throws IOException {
try (FileChannel inputChannel = FileChannel.open(origin, StandardOpenOption.READ);
FileChannel outputChannel = FileChannel.open(destination, StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
long transferred = 0;
long totalSize = inputChannel.size();
while (transferred < totalSize) {
// transferTo peut ne pas transférer tous les octets en un seul appel
transferred += inputChannel.transferTo(transferred, totalSize - transferred, outputChannel);
}
}
}
- Asynchronisme via les files d'attente de messages
Déléguer les opérations lourdes de traitement de fichiers à des workers via un broker de messages permet de garder le service principal réactif et de découpler l'architecture.
Exemple de pulbication d'une tâche vers RabbitMQ :
import com.rabbitmq.client.*;
import java.nio.charset.StandardCharsets;
public void enqueueProcessingTask(String queueName, String fileMetadataJson) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("rabbitmq.internal");
factory.setPort(5672);
try (Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) {
channel.queueDeclare(queueName, true, false, false, null);
byte[] payload = fileMetadataJson.getBytes(StandardCharsets.UTF_8);
channel.basicPublish("", queueName,
new AMQP.BasicProperties.Builder().deliveryMode(2).build(),
payload);
}
}