[英]Can I create and Save a file in Azure storage at the same time?
我正在尝试创建 CSV 文件并将其导入 Azure 存储帐户。
public static void ExportCsvToStorageAccount(string fileName, CloudBlobContainer container, IEnumerable<ReportRow> reportEntries)
{
using (var ms = new MemoryStream())
{
using (var file = new StreamWriter(ms))
{
file.WriteLine("Date,StoreId,ItemId,SalesQuantity");
foreach (var row in reportEntries)
{
var line = $"\"{row.Date}\",\"{row.StoreId}\",\"{row.ItemId}\",\"{row.SalesQuantity}\"";
file.WriteLine(line);
}
var blockBlob = container.GetBlockBlobReference($"{fileName}.csv");
ms.Position = 0;
blockBlob.UploadFromStream(ms);
}
}
}
我在 memory 中创建文件,然后将其复制并上传到 azure。
我的“问题”是为此我需要首先将整个文件保存在 memory 中,然后才开始复制(如果文件太大并且机器内存不足,这可能是一个问题)。
Ideally i could write directly into azure or as soon as i filled my memory stream buffer i would copy it to azure and then write again on top of it instead of allocating more space in me memory stream buffer.
有没有办法直接写入Azure? (目标是节省内存)
编辑:
通过 Gaurav Mantri-AIS 输入的答案,我想出了这个(因为我有超过 50000 个条目,这是块的限制),
public static void ExportCSVToStorageAccount(string fileName, CloudBlobContainer container, IEnumerable<RawReportRow> reportEntries)
{
var blob = container.GetAppendBlobReference($"{fileName}.csv");
blob.CreateOrReplace();
blob.AppendText($"Date,StoreId,ItemId,SalesQuantity{Environment.NewLine}");
foreach (var row in reportEntries)
{
var line = $"\"{row.Date}\",\"{row.StoreId}\",\"{row.ItemId}\",\"{row.SalesQuantity}\"{Environment.NewLine}";
blob.AppendText(line);
}
}
这个解决方案的问题是它需要的时间太长,从 5 分钟到一个多小时。 我可能做错了什么,因为 AppendBlob 应该执行良好的附加,但似乎并非如此。
关于如何提高写入速度的任何想法?
这样做当然是可能的。 一种解决方案是使用StringBuilder
并继续向其中添加数据。 添加所有数据后,创建一个字节数组,然后从中创建一个 memory stream 并上传 memory2F23E544CFAFDZ9196C84
这是示例代码(虽然未经测试):
public static void ExportCsvToStorageAccount(string fileName, CloudBlobContainer container, IEnumerable<ReportRow> reportEntries)
{
using (var ms = new MemoryStream())
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("Date,StoreId,ItemId,SalesQuantity");
foreach (var row in reportEntries)
{
var line = $"\"{row.Date}\",\"{row.StoreId}\",\"{row.ItemId}\",\"{row.SalesQuantity}\"";
sb.AppendLine(line);
}
var buffer = Encoding.UTF8.GetBytes(sb.ToString());
ms.Write(buffer, 0, buffer.Length);
var blockBlob = container.GetBlockBlobReference($"{fileName}.csv");
ms.Position = 0;
blockBlob.UploadFromStream(ms);
}
}
更新
假设您使用的是 SDK 版本 9.3.3,您可以使用UploadText
方法将字符串直接上传到 Azure 存储。 就像是:
public static void ExportCsvToStorageAccount(string fileName, CloudBlobContainer container, IEnumerable<string> reportEntries)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("Date,StoreId,ItemId,SalesQuantity");
foreach (var row in reportEntries)
{
var line = $"\"{row.Date}\",\"{row.StoreId}\",\"{row.ItemId}\",\"{row.SalesQuantity}\"";
sb.AppendLine(line);
}
var blockBlob = container.GetBlockBlobReference($"{fileName}.csv");
blockBlob.UploadText(sb.ToString());
}
更新 2
另一种选择是将每一行作为单独的块上传,然后最终提交块列表。 但是请记住,一个 blob 中只能有 50000 个块,如果您的数据中有超过 50000 条记录,此方法将失败。 为了规避这个限制,您可能希望合并某些记录并将它们保存为一个块。
这是示例代码:
public static void ExportCsvToStorageAccount(string fileName, CloudBlobContainer container, IEnumerable<string> reportEntries)
{
List<string> blockIds = new List<string>();
CloudBlockBlob blob = container.GetBlockBlobReference(fileName);
int counter = 0;
foreach (var row in reportEntries)
{
var line = $"\"{row.Date}\",\"{row.StoreId}\",\"{row.ItemId}\",\"{row.SalesQuantity}\"";
var blockId = Convert.ToBase64String(Encoding.UTF8.GetBytes(counter.ToString("d6")));
blob.PutBlock(blockId, new MemoryStream(Encoding.UTF8.GetBytes(line)), string.Empty);
blockIds.Add(blockId);
counter++;
}
blob.PutBlockList(blockIds);
}
我将有一个 go,主要基于Gaurav Mantri-AIS 的回答。 因为我认为你们正在做某事。
让我们在这里共同努力...一方面,您希望尽快写入 Blob 以限制 memory 的使用。 另一方面,我们不想写每一行,因为这超过了块限制。 所以我们需要在 memory 中有X 条记录,然后再将其写入 blob。
我在这里尝试使用X值为 50 的一些伪代码。我认为这个值可以(并且应该)针对 memory 的使用、性能和块数进行优化:
public static void ExportCsvToStorageAccount(string fileName, CloudBlobContainer container, IEnumerable<string> reportEntries)
{
List<string> blockIds = new List<string>();
CloudBlockBlob blob = container.GetBlockBlobReference(fileName);
int counter = 0;
StringBuilder builder = new StringBuilder();
foreach (var row in reportEntries)
{
builder.Append($"\"{row.Date}\",\"{row.StoreId}\",\"{row.ItemId}\",\"{row.SalesQuantity}\"");
counter++;
if (counter % 50 == 0)
{
var blockId = Convert.ToBase64String(Encoding.UTF8.GetBytes(counter.ToString("d6")));
blob.PutBlock(blockId, new MemoryStream(Encoding.UTF8.GetBytes(line)), string.Empty);
builder = new StringBuilder();
blockIds.Add(blockId);
}
}
// Check if there's anything still in the String Builder and write it
if (builder.Length != 0)
{
var blockId = Convert.ToBase64String(Encoding.UTF8.GetBytes(counter.ToString("d6")));
blob.PutBlock(blockId, new MemoryStream(Encoding.UTF8.GetBytes(line)), string.Empty);
}
blob.PutBlockList(blockIds);
}
要考虑的另一件事是,Azure Function 存储绑定使您能够将 blob 绑定到Stream
。 这让我有两件事需要考虑:
编辑:
我深入研究了azure-webjobs-sdk
的源代码,发现它使用CloudBlobStream
。 尽管它被标记为旧版,但您仍然可以通过在CloudBlobStream
上调用OpenWriteAsync
来获取CloudBlockBlob
。 我没有时间测试一个例子,但我确实在这里找到了这个例子: Uploading a file to Azure Blob on the fly 。
public async Task<Stream> GetWriteStreamAsync(string storagePath, string contentType)
{
var blockBlob = blobContainer.GetBlockBlobReference(storagePath);
blockBlob.Properties.ContentType = contentType;
CloudBlobStream bb = await blockBlob.OpenWriteAsync();
return bb;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.