Comment puis-je accélérer le multithreading lors de l'envoi de fichiers volumineux de FTP vers des fichiers volumineux Azure

Aug 19 2020

Actuellement, j'ai un code qui télécharge un fichier de FTP sur le disque dur local. Il télécharge ensuite un fichier en morceaux sur Azure. Enfin, il supprime le fichier de local et ftp. Ce code est cependant très lent. Je voulais juste savoir comment l'améliorer.

    private async Task UploadToBlobJobAsync(FtpConfiguration ftpConfiguration, BlobStorageConfiguration blobStorageConfiguration, string fileExtension)
    {
        try
        {
               ftpConfiguration.FileExtension = fileExtension;

                var filesToProcess = FileHelper.GetAllFileNames(ftpConfiguration).ToList();
                
                var batchSize = 4;
                List<Task> uploadBlobToStorageTasks = new List<Task>(batchSize);

                for (int i = 0; i < filesToProcess.Count(); i += batchSize)
                {
                    // calculated the remaining items to avoid an OutOfRangeException
                    batchSize = filesToProcess.Count() - i > batchSize ? batchSize : filesToProcess.Count() - i;

                    for (int j = i; j < i + batchSize; j++)
                    {
                        var fileName = filesToProcess[j];
                        var localFilePath = SaveFileToLocalAndGetLocation(ftpConfiguration, ftpConfiguration.FolderPath, fileName);

                        // Spin off a background task to process the file we just downloaded
                        uploadBlobToStorageTasks.Add(Task.Run(() =>
                        {
                            // Process the file
                            UploadFile(ftpConfiguration, blobStorageConfiguration, fileName, localFilePath).ConfigureAwait(false);
                        }));
                    }

                    Task.WaitAll(uploadBlobToStorageTasks.ToArray());
                    uploadBlobToStorageTasks.Clear();
                }
        }
        catch (Exception ex)
        {
        }
    }

    private async Task UploadFile(FtpConfiguration ftpConfiguration, BlobStorageConfiguration blobStorageConfiguration, string fileName, string localFilePath)
    {
        try
        {
            await UploadLargeFiles(GetBlobStorageConfiguration(blobStorageConfiguration), fileName, localFilePath).ConfigureAwait(false);
    FileHelper.DeleteFile(ftpConfiguration, fileName); // delete file from ftp
        }
        catch (Exception exception)
        {
        }
    }

   private async Task UploadLargeFiles(BlobStorageConfiguration blobStorageConfiguration, string fileName, string localFilePath)
    {
        try
        {
            var output = await UploadFileAsBlockBlob(localFilePath, blobStorageConfiguration).ConfigureAwait(false);

            // delete the file from local
            Logger.LogInformation($"Deleting {fileName} from the local folder. Path is {localFilePath}.");

            if (File.Exists(localFilePath))
            {
                File.Delete(localFilePath);
            }
        }
        catch (Exception ex)
        {
        }
    }

    private async Task UploadFileAsBlockBlob(string sourceFilePath, BlobStorageConfiguration blobStorageConfiguration)
    {
        string fileName = Path.GetFileName(sourceFilePath);
        try
        {
            var storageAccount = CloudStorageAccount.Parse(blobStorageConfiguration.ConnectionString);
            var blobClient = storageAccount.CreateCloudBlobClient();
            var cloudContainer = blobClient.GetContainerReference(blobStorageConfiguration.Container);
            await cloudContainer.CreateIfNotExistsAsync().ConfigureAwait(false);

            var directory = cloudContainer.GetDirectoryReference(blobStorageConfiguration.Path);
            var blob = directory.GetBlockBlobReference(fileName);

            var blocklist = new HashSet<string>();

            byte[] bytes = File.ReadAllBytes(sourceFilePath);

            const long pageSizeInBytes = 10485760 * 20; // 20mb at a time
            long prevLastByte = 0;
            long bytesRemain = bytes.Length;

            do
            {
                long bytesToCopy = Math.Min(bytesRemain, pageSizeInBytes);
                byte[] bytesToSend = new byte[bytesToCopy];

                Array.Copy(bytes, prevLastByte, bytesToSend, 0, bytesToCopy);

                prevLastByte += bytesToCopy;
                bytesRemain -= bytesToCopy;

                // create blockId
                string blockId = Guid.NewGuid().ToString();
                string base64BlockId = Convert.ToBase64String(Encoding.UTF8.GetBytes(blockId));

                await blob.PutBlockAsync(base64BlockId, new MemoryStream(bytesToSend, true), null).ConfigureAwait(false);

                blocklist.Add(base64BlockId);
            }
            while (bytesRemain > 0);

            // post blocklist
            await blob.PutBlockListAsync(blocklist).ConfigureAwait(false);
        }
        catch (Exception ex)
        {
        }
    }

Réponses

1 Blindy Aug 20 2020 at 00:25

Tout d'abord, n'écrivez rien sur le disque dont vous n'avez pas besoin. Votre objectif ici n'est pas tout à fait clair, mais je ne vois pas pourquoi vous auriez un tel besoin en premier lieu.

Cela dit, si vous prenez votre fonction d'envoi dans le vide, ce qu'elle fait actuellement est :

  1. Lisez tout votre fichier volumineux (comme vous le dites) en mémoire

  2. Pour chaque bloc, vous allouez un tout nouveau tableau, copiez le bloc, placez un MemoryStreamdessus, puis envoyez-le.

Ce n'est pas ainsi que le streaming est fait.

Au lieu de cela, vous devez ouvrir un flux de fichiers sans rien lire, puis boucler dessus pour autant de morceaux que nécessaire et lire chaque morceau individuellement dans un tampon pré-alloué (ne continuez pas à allouer de nouveaux tableaux d'octets), obtenez la représentation base64 si vous en avez vraiment besoin et envoyez le morceau, puis continuez à boucler. Votre éboueur vous remerciera.