Какие основные структуры данных используются для Redis?

305

Я пытаюсь ответить на два вопроса в окончательном списке:

  1. Какие основные структуры данных используются для Redis?
  2. И каковы основные преимущества / недостатки / варианты использования для каждого типа?

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

В частности, я хочу обрисовать в общих чертах все типы: строку, список, набор, zset и хэш.

О, я до сих пор смотрел на эту статью:

Homer6
источник
7
Как использовать сервер это мелочи? Как определить, когда использовать одну структуру программирования поверх другой? Это напрямую применимо к программированию, так как я бы использовал разные типы для разных целей.
Homer6
2
Как использовать сервер - это не обязательно мелочи, но это не по теме - и это не то, что вы просили. Какие структуры данных использовать для конкретных целей было бы актуально, но это не то, что вы спросили. То, что использовалось в Redis, - это мелочи, без дополнительных рассуждений о том, почему они использовали определенную структуру в конкретной ситуации - когда мы вернемся к тому, что я уже сказал, было бы актуально, а Redis - не имеет значения.
Джерри Коффин
5
В теме четко сказано: «Каковы структуры данных и когда следует использовать разные типы?» Как это не по теме? Вы говорите, что изучение связанных списков, хэшей и массивов не имеет отношения к программированию? Потому что я бы сказал, что они имеют прямое отношение, особенно к серверу, который предназначен в первую очередь для повышения производительности. Кроме того, они актуальны, потому что неправильный выбор может означать значительно меньшую производительность от одного приложения к другому.
Homer6
19
Ответ Антиреса спасает этот вопрос. закрывайте в ущерб программистам и перерисуйте пользователей везде.
Джон Шихан
75
@JerryCoffin, при всем моем уважении, redis - это инструмент разработки программного обеспечения, и вопросы о средствах разработки программного обеспечения решительно обсуждаются. Тот факт, что «вы можете получить ответ от источника», не является непосредственной причиной ... чтобы получить ответ от источника, потребуются часы. И redis очень широко используется, поэтому этот вопрос не слишком локализован. Переполнение стека - все о изучении программирования и задаче, какая структура данных используется чрезвычайно популярным инструментом программирования, способствует достижению этой цели. Короче говоря, я не могу найти причину, чтобы закрыть этот вопрос.
Джоэл Спольски

Ответы:

612

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

Но так как вы спросили, вот основная реализация каждого типа данных Redis.

  • Строки реализованы с использованием библиотеки динамических строк C, так что мы не платим (асимптотически) за выделения в операциях добавления. Таким образом, мы добавляем, например, O (N) вместо квадратичного поведения.
  • Списки реализованы с помощью связанных списков.
  • Наборы и хэши реализованы с помощью хеш-таблиц.
  • Сортированные наборы реализованы с помощью пропускаемых списков (особый тип сбалансированных деревьев).

Но когда списки, наборы и отсортированные наборы малы по количеству элементов и размеру самых больших значений, используется другое, гораздо более компактное кодирование. Эта кодировка отличается для разных типов, но имеет особенность, заключающуюся в том, что она представляет собой компактный блок данных, который часто вызывает сканирование O (N) для каждой операции. Поскольку мы используем этот формат только для небольших объектов, это не проблема; Сканирование небольшого O (N) -блока является кэш-памятью, поэтому практически очень быстро, и когда слишком много элементов, кодирование автоматически переключается на собственное кодирование (связанный список, хэш и т. д.).

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

Струны

Это базовый тип всех типов. Это один из четырех типов, но также является базовым типом сложных типов, потому что List - это список строк, Set - это набор строк и так далее.

Строка Redis - это хорошая идея во всех очевидных сценариях, где вы хотите сохранить HTML-страницу, а также когда вы хотите избежать преобразования уже закодированных данных. Например, если у вас есть JSON или MessagePack, вы можете просто хранить объекты в виде строк. В Redis 2.6 вы можете даже манипулировать такого рода объектной серверной стороной, используя сценарии Lua.

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

