У нас есть служба, которая генерирует сообщения и записывает их в добавочный BLOB-объект, это прекрасно работает на машине windows, но тот же код, опубликованный и работающий на машине linux, вызовет утечку памяти в процессе, даже если фактическое потребление памяти очень мало (при печати G C .GetTotalMemory). Я создал небольшое консольное приложение, которое имитирует то, что должен делать наш сервис, а также отображает утечку памяти (хотя и более медленную). (код приведен ниже). Наша главная цель состояла в том, чтобы этот сервис работал в Кубернетесе, где он легко перевалил за 750 МБ памяти за часы.
В нашей тестовой среде используется следующая версия: Azure .Storage.Blobs 12.4.0 Ubuntu 16.04,. net core 3.1
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Specialized;
namespace StorageTester
{
class Program
{
static BlobContainerClient _container;
static async Task Main(string[] args)
{
List<Reading> fakeBatch = new List<Reading>(20000);
var connectionString = < yourConnectionString >
_container = new BlobContainerClient(connectionString, "testblob");
Random r = new Random();
while (true)
{
string deviceId = r.Next(0, 1000).ToString();
Reading rawReading = new Reading()
{
AppId = "MyAppId",
DeviceId = deviceId,
ProductId = "MyProdId",
ReadingTimestamp = DateTime.Now,
Name = "Temp",
TypeCode = 70,
RawData =
"{\"app_id\":\"MyAppId\",\"dateTime\":\"2020-03-09T08:00:00.1210903Z\",\"type\":70,\"name\":\"Temp\",\"product_id\":\"MyProdId\",\"device_id\":\"MyDeviceId\",\"value\":\"123456\"}"
};
fakeBatch.Add(rawReading);
if (fakeBatch.Count == 10000)
{
await UploadBulk(fakeBatch);
fakeBatch.Clear();
Console.WriteLine("Memory used before collection: {0:N0}",
GC.GetTotalMemory(false));
// Collect all generations of memory.
GC.Collect(GC.MaxGeneration, GCCollectionMode.Optimized, false);
Console.WriteLine("Memory used after optimized background collection: {0:N0}",
GC.GetTotalMemory(true));
Thread.Sleep(10000);
}
}
}
private static string CreateAllValuesKey(string appId, string deviceId, int typeCode, string eventName, DateTime date)
{
return appId + "/" + deviceId + "/" + typeCode + "/" + eventName + "/" + date.ToString("yy-MM-dd");
}
private static async Task Upload(KeyValuePair<string, string> keyValuePair)
{
AppendBlobClient appendBlob = _container.GetAppendBlobClient(keyValuePair.Key);
if (!(await appendBlob.ExistsAsync()))
{
await appendBlob.CreateAsync();
await AppendText(appendBlob, keyValuePair.Value);
}
else
{
await AppendText(appendBlob, "," + keyValuePair.Value);
}
}
private static async Task AppendText(AppendBlobClient appendBlob, string textToAppend)
{
// use append block as append text seems to have an error at the moment.
using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(textToAppend)))
{
await appendBlob.AppendBlockAsync(ms);
}
}
private static async Task UploadBulk(List<Reading> input)
{
var groupedReadings = input.GroupBy(g => new
{ g.AppId, g.DeviceId, g.TypeCode, g.Name, g.ReadingTimestamp.ToUniversalTime().Date });
List<Task> tasks = new List<Task>();
foreach (var readingGroup in groupedReadings)
{
if (readingGroup.Any())
{
var readingSample = readingGroup.First();
string appId = readingSample.AppId;
var key = CreateAllValuesKey(appId, readingSample.DeviceId, readingSample.TypeCode,
readingSample.Name, readingSample.ReadingTimestamp.ToUniversalTime().Date);
var resultString = string.Join(",", readingGroup.Select(r => r.RawData));
tasks.Add(Upload(new KeyValuePair<string, string>(key, resultString)));
}
}
await Task.WhenAll(tasks);
}
internal class Reading
{
public string AppId { get; set; }
public string DeviceId { get; set; }
public string ProductId { get; set; }
public DateTime ReadingTimestamp { get; set; }
public string Name { get; set; }
public int TypeCode { get; set; }
public string RawData { get; set; }
}
}
}