2014-09-23 2 views
10

Если я просто просматриваю некоторые страницы приложения, он находится примерно в 500 МБ. Многие из этих страниц обращаются к базе данных, но на данный момент у меня есть только примерно пара строк для 10 таблиц, в основном для хранения строк и небольших значков размером менее 50 КБ.MVC ASP.NET использует много памяти

Настоящая проблема возникает, когда я загружаю файл. Файл составляет примерно 140 МБ и хранится как varbinary(MAX) в базе данных. Использование памяти внезапно возрастает до 1,3 ГБ на долю секунды, а затем падает до 1 ГБ. Код для этого действия находится здесь:

public ActionResult DownloadIpa(int buildId) 
{ 
    var build = _unitOfWork.Repository<Build>().GetById(buildId); 
    var buildFiles = _unitOfWork.Repository<BuildFiles>().GetById(buildId); 
    if (buildFiles == null) 
    { 
     throw new HttpException(404, "Item not found"); 
    } 

    var app = _unitOfWork.Repository<App>().GetById(build.AppId); 
    var fileName = app.Name + ".ipa"; 

    app.Downloads++; 
    _unitOfWork.Repository<App>().Update(app); 
    _unitOfWork.Save(); 

    return DownloadFile(buildFiles.Ipa, fileName); 
} 

private ActionResult DownloadFile(byte[] file, string fileName, string type = "application/octet-stream") 
{ 
    if (file == null) 
    { 
     throw new HttpException(500, "Empty file"); 
    } 

    if (fileName.Equals("")) 
    { 
     throw new HttpException(500, "No name"); 
    } 

    return File(file, type, fileName);    
} 

На моем локальном компьютере, если я ничего не делаю, использование памяти составляет 1 ГБ. Если я затем вернусь и перейду к некоторым страницам, он опустится до 500 МБ.

На сервере развертывания он остается на 1,6 ГБ после первой загрузки независимо от того, что я делаю. Я могу заставить использование памяти увеличиваться, постоянно загружая файлы, пока не достигнет 3 ГБ, где он упадет до 1,6 ГБ.

В каждом контроллере, я переопределен метод Dispose() в качестве так:

protected override void Dispose(bool disposing) 
{ 
    _unitOfWork.Dispose(); 
    base.Dispose(disposing); 
} 

Это относится к:

public void Dispose() 
{ 
    Dispose(true); 
    GC.SuppressFinalize(this); 
} 

public void Dispose(bool disposing) 
{ 
    if (!_disposed) 
    { 
     if (disposing) 
     { 
      _context.Dispose(); 
     } 
    } 

    _disposed = true; 
} 

Так моя единица работы должна быть утилизирована каждый раз, когда контроллер расположен. Я использую Unity, и я регистрирую часть работы с Heirarchical Lifetime Manager.

Вот несколько скриншотов из Profiler:

enter image description here

enter image description here

enter image description here

Я считаю, что это может быть проблема или я иду по неверному пути. Зачем использовать Find() 300MB?

EDIT:

Repository:

public class Repository<TEntity> : IRepository<TEntity> where TEntity : class 
{ 
    internal IDbContext Context; 
    internal IDbSet<TEntity> DbSet; 

    public Repository(IDbContext context) 
    { 
     Context = context; 
     DbSet = Context.Set<TEntity>(); 
    } 

    public virtual IEnumerable<TEntity> GetAll() 
    {    
     return DbSet.ToList(); 
    } 

    public virtual TEntity GetById(object id) 
    { 
     return DbSet.Find(id); 
    } 

    public TEntity GetSingle(Expression<Func<TEntity, bool>> predicate) 
    { 
     return DbSet.Where(predicate).SingleOrDefault(); 
    } 

    public virtual RepositoryQuery<TEntity> Query() 
    { 
     return new RepositoryQuery<TEntity>(this); 
    } 

    internal IEnumerable<TEntity> Get(
     Expression<Func<TEntity, bool>> filter = null, 
     Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, 
     List<Expression<Func<TEntity, object>>> includeProperties = null) 
    { 
     IQueryable<TEntity> query = DbSet; 

     if (includeProperties != null) 
     { 
      includeProperties.ForEach(i => query.Include(i)); 
     } 

     if (filter != null) 
     { 
      query = query.Where(filter); 
     } 

     if (orderBy != null) 
     { 
      query = orderBy(query); 
     } 

     return query.ToList(); 
    } 

