简体   繁体   English

Net Core MemoryCache 在 Azure 中无法正常工作

[英]Net Core MemoryCache not working properly in Azure

I noticed a strange behavior related to the use of MemoryCache in my API developed in Net Core 2.2我注意到一个与在 Net Core 2.2 开发的 API 中使用 MemoryCache 相关的奇怪行为

After several tests I created this simple controller to demonstrate the behavior.经过几次测试后,我创建了这个简单的控制器来演示该行为。

The API is published to Azure. API 已发布到 Azure。 In my local environment all works fine.在我当地的环境中一切正常。

The controller has three methods:控制器有三种方法:

  • Create: write four items in memory cache创建:在内存缓存中写入四项
  • List: lists the elements present in memory cache List:列出内存缓存中存在的元素
  • Clear: delete the items from memory cache清除:从内存缓存中删除项目
    [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) Execute List action. 1) 执行列表动作。 Expected result: no element - Result obtained: no element - OK预期结果:无元素 - 获得的结果:无元素 - 正常

2) Execute Create action. 2) 执行创建动作。

3) Execute List action. 3) 执行列表动作。 Expected result: 4 elements - Result obtained: 4 elements预期结果:4 个元素 - 获得的结果:4 个元素

4) Execute List action again. 4) 再次执行列表动作。 Expected result: 4 elements - Result obtained: 0 items - Wrong!!!预期结果:4 个元素 - 获得的结果:0 个项目 - 错误!!!

5) Execute Clear action. 5) 执行清除动作。 Expected result: list of 4 items deleted - Result obtained: list of 4 items deleted - OK预期结果:已删除 4 个项目的列表 - 获得的结果:已删除 4 个项目的列表 - 确定

6) Execute List action. 6) 执行列表动作。 Expected result: 0 items - Result obtained: 0 elements - OK预期结果:0 个项目 - 获得的结果:0 个元素 - 确定

7) Execute List action. 7) 执行列表动作。 Expected result: 0 items - Result obtained: 4 elements - Wrong!!!预期结果:0 个项目 - 获得的结果:4 个元素 - 错误!!!

Can anyone explain this strange behavior to me?谁能向我解释这种奇怪的行为?

You haven't given any information on the actual hosting setup, but it sounds like you've got multiple instances running, ie a web farm in IIS, container replicas, etc.您没有提供有关实际托管设置的任何信息,但听起来您有多个实例正在运行,即 IIS 中的网络场、容器副本等。

Memory cache is process-bound, and each running instance of your application is a different process (each process has its own pool of memory).内存缓存是进程绑定的,应用程序的每个运行实例都是不同的进程(每个进程都有自己的内存池)。 As such, it becomes a luck of the draw as to which process the request hits, and therefore, what data actually exists in memory when that request executes.因此,关于请求命中哪个进程,因此,当该请求执行时,内存中实际存在哪些数据成为抽签的运气。

If you need consistency in your cache from request to request, you should be using IDistributedCache instead, and a backing store like Redis or SQL Server.如果您需要从请求到请求的缓存一致性,您应该改用IDistributedCache ,以及像 Redis 或 SQL Server 这样的后备存储。 There's a MemoryDistributedCache implementation, but this is just a sensible default for development;有一个MemoryDistributedCache实现,但这只是开发的一个明智的默认设置; it's not actually distributed and suffers the same process-bound issues as MemoryCache does.它实际上并不是分布式的,并且会遇到与MemoryCache相同的进程绑定问题。

Otherwise, you'd need to ensure that there is only ever one instance of your app running, which may not even be possible depending on how you're deploying and offers no potential to scale, or you must accept that cache will sometimes exist or not depending on what's happened in that process.否则,您需要确保只有一个应用程序实例在运行,这甚至可能无法实现,具体取决于您的部署方式并且没有扩展潜力,或者您必须接受缓存有时会存在或不取决于在那个过程中发生了什么。 There's times when this is okay.有时候这没关系。 For example, if you're just caching the results of some API call, you can use GetOrCreateAsync and the worst case scenario is that sometimes the API will be queried again, but it still overall decreases the load on the API/keeps you under any rate limits.例如,如果你只是缓存一些 API 调用的结果,你可以使用GetOrCreateAsync ,最坏的情况是有时 API 会被再次查询,但它仍然总体上减少了 API 的负载/让你处于任何速率限制。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM