Введение

В отличие от memcache, Redis может быть использован в качестве постоянного хранилища, а не только как временный кэш. Так случилось, что Redis — это невероятно быстрая база данных, дающая поразительно лучшую производительность Вашему приложению в случае правильной настройки. В качестве предостережения хотелось бы добавить, что при работе с Redis в качестве основного хранилища таится много рисков, и эти риски значительно увеличиваются в случае некорректной настройки. Настоятельно рекомендуем предварительно провести тщательное исследование Redis перед тем, как заменять Вашу текущую базу данных в пользу Redis. 

 

Индексация столбцов в Redis


 

Несмотря на преимущество Redis в виде невероятной скорости работы, дающейся Вашему приложению, есть факт, который стоит обязательно учесть – Redis – это фундаментальное хранилище ключей/значений, и он не поддерживает индексы. И Вы можете столкнутся с непредвиденным «челленджем» при попытке проиндексировать Ваши значения. Однако, Вы можете обойти эти ограничения с помощью удивительно полезных предоставляемых Redis типов данных. В этой статье буду рассмотрены способы использования каждых наборов и отсортированных наборов для каждого индекса, и как сортировать записи по датам, а также извлекать строки в пределах диапазона дат.

О комплексных типах Redis

Redis поддерживает несколько комплексных типов — списки, «сеты», отсортированные «сеты» и хэши. В текущей статье не будут упомянуты типы, за исключением «сетов» и отсортированных «сетов». Собственно, «сеты» — это коллекция уникальных неупорядоченных значений. Между тем, отсортированные сеты немного отличаются от обычных сетов. Они позволяют хранить значения, к примеру, с баллами и, таким образом, все члены отсортированного набора можно будет запросить с помощью баллов или диапазона баллов. Это может быть очень удобным при хранении, например, даты. Происходит конвертация каждого одиночного элемента (даты) и хранится в его «тике». Таким образом получается упорядоченный набор значений. Затем можно использовать команду ZRANGEBYSCORE REDIS для получения значений, которые подходят под определённый диапазон.

Redis, как хранилище ключевых значений, может также хранить элемент с любым типом данных в базе данных до тех пор, пока этот элемент будет иметь уникальный ключ, определённый для него, вне зависимости от того, какого типа этот элемент – строковый или числовой.  

Использование в коде

Код не так страшен, как может показаться с первого взгляда. Например, целый объект сохраняется с указателем ко всем индексам. Подход таков, что объект может быть сохранён, как скалярная строка с id в различных индексах.

В коде есть класс «Person», позволяющий сохранять объекты класса «Person» с параметрами, например, имя, пол, страна проживания и дата рождения. Класс написан достаточно просто, на мелкие детали не придётся обращать внимание. Также в коде есть статический класс «RedisAdaptor», который содержит в себе вспомогательные функции для сохранения класса «Person» и вызова его объектов.

Основная программа

В основной программе имеется цикл, который прогоняется ежедневно на протяжении указанного года – 1971 и создаёт новый объект класса «Person» с указанием даты рождения. В случае с полом мы оставляем один единственный объект с указанием женского или мужского пола. Для страны (учитывая, что определены только 3 страны) каждый объект класса «Person» проживает в Индии, США и Англии. В качестве имени используется строка, генерируемая случайным образом.

 

static void Main(string[] args)