    public virtual void Insert(TEntity entity) 
    { 
     DbSet.Add(entity); 
    } 

    public virtual void Update(TEntity entity) 
    { 
     DbSet.Attach(entity); 
     Context.Entry(entity).State = EntityState.Modified; 
    } 

    public virtual void Delete(object id) 
    { 
     var entity = DbSet.Find(id); 

     Delete(entity); 
    } 

    public virtual void Delete(TEntity entity) 
    { 
     if (Context.Entry(entity).State == EntityState.Detached) 
     { 
      DbSet.Attach(entity); 
     } 

     DbSet.Remove(entity); 
    } 
} 

EDIT 2:

Я побежал dotMemory для различных сценариев, и это то, что я получил.

enter image description here

красные круги показывают, что иногда несколько поднимается и падает происходит на одной странице визита. Синий круг указывает на загрузку 40 МБ файла. Зеленый круг указывает загрузку 140 МБ файла. Кроме того, время от времени использование памяти продолжает расти еще на несколько секунд даже после мгновенной загрузки страницы.

+0

Try в JetBrains dotMemory 4. 1 beta www.jetbrains.com/dotmemory/download – leppie

+1

Не могли бы вы разместить весь репозиторий? (Некоторые люди думают, что весь шаблон репозитория - это анти-шаблон, и я один из них, но это не так). У меня подозрительное подозрение '_unitOfWork.Repository () .Update (app);' может привести к загрузке всего DbSet, но я не вижу его из кода, который вы опубликовали. – Martijn

+1

Если вы уменьшили размер файла в два раза, снизится ли использование памяти пиковой памяти? Это поможет вам разобраться, является ли это сам файл или код, вызывающий извлечение файла, вызывающего проблему. – krisdyson

ответ

2

Добавить GC.Collect() в методе Dispose для целей тестирования Если утечка остается это реальная утечка Если она исчезает это была просто задержка GC

Вы сделали это, и сказал:

@usr использование памяти теперь вряд ли достигает 600 Мб. Так что на самом деле просто задержка?

Очевидно, что утечка памяти отсутствует, если GC.Collect удаляет память, о которой вы беспокоились. Если вы хотите сделать действительно уверенным, проведите тест 10 раз. Использование памяти должно быть стабильным.

Обработка таких больших файлов в одиночных кусках может привести к умножению использования памяти, поскольку файл перемещается через различные компоненты и рамки. Это может быть хорошей идеей переключиться на потоковый подход.

0

Возможно, вам потребуется прочитать данные в кусках и записать в выходной поток. Взгляните на SqlDataReader.GetBytes http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqldatareader.getbytes(v=vs.110).aspx

+0

Это не касается его конкретной проблемы. – usr

+0

Когда вы делаете DbSet.Find, он фактически загружает результат. От Microsoft «Находит объект с указанными значениями первичного ключа. Если в контексте существует сущность с указанными значениями первичного ключа, то она немедленно возвращается без запроса в хранилище. В противном случае запрос направляется в хранилище для объект с указанными значениями первичного ключа, и этот объект, если он найден, присоединен к контексту и возвращен. Если в контексте или хранилище не найдено ни одного объекта, возвращается значение null. «http://msdn.microsoft. com/en-us/library/system.data.entity.dbset.find (v = vs.113) .aspx –

+0

Его вопрос в том, почему гораздо больше использования памяти, чем его файл большой. Кроме того, использование памяти, похоже, остается. Поистине целесообразно передавать большие файлы, но это не тот вопрос. – usr

9

Поскольку файл велик, он выделяется на больших объектах Heap, взимаемые с коллекцией Gen2 (которые вы видите в вашем профиле, пурпурные блоками являются большим объектом кучей, и вы видите его собранные через 10 секунд).

На вашем рабочем сервере у вас, скорее всего, гораздо больше памяти, чем на вашей локальной машине. Из-за меньшего давления памяти коллекции будут происходить не так часто, что объясняет, почему это будет увеличиваться до большего числа - на LOH есть несколько файлов, прежде чем они будут собраны.

Я бы не удивился, если бы через разные буферы в MVC и EF некоторые данные также копировались в небезопасных блоках, что объясняет неуправляемый рост памяти (тонкий шип для EF, широкое плато для MVC)

