当我达到大小限制时,为什么我需要在大小有限的 MemoryCache 上调用两次 Set?
Why do I need to call twice the Set on my size limited MemoryCache when I hit the size limit?
我们即将使用 ASP.NET Core 的内置内存缓存解决方案来缓存外部系统响应。 (稍后我们可能会从内存中转移到 IDistributedCache
。)
我们要使用 Mircosoft.Extensions.Caching.Memory's IMemoryCache
as the MSDN suggests.
我们需要限制缓存的大小,因为默认情况下它是无限的。
因此,在将其集成到我们的项目之前,我创建了以下 POC 应用程序来试用一下。
我的自定义 MemoryCache 以指定大小限制
public interface IThrottledCache
{
IMemoryCache Cache { get; }
}
public class ThrottledCache: IThrottledCache
{
private readonly MemoryCache cache;
public ThrottledCache()
{
cache = new MemoryCache(new MemoryCacheOptions
{
SizeLimit = 2
});
}
public IMemoryCache Cache => cache;
}
将此实现注册为单例
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSingleton<IThrottledCache>(new ThrottledCache());
}
我创建了一个非常简单的控制器来使用这个缓存。
与 MemoryCache 一起玩的沙盒控制器
[Route("api/[controller]")]
[ApiController]
public class MemoryController : ControllerBase
{
private readonly IMemoryCache cache;
public MemoryController(IThrottledCache cacheSource)
{
this.cache = cacheSource.Cache;
}
[HttpGet("{id}")]
public IActionResult Get(string id)
{
if (cache.TryGetValue(id, out var cachedEntry))
{
return Ok(cachedEntry);
}
else
{
var options = new MemoryCacheEntryOptions { Size = 1, SlidingExpiration = TimeSpan.FromMinutes(1) };
cache.Set(id, $"{id} - cached", options);
return Ok(id);
}
}
}
如您所见,我的 /api/memory/{id}
端点可以在两种模式下工作:
- 从缓存中检索数据
- 将数据存入缓存
我观察到以下奇怪的行为:
- 获取
/api/memory/first
1.1) Returns first
1.2)缓存条目:first
- 获取
/api/memory/first
2.1) Returns first - cached
2.2)缓存条目:first
- 获取
/api/memory/second
3.1) Returns second
3.2)缓存条目:first
、second
- 获取
/api/memory/second
4.1) Returns second - cached
4.2)缓存条目:first
、second
- 获取
/api/memory/third
5.1) Returns third
5.2)缓存条目:first
、second
- 获取
/api/memory/third
6.1) Returns third
6.2)缓存条目:second
、third
- 获取
/api/memory/third
7.1) Returns third - cached
7.2)缓存条目:second
、third
正如您在第 5 个端点调用中看到的那样,我达到了极限。所以我的期望是:
- 缓存逐出策略删除
first
最旧的条目
- 缓存将
third
存储为最新的
但是这种期望的行为只发生在第 6 次调用时。
所以,我的问题是为什么我必须调用两次 Set
以便在达到大小限制时将新数据放入 MemoryCache?
编辑:同时添加时间相关信息
在测试期间,整个请求流/链花费了大约 15 秒甚至更少。
即使我将 SlidingExpiration
更改为 1 小时,行为仍然完全相同。
我downloaded, built and debugged the unit tests in Microsoft.Extensions.Caching.Memory;似乎没有真正涵盖这种情况的测试。
原因是:一旦您尝试添加一个会使缓存超出容量的项目,MemoryCache triggers a compaction in the background. This will evict the oldest (MRU) cache entries up until a certain difference。在这种情况下,它会尝试删除总大小为 1 的缓存项,在您的情况下是“第一个”,因为它是最后访问的。
但是,由于此紧凑循环在后台运行,并且 SetEntry()
方法中的代码是 already on the code path for a full cache,它会继续而不将项目添加到缓存。
下次尝试时,它会成功。
转载:
class Program
{
private static MemoryCache _cache;
private static MemoryCacheEntryOptions _options;
static void Main(string[] args)
{
_cache = new MemoryCache(new MemoryCacheOptions
{
SizeLimit = 2
});
_options = new MemoryCacheEntryOptions
{
Size = 1
};
_options.PostEvictionCallbacks.Add(new PostEvictionCallbackRegistration
{
EvictionCallback = (key, value, reason, state) =>
{
if (reason == EvictionReason.Capacity)
{
Console.WriteLine($"Evicting '{key}' for capacity");
}
}
});
Console.WriteLine(TestCache("first"));
Console.WriteLine(TestCache("second"));
Console.WriteLine(TestCache("third")); // starts compaction
Thread.Sleep(1000);
Console.WriteLine(TestCache("third"));
Console.WriteLine(TestCache("third")); // now from cache
}
private static object TestCache(string id)
{
if (_cache.TryGetValue(id, out var cachedEntry))
{
return cachedEntry;
}
_cache.Set(id, $"{id} - cached", _options);
return id;
}
}
我们即将使用 ASP.NET Core 的内置内存缓存解决方案来缓存外部系统响应。 (稍后我们可能会从内存中转移到 IDistributedCache
。)
我们要使用 Mircosoft.Extensions.Caching.Memory's IMemoryCache
as the MSDN suggests.
我们需要限制缓存的大小,因为默认情况下它是无限的。
因此,在将其集成到我们的项目之前,我创建了以下 POC 应用程序来试用一下。
我的自定义 MemoryCache 以指定大小限制
public interface IThrottledCache
{
IMemoryCache Cache { get; }
}
public class ThrottledCache: IThrottledCache
{
private readonly MemoryCache cache;
public ThrottledCache()
{
cache = new MemoryCache(new MemoryCacheOptions
{
SizeLimit = 2
});
}
public IMemoryCache Cache => cache;
}
将此实现注册为单例
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSingleton<IThrottledCache>(new ThrottledCache());
}
我创建了一个非常简单的控制器来使用这个缓存。
与 MemoryCache 一起玩的沙盒控制器
[Route("api/[controller]")]
[ApiController]
public class MemoryController : ControllerBase
{
private readonly IMemoryCache cache;
public MemoryController(IThrottledCache cacheSource)
{
this.cache = cacheSource.Cache;
}
[HttpGet("{id}")]
public IActionResult Get(string id)
{
if (cache.TryGetValue(id, out var cachedEntry))
{
return Ok(cachedEntry);
}
else
{
var options = new MemoryCacheEntryOptions { Size = 1, SlidingExpiration = TimeSpan.FromMinutes(1) };
cache.Set(id, $"{id} - cached", options);
return Ok(id);
}
}
}
如您所见,我的 /api/memory/{id}
端点可以在两种模式下工作:
- 从缓存中检索数据
- 将数据存入缓存
我观察到以下奇怪的行为:
- 获取
/api/memory/first
1.1) Returnsfirst
1.2)缓存条目:first
- 获取
/api/memory/first
2.1) Returnsfirst - cached
2.2)缓存条目:first
- 获取
/api/memory/second
3.1) Returnssecond
3.2)缓存条目:first
、second
- 获取
/api/memory/second
4.1) Returnssecond - cached
4.2)缓存条目:first
、second
- 获取
/api/memory/third
5.1) Returnsthird
5.2)缓存条目:first
、second
- 获取
/api/memory/third
6.1) Returnsthird
6.2)缓存条目:second
、third
- 获取
/api/memory/third
7.1) Returnsthird - cached
7.2)缓存条目:second
、third
正如您在第 5 个端点调用中看到的那样,我达到了极限。所以我的期望是:
- 缓存逐出策略删除
first
最旧的条目 - 缓存将
third
存储为最新的
但是这种期望的行为只发生在第 6 次调用时。
所以,我的问题是为什么我必须调用两次 Set
以便在达到大小限制时将新数据放入 MemoryCache?
编辑:同时添加时间相关信息
在测试期间,整个请求流/链花费了大约 15 秒甚至更少。
即使我将 SlidingExpiration
更改为 1 小时,行为仍然完全相同。
我downloaded, built and debugged the unit tests in Microsoft.Extensions.Caching.Memory;似乎没有真正涵盖这种情况的测试。
原因是:一旦您尝试添加一个会使缓存超出容量的项目,MemoryCache triggers a compaction in the background. This will evict the oldest (MRU) cache entries up until a certain difference。在这种情况下,它会尝试删除总大小为 1 的缓存项,在您的情况下是“第一个”,因为它是最后访问的。
但是,由于此紧凑循环在后台运行,并且 SetEntry()
方法中的代码是 already on the code path for a full cache,它会继续而不将项目添加到缓存。
下次尝试时,它会成功。
转载:
class Program
{
private static MemoryCache _cache;
private static MemoryCacheEntryOptions _options;
static void Main(string[] args)
{
_cache = new MemoryCache(new MemoryCacheOptions
{
SizeLimit = 2
});
_options = new MemoryCacheEntryOptions
{
Size = 1
};
_options.PostEvictionCallbacks.Add(new PostEvictionCallbackRegistration
{
EvictionCallback = (key, value, reason, state) =>
{
if (reason == EvictionReason.Capacity)
{
Console.WriteLine($"Evicting '{key}' for capacity");
}
}
});
Console.WriteLine(TestCache("first"));
Console.WriteLine(TestCache("second"));
Console.WriteLine(TestCache("third")); // starts compaction
Thread.Sleep(1000);
Console.WriteLine(TestCache("third"));
Console.WriteLine(TestCache("third")); // now from cache
}
private static object TestCache(string id)
{
if (_cache.TryGetValue(id, out var cachedEntry))
{
return cachedEntry;
}
_cache.Set(id, $"{id} - cached", _options);
return id;
}
}