如何从C#Web API REST Server缓存缓慢的资源初始化?

时间:2019-02-28 07:59:29

标签: c# rest caching asp.net-web-api

上下文

我正在尝试实现“包装”现有C程序的REST API Web服务。

问题/目标

鉴于当我告诉C程序打开特定文件夹时C的初始化时间很慢,并且RAM使用率很高(假设无法改进),我正在考虑缓存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
});

这样,每当一个特定的文件夹已经被GET了,它就会已经有一个句柄供我使用。

为什么不好

因此,如果同时缓存太多文件夹,我现在就有内存不足的风险。如何在TryRemove()中添加类似GC的后台进程并在旧的句柄上调用IHandle.Dispose(),也许是在“最近最少使用”或“最少经常使用”策略中?理想情况下,它应该仅在可用的物理内存不足时才开始触发。

我尝试在GET函数中添加以下语句,但是它似乎太过hacky,并且功能非常有限。仅当我一直希望句柄在10秒后过期时,这种方法才能正常工作;如果在10秒之内收到后续请求,它不会重新启动计时器。

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

这个问题不是什么

我不认为在这里缓存输出是解决方案。返回此GET请求的结果(只是文件夹内容的元数据)之后,可能还会有另一个GET请求,以获取更深入的数据,这需要调用Handle的方法。

我希望我的问题很清楚!

2 个答案:

答案 0 :(得分:0)

  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();
    }
  }
}
  1. 您的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;
});
  1. 您的后台任务。
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));
  });
}

我想该集合中的元素很少,因此无需字典。

答案 1 :(得分:0)

我没有第一次满足您的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 _);
  }
}
  1. 获取方法。
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;
}
  1. 背景任务。
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循环。