Выполнение процесса в IIS ускоряет увеличение оперативной памяти - PullRequest
0 голосов
/ 30 октября 2018

Я построил ASP.NET MVC API, размещенный на IIS в Windows 10 Pro (ВМ на Azure - 4 ГБ ОЗУ, 2CPU). Внутри я вызываю .exe ( wkhtmltopdf ), который хочу преобразовать HTML-страницу в изображение и сохранить ее локально. Все работает отлично, за исключением того, что я заметил, что после некоторых вызовов API ОЗУ сходит с ума, и, исследуя процесс с помощью диспетчера задач, я увидел процесс, называемый IIS Worker Process, который добавляет больше ОЗУ при каждом вызове API. Конечно, я завернул использование экземпляра System.Diagnostics.Process внутри оператора using для удаления, потому что реализован IDisposable, но он по-прежнему потребляет все больше и больше оперативной памяти, и через некоторое время сервер становится неактивным и не отвечает (у него всего 4 ГБ ОЗУ). в конце концов). Я заметил, что через некоторое количество минут (возможно, 10-15-20) этот рабочий процесс IIS успокаивается с точки зрения использования оперативной памяти ... Вот мой код, довольно простой:

  1. Получает URL в кодировке base64
  2. Расшифровывает это
  3. Использует wkhtmltoimage.exe для преобразования его в изображение
  4. Сохраняет локально
  5. Считывает байтовый массив
  6. Создает BLOB-объект в Azure с изображением
  7. Возвращает JSON с URL

    public async Task<ActionResult> Index(string url)
    {
        object oJSON = new { url = string.Empty };
    
        if (!string.IsNullOrEmpty(value: url))
        {
            try
            {
                byte[] EncodedData = Convert.FromBase64String(s: url);
                string DecodedURL = Encoding.UTF8.GetString(bytes: EncodedData);
    
                using (Process proc = new Process())
                {
                    proc.StartInfo.FileName = wkhtmltopdfExecutablePath;
                    proc.StartInfo.Arguments = $"--encoding utf-8 \"{DecodedURL}\" {LocalImageFilePath}";
                    proc.Start();
                    proc.WaitForExit();
    
                    oJSON = new { procStatusCode = proc.ExitCode };
                }
    
                if (System.IO.File.Exists(path: LocalImageFilePath))
                {
                    byte[] pngBytes = System.IO.File.ReadAllBytes(path: LocalImageFilePath);
    
                    System.IO.File.Delete(path: LocalImageFilePath);
    
                    string ImageURL = await CreateBlob(blobName: $"{BlobName}.png", data: pngBytes);
    
                    oJSON = new { url = ImageURL };
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(value: ex);
            }
        }
    
        return Json(data: oJSON, behavior: JsonRequestBehavior.AllowGet);
    }
    
    private async Task<string> CreateBlob(string blobName, byte[] data)
    {
        string ConnectionString = "DefaultEndpointsProtocol=https;AccountName=" + AzureStorrageAccountName + ";AccountKey=" + AzureStorageAccessKey + ";EndpointSuffix=core.windows.net";
        CloudStorageAccount cloudStorageAccount = CloudStorageAccount.Parse(connectionString: ConnectionString);
        CloudBlobClient cloudBlobClient = cloudStorageAccount.CreateCloudBlobClient();
        CloudBlobContainer cloudBlobContainer = cloudBlobClient.GetContainerReference(containerName: AzureBlobContainer);
    
        await cloudBlobContainer.CreateIfNotExistsAsync();
    
        BlobContainerPermissions blobContainerPermissions = await cloudBlobContainer.GetPermissionsAsync();
        blobContainerPermissions.PublicAccess = BlobContainerPublicAccessType.Container;
    
        await cloudBlobContainer.SetPermissionsAsync(permissions: blobContainerPermissions);
    
        CloudBlockBlob cloudBlockBlob = cloudBlobContainer.GetBlockBlobReference(blobName: blobName);
        cloudBlockBlob.Properties.ContentType = "image/png";
    
        using (Stream stream = new MemoryStream(buffer: data))
        {
            await cloudBlockBlob.UploadFromStreamAsync(source: stream);
        }
    
        return cloudBlockBlob.Uri.AbsoluteUri;
    }
    

Вот ресурсы, которые я читаю, так или иначе связанные с этой проблемой IMO, но они мало помогают:

Исследование дампов памяти ASP.Net для идиотов (таких как я)

Приложение ASP.NET ест память. Объекты Application / Session причина?

Рабочий процесс IIS, использующий много памяти?

Запуск метода dispose при перезапуске приложения IIS asp.net

IIS: время ожидания простоя против перезапуска

UPDATE:

if (System.IO.File.Exists(path: LocalImageFilePath))
{
    string BlobName = Guid.NewGuid().ToString(format: "n");
    string ImageURL = string.Empty;

    using(FileStream fileStream = new FileStream(LocalImageFilePath, FileMode.Open)
    {
        ImageURL = await CreateBlob(blobName: $"{BlobName}.png", dataStream: fileStream);
    }

    System.IO.File.Delete(path: LocalImageFilePath);

    oJSON = new { url = ImageURL };
}

1 Ответ

0 голосов
/ 30 октября 2018

Наиболее вероятной причиной вашей боли является выделение больших байтовых массивов:

byte[] pngBytes = System.IO.File.ReadAllBytes(path: LocalImageFilePath);

Самое простое изменение, чтобы попытаться побудить сборщик мусора чаще собирать кучу больших объектов, это установить GCSettings.LargeObjectHeapCompactionMode в CompactOnce в конце метода. Это может помочь.

Но лучшей идеей было бы полностью исключить необходимость в большом массиве. Для этого измените:

private async Task<string> CreateBlob(string blobName, byte[] data)

вместо:

private async Task<string> CreateBlob(string blobName, FileStream data)

А потом позже использовать:

await cloudBlockBlob.UploadFromStreamAsync(source: data);

В вызывающей программе вам нужно прекратить использовать ReadAllBytes и вместо этого использовать FileStream для чтения файла.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...