{

    const int YEAR = 1971;

 

    // We create one Person object for every single day in the given year.

    for (int month = 1; month <= 12; ++month)

    {

        for (int day = 1; day <= 31; ++day)

        {

            try

            {

                // Get any random name:

                string name = Util.GetAnyName();

                // And a DoB:

                DateTime dob = new DateTime(YEAR, month, day);

                // As for the gender, let's alternate:

                Gender gender = Gender.FEMALE;

                if (day % 2 == 0)

                {

                    gender = Gender.MALE;

                }

                // And the country, let's round-robin between all three:

                Country country = Country.INDIA;

                if (day % 3 == 1)

                {

                    country = Country.USA;

                }

                else if (day % 3 == 2)

                {

                    country = Country.GB;

                }

 

                // Create a new Person object:

                Person person = new Person(name, gender, country, dob);

                //Console.WriteLine ("Created new Person object: {0}", person);

 

                // We call the function that will store a new person in Redis:

                RedisAdaptor.StorePersonObject(person);

            }

            catch (Exception)

            {

                // If the control reaches here, it means the date was illegal.

                // So we just shrug your shoulders and move on to the next date.

                continue;

            }

        }

    }

 

    // At this point, we have 365 Person objects as a sorted set in our Redis database.

 

    // Next, let's take a date range and retrieve Person objects from within that range.

    DateTime fromDate = DateTime.Parse("5-May-" + YEAR);

    DateTime toDate = DateTime.Parse("7-May-" + YEAR);

 

    List persons = RedisAdaptor.RetrievePersonObjects(fromDate, toDate);

 

    Console.WriteLine("Retrieved values in specified date range:");

    foreach (Person person in persons)

    {

        Console.WriteLine(person);

    }

 

    // Next, let's select some folks who are female AND from the USA.

    // This calls for a set intersection operation.

    List personsSelection = RedisAdaptor.RetrieveSelection(Gender.FEMALE, Country.USA);

 

    Console.WriteLine("Retrieved values in selection:");

    foreach (Person person in personsSelection)

    {

        Console.WriteLine(person);

    }

}

 

В классе «Redis Adaptor» есть одиночная функция, используемая для хранения и индексации значений, прошедших через него, и функция для запрашивания значений по диапазонам дат, полу и стране. Также в программе имеется несколько статических полей и констант для данных.

Учтите, что индексация уже произошла в процессе сохранения. В этом участке мы проиндексировали пол, страну и дату рождения.

 

static class RedisAdaptor

{

    const string REDIS_HOST = "127.0.0.1";

 

    private static ConnectionMultiplexer _redis;

 

    // Date of birth key:

    const string REDIS_DOB_INDEX = "REDIS_DOB_INDEX";

 

    // Gender keys:

    const string REDIS_MALE_INDEX = "REDIS_MALE_INDEX";

    const string REDIS_FEMALE_INDEX = "REDIS_FEMALE_INDEX";

 

    // Country keys:

    const string REDIS_C_IN_INDEX = "REDIS_C_IN_INDEX";

    const string REDIS_C_USA_INDEX = "REDIS_C_USA_INDEX";

    const string REDIS_C_GB_INDEX = "REDIS_C_GB_INDEX";

 

    static RedisAdaptor()

    {

        // First, init the connection:

        _redis = ConnectionMultiplexer.Connect(REDIS_HOST);

    }

 

    public static void StorePersonObject(Person person)

    {

        // We first JSONize the object so that it's easier to save:

        string personJson = JsonConvert.SerializeObject(person);

        //Console.WriteLine ("JSONized new Person object: {0}", personJson);

 

        // And save it to Redis.

 

        // First, get the database object:

        IDatabase db = _redis.GetDatabase();

 

        // Bear in mind that Redis is fundamentally a key-value store that does not provide

        // indexes out of the box.

        // We therefore work our way around this by creating and managing our own indexes.

 

        // The first index that we have is for gender.

        // We have two sets for this in Redis: one for males and the other for females.

        if (person.Gender == Gender.MALE)

        {

            db.SetAdd(REDIS_MALE_INDEX, personJson);

        }

        else {

            db.SetAdd(REDIS_FEMALE_INDEX, personJson);

        }

 

        // Next, we index by country.

        if (person.Country == Country.INDIA)

        {

            db.SetAdd(REDIS_C_IN_INDEX, personJson);

        }

        else if (person.Country == Country.USA)

        {

            db.SetAdd(REDIS_C_USA_INDEX, personJson);

        }

        else if (person.Country == Country.GB)

        {

            db.SetAdd(REDIS_C_GB_INDEX, personJson);

        }

 

        // Next, we need to create an index to be able to retrieve values that are in a particular

        // date range.

 

        // Since we need to index by date, we use the sorted set structure in Redis. Sorted sets

        // require a score (a real) to save a record. Therefore, in our case, we will use the

        // DoB's `ticks' value as the score.

        double dateTicks = (double)person.DoB.Ticks;

 

        db.SortedSetAdd(REDIS_DOB_INDEX, personJson, dateTicks);

    }

 

    public static List RetrievePersonObjects(DateTime fromDate, DateTime toDate)