Списки

Списки хороши, когда вы можете коснуться только крайностей списка: около хвоста или около головы. Списки не очень хорошо разбивают на страницы, потому что произвольный доступ медленный, O (N). Поэтому хорошим использованием списков являются простые очереди и стеки, или обработка элементов в цикле с использованием RPOPLPUSH с тем же источником и назначением для «вращения» кольца элементов.

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

наборы

Наборы - это неупорядоченный сбор данных, поэтому они хороши каждый раз, когда у вас есть набор элементов, и очень важно очень быстро проверить наличие или размер коллекции. Еще одна интересная вещь о наборах - это поддержка случайных или случайных элементов (команды SRANDMEMBER и SPOP).

Наборы также хороши для представления отношений, например: «Кто друзья пользователя X?» и так далее. Но другие хорошие структуры данных для такого рода вещей - это отсортированные наборы, как мы увидим.

Наборы поддерживают сложные операции, такие как пересечения, объединения и т. Д., Так что это хорошая структура данных для использования Redis «вычислительным» способом, когда у вас есть данные, и вы хотите выполнить преобразования этих данных для получения некоторого вывода.

Маленькие наборы кодируются очень эффективно.

Хэш

Хэши - это идеальная структура данных для представления объектов, состоящих из полей и значений. Поля хэшей также могут быть атомарно увеличены с помощью HINCRBY. Если у вас есть такие объекты, как пользователи, сообщения в блоге или другие элементы , хэши, вероятно, будут хорошим вариантом, если вы не хотите использовать свою собственную кодировку, такую ​​как JSON или аналогичную.

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

Хэши также можно использовать для представления связанных структур данных с использованием ссылок. Например, проверьте реализацию комментариев на lamernews.com.

Сортированные Наборы

Сортированные наборы являются единственными другими структурами данных, кроме списков, которые поддерживают упорядоченные элементы . Вы можете сделать много интересных вещей с отсортированными наборами. Например, вы можете иметь все виды списков Top Something в вашем веб-приложении. Лучшие пользователи по количеству баллов, лучшие посты по количеству просмотров страниц и тому подобное, но один экземпляр Redis будет поддерживать множество операций вставки и get-top-elements в секунду.

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

Сортированные наборы хороши для приоритетных очередей.

Сортированные наборы похожи на более мощные списки, где вставка, удаление или получение диапазонов из середины списка всегда происходит быстро. Но они используют больше памяти и являются O (log (N)) структурами данных.

Вывод

Я надеюсь, что я предоставил некоторую информацию в этом посте, но гораздо лучше скачать исходный код lamernews с http://github.com/antirez/lamernews и понять, как это работает. Многие структуры данных из Redis используются внутри Lamer News, и есть много подсказок о том, что использовать для решения данной задачи.

Извините за грамматические опечатки, у нас полночь, и я слишком устал для просмотра поста;)

antirez
источник
45
Это единственный автор Redis. Я написал ему по электронной почте и попросил его ответить. Спасибо большое Сальваторе. Это отличная информация.
Homer6
58
Спасибо, но я не единственный большой вкладчик, Питер Нордхёйс предоставил очень большую часть текущей реализации :)
antirez
1
Если идентичная строка находится во многих различных наборах, будет ли сохранена только одна копия строки?
Сбриан
Как zscore в O (1), используя только список пропуска?
Максим
1
Хотя список пропусков не является надлежащим сбалансированным деревом, вы можете видеть список пропусков как «перевернутое» случайное дерево. Они в основном своего рода эквивалентны, даже если реализация и расположение отличаются.
антирез
80

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

Внутри Redis использует следующие структуры данных:

  1. строка
  2. Словарь
  3. Двусвязный список
  4. Пропустить список
  5. Zip List
  6. Int устанавливает
  7. Карты Zip (устарел в пользу списка почтовых индексов начиная с Redis 2.6)

Чтобы найти кодировку, используемую определенным ключом, используйте команду object encoding <key>.