Наконец, 500МБ базовая линия для большого проекта не совсем неожиданном (безумие! но это так!)

так ответ на вопрос, почему он использует так много памяти, что вполне вероятно, «потому что он может », или, другими словами, потому что нет никакого давления памяти для выполнения коллекции gen2, и загруженные файлы остаются неиспользованными в большой куче объекта до тех пор, пока коллекция не выселяет их, потому что в вашем продукте много памяти ионный сервер.

Это, вероятно, даже не проблема: если бы было больше давления памяти, было бы больше коллекции, и вы могли бы использовать более низкое использование памяти.

Что касается того, что с этим делать, я боюсь, что вам не повезло с Entity Framework. Насколько я знаю, у него нет потокового API. WebAPI действительно позволяет потоковой передачи ответа, но это не поможет вам, если у вас есть все большой объект, сидящий в памяти в любом случае (хотя он может помочь некоторым с неуправляемой памятью в (по мне) неисследованными частями MVC ....

1

Видимо, это состоит из System.Web и всех его детей, занимающих около 200 МБ. Это указывается как абсолютный минимум для вашего пула приложений.

Наше веб-приложение, использующее EF 6, с моделью, состоящей из 220+ объектов в .Net 4.0, запускается со скоростью 480 МБ в режиме ожидания. При запуске мы выполняем некоторые операции AutoMapper. Пики памяти потребляют, а затем ежедневно возвращаются к 500 МБ. Мы только что приняли это как норму.

Теперь, для загрузки файлов скачков.Вопрос по веб-формам при использовании обработчика ashx и т. Д. Был изучен в этом вопросе: ASP.net memory usage during download

Я не знаю, как это относится к FileActionResult в MVC, но вы можете видеть, что размер буфера, который необходимо контролировать вручную, чтобы минимизировать всплеск памяти. Попробуйте применить принципы, лежащие в основе ответа от этого вопроса путем:

Response.BufferOutput = false; 
var stream = new MemoryStream(file); 
stream.Position = 0; 
return new FileStreamResult(stream, type); // Or just pass the "file" parameter as a stream 

После применения этих изменений, что делает поведение памяти выглядеть?

Для получения более подробной информации см. 'Debugging memory problems (MSDN)'.

0

Это может быть одна из немногих вещей:

Как ваш файл достаточно велик и хранится в базе данных, и вы получаете его через Entity Framework, вы кэшировать эти данные в нескольких местах. Каждый запрос EF кэширует данные до тех пор, пока ваш контекст не будет удален. Когда вы возвращаете файл из действия, данные затем снова загружаются и затем передаются клиенту. Все это происходит в ASP .NET, как уже объяснено.

Решение этой проблемы, чтобы не передавать большие файлы непосредственно из базы данных с EF и ASP .NET. Лучшим решением является использование фонового процесса для локальных кеш-файлов размером локально на веб-сайте, а затем загрузка клиентом с прямым URL-адресом. Это позволяет IIS управлять потоковой передачей, сохраняет ваш сайт на запрос и экономит массу памяти.

ИЛИ (менее вероятно)

Видя, что вы используете Visual Studio 2013, это звучит ужасно, как Page Inspector вопрос.

Что происходит, когда вы запускаете сайт с IIS Express из Visual Studio, Page Inspector кэшей всех данных отклика - что из файла в том числе - в результате чего много памяти для использования. Попробуйте добавить:

<appSettings> 
    <add key="PageInspector:ServerCodeMappingSupport" value="Disabled" /> 
</appSettings> 

к вашему web.config отключить Page Inspector, чтобы увидеть, если это помогает.

TL; DR

Cache большой файл локально, и пусть клиент загрузить файл непосредственно. Пусть IIS справится с тяжелой работой для вас.

0

Предлагаю попробовать библиотеку Ionic.Zip. Я использую его на одном из наших сайтов с требованием загрузить несколько файлов в один блок.

Недавно я тестировал с группой файлов в то время как один из файлов, как большая, как 600MB:

  • Общего размер архивной/сжатой папки: 260MB
  • Общего размер распакованной папки: 630MB
  • использование памяти шипами от 350 Мб до 650 Мб во время загрузки
  • Общее время: 1m 10s скачать, без VPN
Смежные вопросы