Опубликован: 12.03.2016 | Доступ: свободный | Студентов: 465 / 3 | Длительность: 01:23:00
Лекция 3:

Структуры данных

< Лекция 2 || Лекция 3 || Лекция 4 >
Ключевые слова: команда, string

Пришло время взглянуть поближе на пять структур данных Redis. Мы узнаем, что \linebreak представляет собой каждая из этих структур, что с ней можно делать, и в каких случаях она будет полезна.

До этого момента, мы видели в Redis только команды, ключи и значения. Не было сказано ничего конкретного о структурах данных. Как Redis решал какую структуру данных использовать, когда мы выполняли команду set? На самом деле каждая команда \linebreak применима только к одной конкретной структуре данных. Например, когда вы используете set, данные сохраняются в виде строки (string). Когда вы используете hset, вы сохраняете данные в хеше (hash). С учетом небольшого количества команд в Redis, к этому достаточно просто привыкнуть.

Сайт Redis содержит отличный справочный раздел. Нет смысла повторять уже проделанную работу. Мы рассмотрим только самые важные команды, которые необходимы для понимания предназначения каждой структуры данных.

Нет ничего важнее, чем получать удовольствие от практики и экспериментов. Вы всегда можете стереть все значения в вашей базе данных, введя команду flushdb, поэтому не стесняйтесь и попробуйте сделать что-нибудь интересное!

Строки (Strings)

Строки являются самой простой структурой данных в Redis. Когда вы думаете о паре ключ-значение, вы думаете о строках. Имея уникальные имена (ключи), значения строк могут быть какими угодно. Я предпочитаю называть их "скалярными величинами" - возможно, это только мое предпочтение.

Мы уже видели типичный пример использования строк: хранение значений объектов с определенными ключами. Это именно то, с чем вам придется сталкиваться наиболее часто:

set users:leto "{name: leto, planet: dune, likes: [spice]}"

Дополнительно, Redis позволяет выполнять некоторые стандартные действия со строками. Например, strlen <ключ> используется для вычисления длины значения, связанного с ключом; getrange <ключ> <начало> <конец> возвращает подстроку из строки; append <ключ> <значение> добавляет введенное значение к концу существующей строки (или создает новое значение, если указанный ключ не определен). Попробуйте эти команды в действии. Вот что получилось у меня:

> strlen users:leto
(integer) 42

> getrange users:leto 27 40
"likes: [spice]"

> append users:leto " OVER 9000!!"
(integer) 54

Сейчас вы наверное думаете, что это, конечно, замечательно, но лишено смысла. С помощью этих операций невозможно (в общем случае) получить или добавить значение в JSON-строку. Вы правы - некоторые команды, особенно для работы со строками, имеют смысл только при использовании специфичных форматов данных.

Ранее мы узнали, что Redis не придает смысла введенным вами значениям. Это \linebreak действительно так в большинстве случаев. Тем не менее, некоторые команды \linebreak предназначены лишь для строк, значения которых удовлетворяют определенным условиям. В качестве грубого примера можно привести использование команд append и getrange для какого-нибудь нестандартного способа сериализации данных (сериализация - \linebreak преобразование произвольного объекта/данных в строку - прим. перев.). Более наглядным примером будут команды incr, incrby, decr и decrby. Они предназначены для выполнения инкремента/декремента (увеличения/уменьшения значения) значений строк (в примерах ниже строки в этих командах интерпретируются как числа, в связи с чем используемый автором термин "скалярные значения" (scalars) имеет более точное значение, чем "строки" - прим. перев.):

> incr stats:page:about
(integer) 1
> incr stats:page:about
(integer) 2

> incrby ratings:video:12333 5
(integer) 5
> incrby ratings:video:12333 3
(integer) 8

Как вы можете догадаться, строки Redis отлично подходят для аналитики. Попробуйте инкрементировать значение users:leto (нечисловое) и посмотреть, что получится (вы должны получить ошибку).

Более сложным примером будут команды setbit и getbit. Есть замечательный пост о том, как Spool (Spool - приложение, на которое ссылаются выше - прим. перев.) использует эти две команды для эффективного ответа на вопрос "сколько уникальных посетителей было у нас сегодня?". Для 128 миллионов пользователей ноутбук генерирует ответ менее чем за 50 мс и использует всего лишь 16 МБ памяти.

Не так уж важно понимание того, как работают битовые маски и как Spool использует их, но важно понять, что строки в Redis являются гораздо более мощным инструментом, чем кажется на первый взгляд. Тем не менее, наиболее частыми примерами использования являются те, что мы видели выше: хранение объектов (простых или сложных) и счетчиков. Кроме того, поскольку получение значения по ключу настолько быстрая операция, строки часто используются для кеширования данных.

Хеши (Hashes)

Хеши - хороший пример того, почему называть Redis хранилищем пар ключ-значение не совсем корректно. Хеши во многом похожи на строки. Важным отличием является то, что они предоставляют дополнительный уровень адресации данных - поля (fields). Эквивалентами команд set и get для хешей являются:

