Использование Redis для кэширования может сэкономить вам много времени, поскольку вам не нужно разрешать ваш объект или извлекать данные из вашей базы данных. Но если вы хотите работать с коллекциями, вы можете столкнуться с дилеммой: хранить или не хранить их в базе данных Redis.

Если вы храните всю свою коллекцию в одной строке в кеше, каждый раз, когда вы извлекаете один или несколько элементов (например, данные об одном сотруднике), вся ваша коллекция должна быть сериализована, чтобы затем фильтровать нужные вам объекты. Если у вас большая коллекция (более 10 тысяч элементов), это ухудшит производительность вашего приложения, особенно если вы храните сложную структуру данных.

Использование Redis Hash может значительно ускорить эти запросы к вашей коллекции.

Итак, для этого давайте рассмотрим способ получить максимальную отдачу от вашей базы данных Redis с помощью Hash!

Что такое хэши Redis?

Хэши Redis — это типы записей, структурированные как наборы сопоставлений полей и значений таким образом, что они не требуют много места.

Основное преимущество использования Redis Hash для ваших коллекций заключается в том, что вы можете напрямую обращаться к нужному вам реестру, просто используя имя поля для доступа к нему. Если вам не нужно визуализировать всю коллекцию, ее можно получить гораздо быстрее.

Вы можете работать со строками как для ключа, так и для значения, поэтому становится легко обрабатывать простые структуры или даже целые объекты JSON в вашем хеше.

Использование хэша Redis

Вот ключевые команды, которые вы можете использовать для управления своим хэшем в Redis:

HSET

Устанавливает значение одного или нескольких полей в вашем хэше

HSET key field value [field value ...]

HGET

Возвращает значение в определенное поле в вашем хэше

HGET key field

HMGET

Возвращает значения, связанные с указанным fields в хэше, хранящемся в key.

HMGET key field [field ...]

HGETALL

Возвращает все поля и значения хеша, хранящиеся в key.

HGETALL key

Хранение вашей коллекции в Redis Hash

Ниже вы можете увидеть пример того, как вы можете реализовать свою коллекцию в Redis Hash, используя C# и пакет StackExchange.Redis NuGet:

using Newtonsoft.Json;
using StackExchange.Redis;

namespace RedisTest.Redis
{
    public class RedisHash
    {
        protected IDatabase RedisDatabase { get; }
        protected ConnectionMultiplexer Connection { get; }

        public RedisHash(string redisServer, string password = "", int redisDatabaseId = 0)
        {
            var config = new ConfigurationOptions
            {
                EndPoints = { redisServer },
                Password = password ?? "",
                DefaultDatabase = redisDatabaseId,
                AllowAdmin = true,
                SyncTimeout = 20000,
            };
            Connection = ConnectionMultiplexer.Connect(config);
            RedisDatabase = Connection.GetDatabase(redisDatabaseId);
        }

        public bool SetCollection<T>(string key, IEnumerable<(string field, T value)> cacheObject, TimeSpan expiryTime)
        {
            RedisDatabase.HashSet(
                key,
                cacheObject
                    .Select(itm => new HashEntry(itm.field, JsonConvert.SerializeObject(itm.value)))
                    .ToArray());
            return RedisDatabase.KeyExpire(key, expiryTime);
        }

        public IEnumerable<T> GetCollection<T>(string key)
        {
            var result = RedisDatabase.HashGetAll(key);
            if (result.Length > 0)
            {
                var items = result.Where(e => e.Value.HasValue)
                  .Select(e => JsonConvert.DeserializeObject<T>(e.Value))
                  .ToList();

                return items;
            }
            return Array.Empty<T>(); ;
        }

        public IEnumerable<T> GetItemsFromCollection<T>(string key, params string[] fields)
        {
            var hashResult = RedisDatabase.HashGet(
                key, 
                fields.Select(key => (RedisValue)key).ToArray());

            var items = hashResult.Where(e => e.HasValue)
                .Select(e => JsonConvert.DeserializeObject<T>(e))
                .ToList();

            return items;
        }
    }
}

У нас есть 3 метода в этом классе:

  • SetCollection
  • ПолучитьКоллекцию
  • GetItemsFromCollection

И вот как вы можете использовать эти методы:

public class Employee
{
    public int Id { get; init; }
    public string Name { get; init; }
    public string UserName { get; init; }
}
using RedisTest.Redis;

const string cacheKey = "myRedisHashKey";

//Creates a new redis cache instance 
var cacheInstance = new RedisHash("127.0.0.1");

//Create the employee instances
var employeeJohn = new Employee
{
    Id = 1,
    Name = "John White",
    UserName = "john.white"
};
var employeeJack = new Employee
{
    Id = 2,
    Name = "Jack Black",
    UserName = "jack.black"
};
var employeeLucas = new Employee
{
    Id = 3,
    Name = "Lucas Brown",
    UserName = "lucas.brown"
};

//Sets the employees in the collection
cacheInstance.SetCollection(
    cacheKey, 
    TimeSpan.FromMinutes(5),
    new List<(string, Employee)>
    {
        (employeeJohn.UserName, employeeJohn),
        (employeeJack.UserName, employeeJack),
        (employeeLucas.UserName, employeeLucas)
    });

//Gets all the employees from Redis
var allEmployees = cacheInstance.GetCollection<Employee>(cacheKey);

//Gets only Jack and John from Redis
var jackAndJohn = cacheInstance.GetItemsFromCollection<Employee>(cacheKey, "jack.black", "john.white");

Производительность

Ниже вы можете сравнить использование Redis String Set и Redis Hash при работе с коллекцией из 20 тыс. элементов со сложной структурой данных:

PS.: Вы также можете выполнить тесты самостоятельно, используя код в GitHub.

RedisTest.Redis.RedisHash
SetCollection(20000): avg: 561 ms         //Sets all the 20k items in the collection
GetCollection(20000): avg: 1039 ms        //Retrieves all the 20k items in the collection
GetItemsFromCollection(1000): avg: 23 ms  //Retrieves 1k items in the collection

RedisTest.Redis.RedisCache
Set(20000): avg: 752 ms      //Sets all the 20k items in the collection
Get(20000): avg: 870 ms      //Retrieves all the 20k items in the collection
GetItems(1000): avg: 473 ms  //Retrieves 1k items in the collection

Как видно из приведенных выше результатов, получение и/или установка всех элементов с использованием двух подходов может иметь одинаковую производительность, но получение только выбранных имеет огромную разницу.

Для предложенного примера это в 20 раз быстрее при использовании Redis Hash. Эта разница может стать еще больше с коллекциями, содержащими более 100 000 элементов или более сложной структурой.

Заключение

Кэши должны улучшить производительность наших приложений. Но, в зависимости от использования, это может создать в нем новое узкое место. Использование Redis Hash может помочь устранить некоторые из тех пробелов в производительности, которые у вас есть.

У вас есть другие идеи о том, как использовать Redis Hash?
Поделитесь со мной в комментариях ниже.

Чтобы узнать об этом больше…
https://redis.io/docs/manual/data-types/#hashes
https://redis.io/ docs/data-types/hashes/
https://github.com/StackExchange/StackExchange.Redis
https://stackexchange.github.io/StackExchange.Redis/

Чтобы проверить код, вы можете получить его на моем GitHub.
https://github.com/danilobsi/Redis-Collection-Store/tree/main/RedisTest