    {

        // First. let's convert the dates to tick values:

        double fromTicks = fromDate.Ticks;

        double toTicks = toDate.Ticks;

 

        // And retrieve values from the sorted set.

 

        // First, get the database object:

        IDatabase db = _redis.GetDatabase();

 

        RedisValue[] vals = db.SortedSetRangeByScore(REDIS_DOB_INDEX, fromTicks, toTicks);

        List opList = new List();

        foreach (RedisValue val in vals)

        {

            string personJson = val.ToString();

            Person person = JsonConvert.DeserializeObject(personJson);

            opList.Add(person);

        }

 

        return opList;

    }

 

    public static List RetrievePersonObjects(Gender gender)

    {

        // First, get the database object:

        IDatabase db = _redis.GetDatabase();

 

        string keyToUse = gender == Gender.MALE ? REDIS_MALE_INDEX : REDIS_FEMALE_INDEX;

 

        RedisValue[] vals = db.SetMembers(keyToUse);

 

        List opList = new List();

        foreach (RedisValue val in vals)

        {

            string personJson = val.ToString();

            Person person = JsonConvert.DeserializeObject(personJson);

            opList.Add(person);

        }

 

        return opList;

    }

 

    public static List RetrievePersonObjects(Country country)

    {

        // First, get the database object:

        IDatabase db = _redis.GetDatabase();

 

        string keyToUse = REDIS_C_IN_INDEX;

        if (country == Country.USA)

        {

            keyToUse = REDIS_C_USA_INDEX;

        }

        else if (country == Country.GB)

        {

            keyToUse = REDIS_C_GB_INDEX;

        }

 

        RedisValue[] vals = db.SetMembers(keyToUse);

 

        List opList = new List();

        foreach (RedisValue val in vals)

        {

            string personJson = val.ToString();

            Person person = JsonConvert.DeserializeObject(personJson);

            opList.Add(person);

        }

 

        return opList;

    }

 

    public static List RetrieveSelection(Gender gender, Country country)

    {

        // First, get the database object:

        IDatabase db = _redis.GetDatabase();

 

        string keyToUseGender = gender == Gender.MALE ? REDIS_MALE_INDEX : REDIS_FEMALE_INDEX;

        string keyToUseCountry = REDIS_C_IN_INDEX;

        if (country == Country.USA)

        {

            keyToUseCountry = REDIS_C_USA_INDEX;

        }

        else if (country == Country.GB)

        {

            keyToUseCountry = REDIS_C_GB_INDEX;

        }

 

        RedisKey[] keys = new RedisKey[] { keyToUseGender, keyToUseCountry };

 

        RedisValue[] vals = db.SetCombine(SetOperation.Intersect, keys);

 

        List opList = new List();

        foreach (RedisValue val in vals)

        {

            string personJson = val.ToString();

            Person person = JsonConvert.DeserializeObject(personJson);

            opList.Add(person);

        }

 

        return opList;

    }

}

 

Каждый ключ константы определяется в статическом классе, как, например, REDIS_DOB_INDEX, REDIS_MALE_INDEX, REDIS_FEMALE_INDEX и остальные,  всё это – ключи к индивидуальным сетам в хранилище Redis.

Однажды, когда мы сохранили значения и создали индексы для сетов в Redis, мы сможем запрашивать их, используя различные версии перегруженных функций RetrievePersonObjects с параметрами – диапазоном дат, полом и страной.

Запрос по полу достаточно прост: основываясь на определении пола, мы «погружаемся» в один из двух гендерных сетов и получаем запрошенное значение. Точно та же процедура используется в отношении индекса стран.

Чтобы извлечь значения диапазона дат, используется метод SortedSetRangeByScore в объекте базы данных. Он принимает три аргумента: первый – имя сортированного сета, минимальные и максимальные значения.

Ещё одна интересная фича в работе с сетами Redis – великолепные семантические сеты. С их помощью можно определять два и более сетов, в результате чего БД Redis делает объединение, пересечение или определяет разность сетов. В последней секции кода посмотрите на функцию снизу – «RetrieveSelection», которая декларирует два параметра – пол и страна. Эта функция соответственно возвращает два параметра – пол и страну.

Источник: http://www.codeproject.com/Articles/1072137/Indexing-Columns-in-Redis