Come posso rendere più veloce il multithreading nell'invio di file di grandi dimensioni da FTP a file di grandi dimensioni di Azure
Attualmente, ho un codice che scarica un file da FTP sul disco rigido locale. Quindi carica un file in blocchi in Azure. Infine, cancella il file da local e ftp. Questo codice è molto lento però. Volevo solo sapere come migliorarlo.
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)
{
}
}
Risposte
Innanzitutto, non scrivere su disco nulla di cui non hai bisogno. Non è del tutto chiaro quale sia il tuo obiettivo qui, ma non riesco a capire perché avresti un tale bisogno in primo luogo.
Detto questo, se prendi la tua funzione di invio nel vuoto, quello che fa adesso è:
Leggi tutto il tuo file di grandi dimensioni (come dici tu) in memoria
Per ogni blocco, allochi un array completamente nuovo, copi il blocco, mettici sopra un
MemoryStream
e poi lo invii.
Non è così che si fa lo streaming.
Invece, dovresti aprire un flusso di file senza leggere nulla, quindi eseguirne il loop per tutti i blocchi di cui hai bisogno e leggere ogni blocco individualmente in un buffer pre-allocato (non continuare ad allocare nuovi array di byte), ottenere la rappresentazione base64 se ne hai davvero bisogno e invia il blocco, quindi continua a ripetere. Il tuo netturbino ti ringrazierà.