1. Струны

В Redis строки называются простыми динамическими строками или SDS . Это небольшая обертка надchar * которая позволяет хранить длину строки и количество свободных байтов в качестве префикса.

Поскольку длина строки сохраняется, strlen является операцией O (1). Кроме того, поскольку длина известна, строки Redis безопасны в двоичном формате. Для строки вполне допустимо содержать нулевой символ .

Строки - самая универсальная структура данных, доступная в Redis. Строка это все из следующего:

  1. Строка символов, которая может хранить текст. Смотрите команды SET и GET .
  2. Массив байтов, который может хранить двоичные данные.
  3. А, longкоторый может хранить номера. См INCR , ОВЦС , INCRBY и DECRBY команды.
  4. Array (из chars, ints, longsили любого другого типа данных) , что может позволить эффективно случайному доступ. Смотрите команды SETRANGE и GETRANGE .
  5. Битовый массив , который позволяет установить или получить отдельные биты. Смотрите команды SETBIT и GETBIT .
  6. Блок памяти, который вы можете использовать для построения других структур данных. Это используется внутри для создания ziplists и intsets, которые являются компактными, эффективными для памяти структурами данных для небольшого числа элементов. Подробнее об этом ниже.

2. Словарь

Redis использует словарь для следующего:

  1. Чтобы сопоставить ключ с его связанным значением, где значением может быть строка, хэш, набор, отсортированный набор или список.
  2. Для сопоставления ключа с отметкой времени его истечения.
  3. Для реализации типов данных Hash, Set и Sorted Set.
  4. Чтобы сопоставить команды Redis с функциями, которые обрабатывают эти команды.
  5. Чтобы сопоставить ключ Redis со списком клиентов, заблокированных для этого ключа. Смотрите BLPOP .

Словари Redis реализованы с использованием хэш-таблиц . Вместо объяснения реализации я просто объясню конкретные вещи Redis:

  1. Словари используют структуру, вызываемую dictTypeдля расширения поведения хеш-таблицы. Эта структура имеет указатели на функции, поэтому следующие операции являются расширяемыми: а) хеш-функция, б) сравнение ключей, в) деструктор ключей и г) деструктор значений.
  2. Словари используют murmurhash2 . (Ранее они использовали хеш-функцию djb2 с seed = 5381, но затем хэш-функция была переключена на murmur2 . См. Этот вопрос для объяснения алгоритма хеширования djb2 .)
  3. Redis использует пошаговое хеширование, также известное как пошаговое изменение размеров . В словаре есть две хеш-таблицы. Каждый раз при касании словаря одна корзина переносится из первой (меньшей) хэш-таблицы во вторую. Таким образом, Redis предотвращает дорогостоящую операцию изменения размера.

Структура Setданных использует словарь, чтобы гарантировать отсутствие дубликатов. Sorted SetИспользует словарь для отображения элемента в его счет, поэтому ZSCORE представляет собой O (1) операции.

3. Вдвойне связанные списки

Тип listданных реализован с использованием двусвязных списков . Реализация Redis - это учебник прямо из алгоритма. Единственное изменение заключается в том, что Redis сохраняет длину в структуре данных списка. Это гарантирует, что LLEN имеет O (1) сложность.

4. Пропустить списки

Redis использует Skip Lists в качестве базовой структуры данных для Sorted Sets. В Википедии есть хорошее введение. В статье Уильяма Пью « Пропустить списки: вероятностная альтернатива сбалансированным деревьям» есть больше деталей.

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

Реализация списка пропусков в Redis отличается от стандартной реализации следующими способами:

  1. Redis позволяет дублировать баллы. Если два узла имеют одинаковую оценку, они сортируются по лексикографическому порядку .
  2. Каждый узел имеет обратный указатель на уровне 0. Это позволяет вам проходить элементы в обратном порядке оценки.

5. Список почтовых индексов

Zip-список похож на двусвязный список, за исключением того, что он не использует указатели и хранит данные встроенными.

