Как кэшировать медленную инициализацию ресурсов с сервера CEST Web REST? - PullRequest
0 голосов
/ 28 февраля 2019

Контекст

Я пытаюсь реализовать веб-службу API REST, которая "оборачивает" существующую программу на Си.

Проблема / цель

Учитывая, что программа на C имеет медленное время инициализации и высокое использование ОЗУ, когда я говорю ей открыть определенную папку (предположим, это не может быть улучшено), я имею в виду кэширование дескриптора / объекта C, поэтому следующийКогда запрос GET попадает в ту же папку, я могу использовать существующий дескриптор.

То, что я пробовал

Сначала объявляем статическое сопоставление словаря из пути к папке для обработки:

static ConcurrentDictionary<string, IHandle> handles = new ConcurrentDictionary<string, IHandle>();

В моей функции GET:

IHandle theHandle = handles.GetOrAdd(dir.Name, x => {
    return new Handle(x); //this is the slow and memory-intensive function
});

Таким образом, всякий раз, когда определенная папка была получена ранее, она уже будет иметь готовый для использования дескриптор.

Почему это нехорошо

Так что теперь я рискую исчерпать память, если слишком много папок кэшируется одновременно.Как я могу добавить GC-подобный фоновый процесс к TryRemove() и вызвать IHandle.Dispose() на старых дескрипторах, возможно, в политике наименьшего недавно использовавшегося или наименьшего часто используемого?В идеале он должен запускаться только при низком доступном объеме физической памяти.

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

HostingEnvironment.QueueBackgroundWorkItem(ct =>
{
    System.Threading.Thread.Sleep(10000);
    if (handles.TryRemove(dir.Name, out var handle2))
        handle2.Dispose();
});

Чем этот вопрос не является

Я не думаю, что кэширование выходных данных является решением здесь.После того, как я верну результат этого запроса GET (это просто метаданные содержимого папки), может быть другой запрос GET для получения более подробных данных, который требует вызова методов Handle.

Iнадеюсь, мой вопрос достаточно ясен!

Ответы [ 2 ]

0 голосов
/ 02 марта 2019

Я не уловил ваш спрос на LRU / LFU в первый раз.Здесь вы можете проверить наличие гибридной модели LRU / LFU-кэша.

  1. Ручки закрываются при нехватке памяти.
/* 
*  string – handle name,
*  IHandle – the handle,
*  int – hit count,
*/
ConcurrentDictionary<string, (IHandle, int)> handles = new ConcurrentDictionary<string, (IHandle, int)>();

void FreeResources()
{
  if (handles.Count == 0)
  {
    return;
  }

  var performance = new System.Diagnostics.PerformanceCounter("Memory", "Available MBytes");

  while (performance.NextValue() <= YOUR_TRESHHOLD)
  {
    int maxIndex = (int)Math.Ceiling(handles.Count / 2.0d);
    KeyValuePair<string, (IHandle, int)> candidate = handles.First();

    for (int index = 1; index < maxIndex; index++)
    {
      KeyValuePair<string, (IHandle, int)> item = handles.ElementAt(index);

      if(item.Value.Item2 < candidate.Value.Item2)
      {
        candidate = item;
      }          
    }

    candidate.Value.Item1.Dispose();
    handles.TryRemove(candidate.Key, out _);
  }
}
Получить метод.
IHandle GetHandle(Dir dir, int handleOpenAttemps = 1)
{
  if(handles.TryGetValue(dir.Name, out (IHandle, int) handle))
  {
    handle.Item2++;
  }
  else
  {

    if(new System.Diagnostics.PerformanceCounter("Memory", "Available MBytes").NextValue() < YOUR_TRESHHOLD)
    {
      FreeResources();
    }

    try
    {
      handle.Item1 = new Handle(dir.Name);
    }
    catch (OutOfMemoryException)
    {

      if (handleOpenAttemps == 2)
      {
        return null;
      }

      FreeResources();
      return GetHandle(dir, handleOpenAttemps++);
    }
    catch (Exception)
    {
      // Your handling.
    }

    handle.Item2 = 1;
    handles.TryAdd(dir.Name, handle);
  }       

  return handle.Item1;
}
Фоновая задача.
void SetupMemoryCheck()
{

  Action<CancellationToken> BeCheckingTheMemory = ct =>
  {

    for (;;)
    {        
      if (ct.IsCancellationRequested) break;          

      FreeResources();
      Thread.Sleep(500);        
    }
  };

  HostingEnvironment.QueueBackgroundWorkItem(ct =>
  {
    new Task(() => BeCheckingTheMemory(ct), TaskCreationOptions.LongRunning).Start();
  });
}

Если вы ожидаете большую коллекцию, цикл for можно оптимизировать.

0 голосов
/ 28 февраля 2019
  1. Ручки закрываются на малой памяти.
ConcurrentQueue<(string, IHandle)> handles = new ConcurrentQueue<(string, IHandle)>();

void CheckMemory_OptionallyReleaseOldHandles()
{
  var performance = new System.Diagnostics.PerformanceCounter("Memory", "Available MBytes");

  while (performance.NextValue() <= YOUR_TRESHHOLD)
  {
    if (handles.TryDequeue(out ValueTuple<string, IHandle> value))
    {
      value.Item2.Dispose();
    }
  }
}
Ваш метод Get.
IHandle GetHandle()
{
  IHandle theHandle = handles.FirstOrDefault(v => v.Item1 == dir.Name).Item2;

  if (theHandle == null)
  {
    theHandle = new Handle(dir.Name);
    handles.Enqueue((dir.Name, theHandle));
  }

  return theHandle;
});
Ваша фоновая задача.
void SetupMemoryCheck()
{

  Action<CancellationToken> BeCheckingTheMemory = ct =>
  {

    for(;;)
    {
      if (ct.IsCancellationRequested)
      {
        break;
      }

      CheckMemory_OptionallyReleaseOldHandles();
      Thread.Sleep(500);
    };
  };

  HostingEnvironment.QueueBackgroundWorkItem(ct =>
  {
    var tf = new TaskFactory(ct, TaskCreationOptions.LongRunning, TaskContinuationOptions.None, TaskScheduler.Current);
    tf.StartNew(() => BeCheckingTheMemory(ct));
  });
}

Полагаю, в коллекции будут маленькие элементы, поэтому нет необходимости в словаре.

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