hset users:goku powerlevel 9000
hget users:goku powerlevel

Мы также можем устанавливать значения сразу нескольких полей, получать все поля со значениями, выводить список всех полей и удалять отдельные поля:

hmset users:goku race saiyan age 737
hmget users:goku race powerlevel
hgetall users:goku
hkeys users:goku
hdel users:goku age

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

Рассматривая хеши как структурированные объекты, такие как данные пользователя, важно понимать, как они работают. И это правда, что такой подход может быть полезен для повышения производительности. Тем не менее, в следующей главе мы увидим, как хеши могут использоваться для организации ваших данных и удобства запросов к ним. Мне кажется, что именно в этом хеши особенно хороши.

Списки (Lists)

Списки позволяют хранить и манипулировать массивами значений для заданного ключа. Можно добавлять значения в список, получать первое и последнее значение из списка и манипулировать значениями с заданными индексами. Списки сохраняют порядок своих значений и имеют эффективные операции с использованием индексов. Мы можем создать список newusers, содержащий последних зарегистрировавшихся на нашем сайте \linebreak пользователей:

lpush newusers goku
ltrim newusers 0 49

Сначала мы добавляем нового пользователя в начало списка, затем укорачиваем список так, чтобы он содержал 50 последних записей. Это типичный пример использования. Операция ltrim имеет асимптотическую сложность O(N), где N - число значений, которое мы удаляем. В этом случае, если мы всегда укорачиваем список после каждой единичной вставки, эта операция имеет постоянную производительность O(1) (потому что N всегда будет равным единице).

Сейчас мы первый раз столкнемся с тем, что значение с одним ключом ссылается на значение с другим ключом. Если мы хотим получить сведения о последних 10 \linebreak пользователях, мы должны сделать следующее:

keys = redis.lrange('newusers', 0, 9)
redis.mget(*keys.map {|u| "users:#{u}"})

Приведенный выше код на языке Ruby показывает случай многократного обращения к базе данных, о котором мы говорили выше.

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

Множества (Sets)

Множества используются для хранения уникальных значений и предоставляют набор операций - таких, как объединение. Множества не упорядочены, но предоставляют \linebreak эффективные операции со значениями. Список друзей является классическим примером использования множеств:

sadd friends:leto ghanima paul chani jessica
sadd friends:duncan paul jessica alia

Независимо от того, сколько друзей имеет пользователь, мы можем эффективно (O(1)) определить, являются ли пользователи userX и userY друзьями, или нет.

sismember friends:leto jessica
sismember friends:leto vladimir

Более того, мы можем узнать, имеют ли два пользователя общих друзей:

sinter friends:leto friends:duncan

и даже сохранить результат этой операции под новым ключом:

sinterstore friends:leto_duncan friends:leto friends:duncan

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

Упорядоченные Множества (Sorted Sets)

Последней и самой мощной структурой данных являются упорядоченные множества. Хеши похожи на строки, но имеют поля, а упорядоченные множества похожи на множества, но имеют счетчики. Счетчики предоставляют возможности упорядочивания и \linebreak ранжирования. Если мы хотим получить ранжированный список друзей, мы можем сделать следующее:

zadd friends:leto 100 ghanima 95 paul 95 chani 75 jessica 1 vladimir

Хотим узнать, сколько друзей с рейтингом 90 и выше имеет пользователь leto?

zcount friends:leto 90 100

Как насчет рейтинга chani?

zrevrank friends:leto chani

Мы используем zrevrank вместо zrank, поскольку по умолчанию в Redis сортировка \linebreak происходит от меньшего к большему (мы ранжируем от большего к меньшему). Самым очевидным примером использования упорядоченных множеств являются системы \linebreak рейтингов. На самом деле упорядоченные множества подойдут для любых случаев, когда вы имеете значения, которые вы хотите упорядочить, используя для этого целочисленные "весовые коэффициенты", и которыми вы хотите управлять на основании этих \linebreak коэффициентов.

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

В Этой Главе

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

\clearpage
< Лекция 2 || Лекция 3 || Лекция 4 >
Александр Прокофьев
Александр Прокофьев

Что за бред? зачем его выложили если его нельзя закончить???

Тогда уберите бесплатность курса! Или надо жаловаться администрации сайта на вас?

Ups Shelest
Ups Shelest

Раз "Задания которые требуют ручной проверки проверяются только при записи на платное обучение или при обучение с тьютором" то те, кто записался на безплатный курс, попросту не смогут завершить этот курс.

Уровень курса - обзорная статья на хабре. Платить за такое - просто нельзя.

Так как же, все таки, завершить обучение в этом курсе?

Евгений Гавриш
Евгений Гавриш
Россия, Москва
Леонид Рисков
Леонид Рисков
Россия, Екатеринбург, Уральский федеральный университет, 2006