Каждый узел в двусвязном списке имеет 3 указателя - один прямой указатель, один обратный указатель и один указатель для ссылки на данные, хранящиеся на этом узле. Указатели требуют памяти (8 байт в 64-битной системе), поэтому для небольших списков двусвязный список очень неэффективен.

Список Zip хранит элементы последовательно в строке Redis. Каждый элемент имеет небольшой заголовок, в котором хранится длина и тип данных элемента, смещение к следующему элементу и смещение к предыдущему элементу. Эти смещения заменяют прямые и обратные указатели. Поскольку данные хранятся в строке, нам не нужен указатель данных.

Zip-список используется для хранения небольших списков, отсортированных наборов и хэшей. Сортированные наборы сведены в список, как [element1, score1, element2, score2, element3, score3]и сохранены в Zip List. Хэши сведены в список, как [key1, value1, key2, value2]и т. Д.

С помощью Zip-списков вы можете найти компромисс между процессором и памятью. Zip-списки эффективны при использовании памяти, но они используют больше ресурсов ЦП, чем связанный список (или хэш-таблица / список пропусков). Найти элемент в списке почтовых индексов O (n). Вставка нового элемента требует перераспределения памяти. Из-за этого Redis использует эту кодировку только для небольших списков, хэшей и отсортированных наборов. Вы можете настроить это поведение, изменив значения <datatype>-max-ziplist-entriesи <datatype>-max-ziplist-value>в redis.conf. Видеть Redis Memory Optimization, раздел «Специальное кодирование небольших агрегированных типов данных» для получения дополнительной информации.

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

6. Интеллектуальные сеты

Наборы Int - это причудливое название для "Sorted Integer Arrays"

В Redis наборы обычно реализуются с использованием хеш-таблиц. Для небольших наборов хеш-таблица неэффективна с точки зрения памяти. Когда набор состоит только из целых чисел, массив часто более эффективен.

Набор Int - это отсортированный массив целых чисел. Для поиска элемента используется алгоритм двоичного поиска . Это имеет сложность O (log N). Добавление новых целых чисел в этот массив может потребовать перераспределения памяти, что может стать дорогим для больших целочисленных массивов.

В качестве дальнейшей оптимизации памяти, наборы Int входят в 3 варианта с различными целочисленными размерами: 16 бит, 32 бита и 64 бита. Redis достаточно умен, чтобы использовать правильный вариант в зависимости от размера элементов. Когда добавляется новый элемент, и он превышает текущий размер, Redis автоматически переносит его на следующий размер. Если строка добавлена, Redis автоматически преобразует набор Int в обычный набор на основе хэш-таблицы.

Наборы Int - это компромисс между процессором и памятью. Наборы Int чрезвычайно эффективны в использовании памяти, а для небольших наборов они работают быстрее, чем хеш-таблица. Но после определенного количества элементов время извлечения O (log N) и стоимость перераспределения памяти становятся слишком большими. На основе экспериментов было установлено, что оптимальное пороговое значение для переключения на обычную хэш-таблицу составляет 512. Однако вы можете увеличить это пороговое значение (уменьшать его не имеет смысла) в зависимости от потребностей вашего приложения. Смотрите set-max-intset-entriesв redis.conf.

7. Почтовые Карты

Zip-карты - это словари, сведенные и сохраненные в списке. Они очень похожи на Zip Lists.

Zip-карты больше не используются с Redis 2.6, а небольшие хеш-коды хранятся в Zip-списках. Чтобы узнать больше об этой кодировке, обратитесь к комментариям в zipmap.c .

Sripathi Krishnan
источник
2

Redis хранит ключи, указывающие на значения. Ключи могут быть любыми двоичными значениями вплоть до разумного размера (рекомендуется использовать короткие строки ASCII для удобства чтения и отладки). Значения являются одним из пяти собственных типов данных Redis.

1.strings - последовательность двоичных безопасных байтов до 512 МБ

2.hashes - коллекция пар ключ-значение

