我可以通过执行以下命令来检索Redis数据库中的所有键:

RedisResult allRedisKeys = redisDatabase.Execute("KEYS", new List<object>
{
   "*"
})


但是我不能使用变量allRedisKeys,因为它的_value字段是私有的。如何从RedisResult中提取值?在我的情况下,_value拥有RedisResult [],而allRedisKeys具有RedisType的RedisType.MultiBulk。

最佳答案

首先,每个the documentation都不要在生产环境中使用KEYS命令。但是,您可以在每个服务器上使用.Keys方法,但这需要分别查询每个服务器(它是O(N),所以也不是最好的)。

对于更快(但更复杂)的解决方案,请查看Marc Gravell撰写的this answer。基本上,您将所有密钥存储在SET中,因此您具有O(1)响应时间。

这是我对他的建议的实施:

/// <summary>
/// Class that keeps track of cache keys and the expiration of said keys.
/// Uses a LIST to store the keys because it's faster than using SCAN on the entire Redis database.
/// Expirations are stored as ticks.
/// </summary>
public static class KeyAndExpirationTracker
{
    private const string ALL_KEYS = "ALL_KEYS";
    private const string EXPIRATION_SUFFIX = "|EXPIRATION";

    public static void StoreKeyAndExpiration(IDatabase db, string key, TimeSpan? timeToLive, ITransaction transaction)
    {
        string expiryKey = $"{key}{EXPIRATION_SUFFIX}";
        // although Redis will automatically replace an existing key,
        // we still have to remove this in case this we went from having an expiration to not expiring
        db.KeyDelete(expiryKey);

        // add the item key to the list of keys
        transaction.SetAddAsync(key);
        // add the separate cache item for expiration (if necessary)
        if (timeToLive.HasValue)
        {
            DateTime expiry;
            try
            {
                expiry = DateTime.UtcNow.Add(timeToLive.Value);
                transaction.StringSetAsync(expiryKey, expiry.Ticks.ToString());
            }
            catch (ArgumentOutOfRangeException)
            {
                // no expiration then
            }
        }
    }

    public static void StoreKeyAndExpiration(IDatabase db, string key, DateTime? expiration, ITransaction transaction)
    {
        string expiryKey = $"{key}{EXPIRATION_SUFFIX}";
        // although Redis will automatically replace an existing key, we still have to remove this in case this we went from having an expiration to not expiring
        db.KeyDelete(expiryKey);

        // add the item key to the list of keys for this client type
        transaction.SetAddAsync(cacheKey, key);
        // add the separate cache item for expiration (if necessary)
        if (expiration.HasValue)
        {
            transaction.StringSetAsync(expiryKey, expiration.Value.Ticks.ToString());
        }
    }

    public static IEnumerable<string> GetKeys(IDatabase db)
    {
        RedisValue[] keys = db.SetMembers(ALL_KEYS));
        if (keys.Length == 0)
            return new string[0];

        List<string> expiredKeys = new List<string>(keys.Count());
        List<string> ret = new List<string>(keys.Count());

        // get expiration values using SORT with GET: https://redis.io/commands/sort
        RedisValue[] results = db.Sort(
            ALL_KEYS,
            sortType: SortType.Alphabetic,
            by: "notarealkey", // a (hopefully) non-existent key which causes the list to actually not be sorted
            get: new RedisValue[] { $"*{EXPIRATION_SUFFIX}", "#" } // # = "get the list item itself" which means the key in our case
        ));

        // SORT results will be in the same order as the GET parameters:
        // result[0] = expiration (= null if no matching expiration item found)
        // result[1] = key
        // (repeat)
        for (int i = 0; i < results.Length; i += 2)
        {
            string key = results[i + 1].Replace(EXPIRATION_SUFFIX, string.Empty);

            RedisValue expiryRedis = results[i];
            long ticks;
            if (long.TryParse(expiryRedis, out ticks))
            {
                DateTime expiry = new DateTime(ticks, DateTimeKind.Utc);
                if (expiry <= DateTime.UtcNow)
                    expiredKeys.Add(key); // expired: add to list for removal
                else
                    ret.Add(key);
            }
            else
                ret.Add(key);
        }

        // remove any expired keys from list
        if (expiredKeys.Count > 0)
        {
            IBatch batch = db.CreateBatch();
            batch.SetRemoveAsync(cacheKey, expiredKeys.ConvertAll(x => (RedisValue)x).ToArray());
            foreach (string expiredKey in expiredKeys)
            {
                batch.KeyDeleteAsync(expiredKey);
            }
            batch.Execute();
        }

        return ret;
   }

    public static void RemoveKeyAndExpiration(IDatabase db, string key, ITransaction transaction)
    {
        // remove the key from the list and its corresponding expiration value
        string expiryKey = $"{key}{EXPIRATION_SUFFIX}";
        transaction.SetRemoveAsync(ALL_KEYS, key);
        transaction.KeyDeleteAsync(expiryKey);
    }

}


注意:StoreKeyAndExpirationRemoveKeyAndExpiration方法都采用ITransaction,因为我将这些操作与添加或删除要缓存的项目结合在一起进行,因此我想将它们全部包装到一个事务中。如果调用方法想要向其添加更多操作,则该事务不会在此处执行。

关于c# - 如果结果是MultiBulk,如何从RedisResult中检索值,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/58864724/

10-13 06:22