首页 > 解决方案 > 多个请求访问数据库 - 等待缓存可用

问题描述

我们正在使用一个简单的Memory Cache方法来缓存密集查询的响应,否则每秒会被多次命中。缓存非常快,偏移量设置为 2 秒。

public StateService
{
    private readonly ObjectCache _cache = MemoryCache.Default;
    private const string _queueStatesCacheKey = "_states";

    public IList<States> GetStates()
    {
        var states = _cache.Get(_statesCacheKey);
                if (states== null)
                {
                    states = getStatesFromDatabase();
                    _cache.Set(_statesCacheKey, states, DateTimeOffset.Now.AddSeconds(Settings.AppSettings.QueueStateCacheExpiration));
                }
       return states as List<States>;
    }
}

方法:getStatesFromDatabase()简单地运行 aSQL-query来运行一个存储过程并获取这些值。

这都是通过 an 获取的API,目前我们遇到的情况是缓存被清除并且getStatesFromDatabase()同时被 4-5 个线程命中。

标签: c#sqlasp.netcachingasp.net-web-api

解决方案


用于SemaphoreSlim锁定其他线程,直到缓存准备好:

public StateService
{
    private readonly ObjectCache _cache = MemoryCache.Default;
    private const string _queueStatesCacheKey = "_states";
    private static SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);

    public IList<States> GetStates()
    {
        var states = _cache.Get(_statesCacheKey);
        if (states== null)
        {
           try
           {
              semaphore.Wait();
              // we check again from cache, that could have been populated from other thread
              states = _cache.Get(_statesCacheKey);
              if(states != null) return states as IList<States>;
              states = getStatesFromDatabase();
              _cache.Set(_statesCacheKey, states, DateTimeOffset.Now.AddSeconds(Settings.AppSettings.QueueStateCacheExpiration));
           }
           finally
           {
              semaphore.Release();
           }
        }
        return states as List<States>;
    }
}

您可以配置无超时(如示例中所示)或某种超时,可能基于查询的运行时间统计信息。

请注意,这仅适用于单个服务器和单个进程设置。如果您的应用程序打算在多个服务器或多个进程上运行,则此解决方案将允许每个服务器/进程进行一个并发查询。

为避免这种情况,您需要检查一些用于从外部管理锁的包(分布式锁)。例如, Nuget 包DistributedLock就是一个可以完成这项工作的包。


推荐阅读