【问题标题】:Caching Pattern for Fetching Collections用于获取集合的缓存模式
【发布时间】:2017-05-07 22:49:15
【问题描述】:

是否有可用于获取多个项目的缓存策略/模式,其中只有一些可能被缓存?在这种情况下使用缓存是否有意义?

更多详情

缓存将返回单个(或已知数量的)结果缓存的简单;如果它在缓存中我们返回它,如果没有我们获取它,将它添加到缓存中,然后返回它:

//using System.Runtime.Caching;
ObjectCache cache = MemoryCache.Default;
TimeSpan cacheLifetime = new TimeSpan(0, 20, 0);
SomeObjectFinder source = new SomeObjectFinder();
public SomeObject GetById(long id)
{
    return GetFromCacheById(id) ?? GetFromSourceById(id);
}
protected SomeObject GetFromCacheById(long id)
{
    return (SomeObject)cache.Get(id.ToString(),null);
}
protected SomeObject GetFromSourceById (long id)
{
    SomeObject result = source.GetById(id);
    return result == null ? null : (SomeObject)cache.AddOrGetExisting(id.ToString(), result, DateTimeOffset.UtcNow.Add(cacheLifetime), null);
}

但是,如果我们不知道预期会有多少结果,如果我们从缓存中获取结果,我们就不会知道我们已经获取了所有内容;只有被缓存的东西。因此,按照上述模式,如果没有或所有结果都被缓存,我们会很好;但如果其中一半是,我们将得到部分结果集。

我在想下面的可能是有道理的;但是我以前没有玩过async/await,而且我读过的大部分内容都暗示从同步代码调用异步代码通常被认为是不好的;所以这个解决方案可能不是一个好主意。

public IEnumerable<SomeObject> GetByPartialName(string startOfName)
{
    //kick off call to the DB (or whatever) to get the full result set 
    var getResultsTask = Task.Run<IList<SomeObject>>(async() => await GetFromSourceByPartialNameAsync(startOfName));
    //whilst we leave that to run in the background, start getting and returning the results from the cache
    var cacheResults = GetFromCacheByPartialName(startOfName);
    foreach (var result in cacheResults)
    {
        yield return result;
    }
    //once all cached values are returned, wait for the async task to complete, remove the results we'd already returned from cache, then add the remaining results to cache and return those also
    var results = getResultsTask.GetAwaiter().GetResult();
    foreach (var result in results.Except(cacheResults))
    {
        yield return CacheAddOrGetExistingByName(result.Name, result);
    }
}
protected async Task<IList<SomeObject>> GetFromSourceByPartialNameAsync(string startOfName)
{
    return source.GetByPartialName(startOfName);
}

我的假设是,答案将是“在这种情况下,要么预先缓存所有内容,要么不使用缓存”……但希望有更好的选择。

【问题讨论】:

  • 如果您每次都必须去(相对较慢的)源进行检查,那么也许该集合类型不适合缓存?我认为 async 在这里不会对您有太大帮助-异步调用不比同步调用快-实际上稍微慢一些;你只是在等待它完成的时候做其他事情。因此,在返回给调用者之前,您最终仍需要等待比获取缓存项并返回它们所需的时间更长的时间。
  • 谢谢@sellotape;这符合我的假设。上面的好处是,当我们返回IEnumerable 时,在获取结果后要进行的任何处理都可以开始循环遍历第一个(缓存的)值,而其他值仍在获取中;但我同意这样做的任何好处都可能会因极端情况之外的额外复杂性而丢失。
  • @JohnLBevan:请注意,IEnumerable 返回值可能会导致对该方法的多次调用,这有时会导致不良结果(例如,集合值不断变化)。如果您不包含这些行为,则最好返回IList&lt;T&gt;。关于缓存,我发现IDictionary&lt;T1, T2&gt; 具有高性能,只要它都在内存中。如果需要持久化,SQLite 非常快

标签: c# performance caching design-patterns async-await


【解决方案1】:

您可以使用装饰器和策略模式,这里是Steve Smith post regarding building a cached repository 的链接,对于单个项目也可以使用,因此您可以使用redis 使用装饰器,如果找不到,它会进入数据库然后保存到数据库,之后查询数据会非常快。

这里是一些示例代码:

public class CachedAlbumRepository : IAlbumRepository
{
    private readonly IAlbumRepository _albumRepository;

public CachedAlbumRepository(IAlbumRepository albumRepository)
{
    _albumRepository = albumRepository;
}

private static readonly object CacheLockObject = new object();

public IEnumerable<Album> GetTopSellingAlbums(int count)
{
    Debug.Print("CachedAlbumRepository:GetTopSellingAlbums");
    string cacheKey = "TopSellingAlbums-" + count;
    var result = HttpRuntime.Cache[cacheKey] as List<Album>;
    if (result == null)
    {
        lock (CacheLockObject)
        {
            result = HttpRuntime.Cache[cacheKey] as List<Album>;
    if (result == null)
            {
                result = _albumRepository.GetTopSellingAlbums(count).ToList();
                HttpRuntime.Cache.Insert(cacheKey, result, null, 
                    DateTime.Now.AddSeconds(60), TimeSpan.Zero);
            }
        }
    }
    return result;
}
}

【讨论】:

    猜你喜欢
    • 2015-10-14
    • 2019-03-29
    • 1970-01-01
    • 2019-05-26
    • 2017-08-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-12
    相关资源
    最近更新 更多