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 你的负载。