简体   繁体   中英

Redis Cache cashe-aside pattern code issue

I am attempting to create a BaseService that uses Redis cache to build out the following pattern:

  1. Get from cache
  2. If in cache, return result
  3. If result is null, call Func to get result from source (database)
  4. Place in cache
  5. Return result from 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. 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.

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.

However, you also have a subtle bug in regards to checking for null in ResultFromCache . As it is currently written, value types will incorrectly return if the default is returned in CacheGet<T> . To solve for this, you need to use `EqualityComparer.Default.Equals to check for the default value instead of simply 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. 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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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