3.lists - коллекция строк в порядке вставки

4.sets - коллекция уникальных строк без упорядочивания

5. отсортированные наборы - набор уникальных строк, упорядоченных по пользовательской оценке

Струны

Строка Redis - это последовательность байтов.

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

Строки - это каноническое понятие «ключ-хранилище». У вас есть ключ, указывающий на значение, где ключ и значение являются текстовыми или двоичными строками.

Для всех возможных операций со строками, см. Http://redis.io/commands/#string

Хэш

Хэш Redis - это коллекция пар ключ-значение.

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

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

Прямые представления объектов просты для понимания. Объекты имеют имя (ключ хеша) и набор внутренних ключей со значениями. Смотрите пример ниже, ну, в качестве примера.

Хранение множества небольших значений с использованием хэша - это умная технология хранения больших объемов данных Redis. Когда хеш имеет небольшое количество полей (~ 100), Redis оптимизирует хранение и эффективность доступа ко всему хешу. Оптимизация хранилища небольших хэшей в Redis вызывает интересное поведение: более эффективно иметь 100 хешей с 100 внутренними ключами и значениями, а не 10 000 ключей верхнего уровня, указывающих на строковые значения. Использование хэшей Redis для оптимизации хранилища данных таким способом требует дополнительных затрат на программирование для отслеживания того, где заканчиваются данные, но если ваше хранилище данных основано на строковых значениях, вы можете сэкономить много накладных расходов памяти, используя этот один странный прием.

Для всех возможных операций с хэшами см. Документацию по хешу.

Списки

Списки Redis действуют как связанные списки.

Вы можете вставлять, удалять и просматривать списки либо из заголовка, либо из конца списка.

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

Списки повторного использования часто используются в качестве очередей производителей / потребителей. Вставьте элементы в список, а затем вытолкните элементы из списка. Что произойдет, если ваши потребители попытаются выскочить из списка без элементов? Вы можете попросить Redis дождаться появления элемента и сразу же вернуть его вам, когда он будет добавлен. Это превращает Redis в систему сообщений / событий / заданий / задач / уведомлений в режиме реального времени.

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

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

Для всех возможных операций со списками, см. Списки документов

наборы

Наборы Redis - это, ну, наборы.

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

Наборы быстрые для проверки членства, вставки и удаления членов в наборе.

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

Наборы имеют постоянный доступ для проверки членства (в отличие от списков), а Redis даже имеет удобное удаление и возврат случайных элементов («извлечение случайного элемента из набора») или случайный возврат элементов без замены («дайте мне 30 уникальных случайных пользователей») ") или с заменой (" дайте мне 7 карточек, но после каждого выбора положите карточку обратно, чтобы она потенциально могла быть взята снова ").

Для всех возможных операций над наборами см. Документацию по наборам .

Сортированные Наборы

Сортированные наборы Redis - это наборы с заданным пользователем порядком.

Для простоты вы можете представить отсортированный набор как двоичное дерево с уникальными элементами. (Сортированные Redis наборы на самом деле пропустить списки .) Порядок сортировки элементов определяется счетом каждого элемента.

Сортированные наборы все еще наборы. Элементы могут появляться только один раз в наборе. Элемент в целях уникальности определяется его содержимым строки. Вставка элемента «яблоко» с оценкой сортировки 3, затем вставка элемента «яблоко» с оценкой 500 сортировки приводит к получению одного элемента «яблоко» с оценкой 500 сортировки в вашем отсортированном наборе. Наборы уникальны только на основе данных, а не на основе пар (оценка, данные).

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

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

Вы можете получить элементы несколькими различными способами. Поскольку все отсортировано, вы можете запросить элементы, начиная с самых низких баллов. Вы можете запросить элементы, начиная с самых высоких баллов («в обратном порядке»). Вы можете запросить элементы по их сортировке в натуральном или обратном порядке.

Для всех возможных операций над отсортированными наборами см. Документацию по отсортированным наборам.

Shrikant
источник