简体   繁体   English

Redis 缓存缓存模式代码问题

[英]Redis Cache cashe-aside pattern code issue

I am attempting to create a BaseService that uses Redis cache to build out the following pattern:我正在尝试创建一个使用 Redis 缓存来构建以下模式的 BaseService:

  1. Get from cache从缓存中获取
  2. If in cache, return result如果在缓存中,则返回结果
  3. If result is null, call Func to get result from source (database)如果结果是 null,调用 Func 从源(数据库)获取结果
  4. Place in cache放入缓存
  5. Return result from Func Func 的返回结果

I have everything working, but for some reason the service method that is calling to get the result is needing an "await await" before it will compile.我一切正常,但由于某种原因,调用以获取结果的服务方法在编译之前需要“等待等待”。 I can't seem to figure out why my ResultFromCache method which is meant to imitate what Ok() does in WebAPI is doubly wrapped.我似乎无法弄清楚为什么我的 ResultFromCache 方法被双重包装,该方法旨在模仿 Ok() 在 WebAPI 中的作用。 Can you please help me find where I am not returning the correct result so my users of this pattern won't have to use two awaits to get their results:)你能帮我找到我没有返回正确结果的地方吗,这样我的这种模式的用户就不必使用两次等待来获得他们的结果:)

Here's a slimmed down version of my code that requires the await await in the GetMessage method.这是我的代码的精简版本,需要在 GetMessage 方法中等待 await。

using StackExchange.Redis;
using System.Text.Json;

namespace TestCache
{
    public class Service: BaseService
    {
        //Injected DbContextx
        private Context context { get; }

        //service method
        public async Task<Message> GetMessage(int accountId)
        {
            return await await ResultFromCache(accountId, FetchMessage, "Message");
        }

        //get from database method
        private async Task<Message> FetchMessage(int parentId)
        {
            //Example of using EF to retrieve one record from Message table
            return await context.Message;
        }
    }

    public class BaseService
    {
        private const int Hour = 3600;

        private ConnectionMultiplexer connection;
        private IDatabaseAsync database;

        public async Task<T1> ResultFromCache<T1, T2>(T2 param1, Func<T2, T1> fromSource, string cacheKey, int cacheDuration = Hour)
        {
            //get from cache
            var result = await CacheGet<T1>(cacheKey);

            if (result != null)
                return result;

            //get from db
            result = fromSource(param1);

            //TODO: add to cache
            return result;
        }

        public async Task<T> CacheGet<T>(string key)
        {
            var value = await database.StringGetAsync(key);

            if (value.IsNull)
            {
                return default;
            }

            return JsonSerializer.Deserialize<T>(value);
        }
    }
}

As I mentioned in my comment, your fromSource needs to be defined as Func<T2, Task<T1> , since you need to await the func.正如我在评论中提到的,您的fromSource需要定义为Func<T2, Task<T1> ,因为您需要等待 func。

However, you also have a subtle bug in regards to checking for null in ResultFromCache .但是,关于在ResultFromCache中检查 null,您还有一个微妙的错误。 As it is currently written, value types will incorrectly return if the default is returned in CacheGet<T> .正如目前所写,如果在CacheGet<T>中返回默认值,值类型将错误地返回。 To solve for this, you need to use `EqualityComparer.Default.Equals to check for the default value instead of simply null.要解决这个问题,您需要使用`EqualityComparer.Default.Equals 来检查默认值,而不是简单地使用 null。

public class BaseService
{
    private const int Hour = 3600;

    private ConnectionMultiplexer connection;
    private IDatabaseAsync database;

    public async Task<T1> ResultFromCache<T1, T2>(T2 param1, Func<T2, Task<T1>> fromSource, 
        string cacheKey, int cacheDuration = Hour)
    {
        //get from cache
        var result = await CacheGet<T1>(cacheKey);

        if (!EqualityComparer<T1>.Default.Equals(result, default(T1)))
            return result;

        //get from db
        result = await fromSource(param1);

        //TODO: add to cache
        return result;
    }

    public async Task<T> CacheGet<T>(string key)
    {
        var value = await database.StringGetAsync(key);

        if (value.IsNull)
        {
            return default;
        }

        return JsonSerializer.Deserialize<T>(value);
    }
}

Finally, if multiple requests experience Redis cache misses at the same time, they will all call your loader function. This can lead to significant strain on your loading source if the queries are expensive.最后,如果多个请求同时遇到 Redis 缓存未命中,它们都会调用您的加载器 function。如果查询开销很大,这可能会给您的加载源带来很大的压力。 A common solution is double-checked locking which can be implemented as a ConcurrentDictionary of SemaphoreSlim instances, or a much better implementation in Stephen Cleary's AsyncDuplicateLock : Asynchronous locking based on a key .一个常见的解决方案是双重检查锁定,它可以作为SemaphoreSlim实例的 ConcurrentDictionary 来实现,或者在 Stephen Cleary 的AsyncDuplicateLock中有更好的实现: 基于键的异步锁定

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

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