Net Core MemoryCache 在 Azure 中无法正常工作
Net Core MemoryCache not working properly in Azure
我注意到我的 API 在 Net Core 2.2
中开发的与使用 MemoryCache 相关的奇怪行为
经过几次测试后,我创建了这个简单的控制器来演示该行为。
API 已发布到 Azure。在我的本地环境中一切正常。
控制器有3个方法:
- 创建:在内存缓存中写入四项
- 列表:列出内存缓存中存在的元素
- 清除:从内存缓存中删除项目
[Route("api/[controller]")]
public class CacheController : ControllerBase
{
private readonly IMemoryCache memoryCache;
public CacheController(IMemoryCache memoryCache)
{
this.memoryCache = memoryCache;
}
[HttpGet("Create")]
public IActionResult CreateEntries()
{
memoryCache.Set("Key1", "Value1", TimeSpan.FromHours(1));
memoryCache.Set("Key2", "Value2", TimeSpan.FromHours(1));
memoryCache.Set("Key3", "Value3", TimeSpan.FromHours(1));
memoryCache.Set("Key4", "Value4", TimeSpan.FromHours(1));
return Ok();
}
[HttpGet()]
public IActionResult List()
{
// Get the empty definition for the EntriesCollection
var cacheEntriesCollectionDefinition = typeof(MemoryCache).GetProperty("EntriesCollection", BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Public);
// Populate the definition with your IMemoryCache instance.
// It needs to be cast as a dynamic, otherwise you can't
// loop through it due to it being a collection of objects.
var cacheEntriesCollection = cacheEntriesCollectionDefinition.GetValue(memoryCache) as dynamic;
// Define a new list we'll be adding the cache entries too
var cacheCollectionValues = new List<KeyValuePair<string, string>>();
foreach (var cacheItem in cacheEntriesCollection)
{
// Get the "Value" from the key/value pair which contains the cache entry
Microsoft.Extensions.Caching.Memory.ICacheEntry cacheItemValue = cacheItem.GetType().GetProperty("Value").GetValue(cacheItem, null);
if (!cacheItemValue.Key.ToString().StartsWith("Microsoft.EntityFrameworkCore"))
// Add the cache entry to the list
cacheCollectionValues.Add(new KeyValuePair<string, string>(cacheItemValue.Key.ToString(), cacheItemValue.Value.ToString()));
}
return Ok(cacheCollectionValues);
}
[HttpGet("Clear")]
public IActionResult Clear()
{
var removedKeys = new List<string>();
// Get the empty definition for the EntriesCollection
var cacheEntriesCollectionDefinition = typeof(MemoryCache).GetProperty("EntriesCollection", BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Public);
// Populate the definition with your IMemoryCache instance.
// It needs to be cast as a dynamic, otherwise you can't
// loop through it due to it being a collection of objects.
var cacheEntriesCollection = cacheEntriesCollectionDefinition.GetValue(memoryCache) as dynamic;
// Define a new list we'll be adding the cache entries too
var cacheCollectionValues = new List<KeyValuePair<string, string>>();
foreach (var cacheItem in cacheEntriesCollection)
{
// Get the "Value" from the key/value pair which contains the cache entry
Microsoft.Extensions.Caching.Memory.ICacheEntry cacheItemValue = cacheItem.GetType().GetProperty("Value").GetValue(cacheItem, null);
if (!cacheItemValue.Key.ToString().StartsWith("Microsoft.EntityFrameworkCore"))
{
// Remove the cache entry from the list
memoryCache.Remove(cacheItemValue.Key.ToString());
removedKeys.Add(cacheItemValue.Key.ToString());
}
}
return Ok(removedKeys);
}
}
1) 执行列表操作。预期结果:无元素 - 获得的结果:无元素 - OK
2) 执行创建操作。
3) 执行列表操作。预期结果:4 个元素 - 获得的结果:4 个元素
4) 再次执行列表操作。预期结果:4 个元素 - 获得的结果:0 个项目 - 错误!!!
5) 执行清除操作。预期结果:删除 4 个项目的列表 - 获得的结果:删除 4 个项目的列表 - OK
6) 执行列表操作。预期结果:0 项 - 获得的结果:0 个元素 - OK
7) 执行列表操作。预期结果:0 项 - 获得的结果:4 个元素 - 错误!!!
任何人都可以向我解释这种奇怪的行为吗?
您没有提供任何关于实际托管设置的信息,但听起来您有多个实例运行,即 IIS 中的网络场、容器副本等
内存缓存是进程绑定的,应用程序的每个 运行 实例都是一个不同的进程(每个进程都有自己的内存池)。因此,关于请求命中哪个进程,以及因此执行该请求时内存中实际存在哪些数据,就成了运气。
如果您需要请求之间缓存的一致性,您应该改用 IDistributedCache
,以及像 Redis 或 SQL 服务器这样的后备存储。有一个 MemoryDistributedCache
实现,但这只是一个合理的开发默认值;它实际上并没有分发,并且遇到与 MemoryCache
相同的过程限制问题。
否则,您需要确保只有一个应用程序实例 运行,这甚至可能无法实现,具体取决于您的部署方式并且没有提供扩展的潜力,或者您必须接受缓存有时会存在或不存在,具体取决于该过程中发生的事情。有时候这没关系。例如,如果你只是缓存一些 API 调用的结果,你可以使用 GetOrCreateAsync
最坏的情况是有时 API 会被再次查询,但它仍然在任何速率限制下,总体上减少了 API/keeps 你的负载。
我注意到我的 API 在 Net Core 2.2
中开发的与使用 MemoryCache 相关的奇怪行为经过几次测试后,我创建了这个简单的控制器来演示该行为。
API 已发布到 Azure。在我的本地环境中一切正常。
控制器有3个方法:
- 创建:在内存缓存中写入四项
- 列表:列出内存缓存中存在的元素
- 清除:从内存缓存中删除项目
[Route("api/[controller]")]
public class CacheController : ControllerBase
{
private readonly IMemoryCache memoryCache;
public CacheController(IMemoryCache memoryCache)
{
this.memoryCache = memoryCache;
}
[HttpGet("Create")]
public IActionResult CreateEntries()
{
memoryCache.Set("Key1", "Value1", TimeSpan.FromHours(1));
memoryCache.Set("Key2", "Value2", TimeSpan.FromHours(1));
memoryCache.Set("Key3", "Value3", TimeSpan.FromHours(1));
memoryCache.Set("Key4", "Value4", TimeSpan.FromHours(1));
return Ok();
}
[HttpGet()]
public IActionResult List()
{
// Get the empty definition for the EntriesCollection
var cacheEntriesCollectionDefinition = typeof(MemoryCache).GetProperty("EntriesCollection", BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Public);
// Populate the definition with your IMemoryCache instance.
// It needs to be cast as a dynamic, otherwise you can't
// loop through it due to it being a collection of objects.
var cacheEntriesCollection = cacheEntriesCollectionDefinition.GetValue(memoryCache) as dynamic;
// Define a new list we'll be adding the cache entries too
var cacheCollectionValues = new List<KeyValuePair<string, string>>();
foreach (var cacheItem in cacheEntriesCollection)
{
// Get the "Value" from the key/value pair which contains the cache entry
Microsoft.Extensions.Caching.Memory.ICacheEntry cacheItemValue = cacheItem.GetType().GetProperty("Value").GetValue(cacheItem, null);
if (!cacheItemValue.Key.ToString().StartsWith("Microsoft.EntityFrameworkCore"))
// Add the cache entry to the list
cacheCollectionValues.Add(new KeyValuePair<string, string>(cacheItemValue.Key.ToString(), cacheItemValue.Value.ToString()));
}
return Ok(cacheCollectionValues);
}
[HttpGet("Clear")]
public IActionResult Clear()
{
var removedKeys = new List<string>();
// Get the empty definition for the EntriesCollection
var cacheEntriesCollectionDefinition = typeof(MemoryCache).GetProperty("EntriesCollection", BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Public);
// Populate the definition with your IMemoryCache instance.
// It needs to be cast as a dynamic, otherwise you can't
// loop through it due to it being a collection of objects.
var cacheEntriesCollection = cacheEntriesCollectionDefinition.GetValue(memoryCache) as dynamic;
// Define a new list we'll be adding the cache entries too
var cacheCollectionValues = new List<KeyValuePair<string, string>>();
foreach (var cacheItem in cacheEntriesCollection)
{
// Get the "Value" from the key/value pair which contains the cache entry
Microsoft.Extensions.Caching.Memory.ICacheEntry cacheItemValue = cacheItem.GetType().GetProperty("Value").GetValue(cacheItem, null);
if (!cacheItemValue.Key.ToString().StartsWith("Microsoft.EntityFrameworkCore"))
{
// Remove the cache entry from the list
memoryCache.Remove(cacheItemValue.Key.ToString());
removedKeys.Add(cacheItemValue.Key.ToString());
}
}
return Ok(removedKeys);
}
}
1) 执行列表操作。预期结果:无元素 - 获得的结果:无元素 - OK
2) 执行创建操作。
3) 执行列表操作。预期结果:4 个元素 - 获得的结果:4 个元素
4) 再次执行列表操作。预期结果:4 个元素 - 获得的结果:0 个项目 - 错误!!!
5) 执行清除操作。预期结果:删除 4 个项目的列表 - 获得的结果:删除 4 个项目的列表 - OK
6) 执行列表操作。预期结果:0 项 - 获得的结果:0 个元素 - OK
7) 执行列表操作。预期结果:0 项 - 获得的结果:4 个元素 - 错误!!!
任何人都可以向我解释这种奇怪的行为吗?
您没有提供任何关于实际托管设置的信息,但听起来您有多个实例运行,即 IIS 中的网络场、容器副本等
内存缓存是进程绑定的,应用程序的每个 运行 实例都是一个不同的进程(每个进程都有自己的内存池)。因此,关于请求命中哪个进程,以及因此执行该请求时内存中实际存在哪些数据,就成了运气。
如果您需要请求之间缓存的一致性,您应该改用 IDistributedCache
,以及像 Redis 或 SQL 服务器这样的后备存储。有一个 MemoryDistributedCache
实现,但这只是一个合理的开发默认值;它实际上并没有分发,并且遇到与 MemoryCache
相同的过程限制问题。
否则,您需要确保只有一个应用程序实例 运行,这甚至可能无法实现,具体取决于您的部署方式并且没有提供扩展的潜力,或者您必须接受缓存有时会存在或不存在,具体取决于该过程中发生的事情。有时候这没关系。例如,如果你只是缓存一些 API 调用的结果,你可以使用 GetOrCreateAsync
最坏的情况是有时 API 会被再次查询,但它仍然在任何速率限制下,总体上减少了 API/keeps 你的负载。