предисловие
В настоящее время Redis является одной из самых популярных баз данных в памяти.Благодаря чтению и записи данных в памяти скорость чтения и записи значительно повышается.Можно сказать, что Redis является неотъемлемой частью достижения высокого уровня параллелизма на веб-сайте.
Когда мы используем Redis, мы сталкиваемся с 5 типами объектов Redis (строка, хэш, список, набор, упорядоченный набор) Богатые типы являются основным преимуществом Redis по сравнению с Memcached. На основе понимания использования и характеристик пяти типов объектов Redis дальнейшее понимание модели памяти Redis очень помогает в использовании Redis, например:
1. Оцените использование памяти Redis. До сих пор стоимость использования памяти все еще относительно высока, и использование памяти не может быть безрассудным; разумная оценка использования памяти Redis в соответствии с требованиями и выбор соответствующей конфигурации машины могут сэкономить затраты при соблюдении требований.
2. Оптимизируйте использование памяти. Понимание модели памяти Redis позволяет выбирать более подходящие типы данных и кодировки для более эффективного использования памяти Redis.
3. Анализировать и решать проблемы. Когда у Redis возникают проблемы, такие как блокировка и использование памяти, как можно скорее найдите причину проблемы, чтобы проанализировать и решить проблему.
В этой статье в основном представлена модель памяти Redis (в качестве примера возьмем 3.0), в том числе использование памяти Redis и способы ее запроса, метод кодирования различных типов объектов в памяти, распределитель памяти (jemalloc), простая динамическая строка (SDS). ) ), RedisObject и т. д., а затем внедрить на этой основе несколько приложений модели памяти Redis.
В следующих статьях мы представим содержание высокой доступности Redis, включая репликацию master-slave, сторожевые устройства, кластеры и т. д., обратите внимание.
Нелегко быть оригинальным. Если вы считаете, что статья полезна для вас, ставьте лайк и оставляйте комментарии.В статье есть упущения, критика и исправления приветствуются.
Добро пожаловать на перепечатку, пожалуйста, укажите оригинальную ссылку:блог woo woo woo.cn на.com/kismet V/afraid/8…
содержание
2. Память, необходимая для запуска самого процесса
3. Подробная информация о хранилище данных Redis
В-четвертых, тип объекта Redis и внутренняя кодировка.
1. Оцените использование памяти Redis
2. Оптимизируйте использование памяти
3. Обратите внимание на скорость фрагментации памяти
1. Статистика памяти Redis
Прежде чем объяснять память Redis, мы сначала объясним, как подсчитывать использование памяти Redis.
После того, как клиент подключится к серверу через redis-cli (если потом не будет специальных указаний, клиент всегда будет использовать redis-cli), можно проверить использование памяти через команду info:
?1 | info memory |
Среди них команда info может отображать много информации о сервере Redis, включая базовую информацию о сервере, ЦП, памяти, постоянстве, информацию о клиентском подключении и т. д. memory — это параметр, указывающий, что отображается только информация, связанная с памятью.
Наиболее важные описания в возвращаемых результатах следующие:
(1)used_memory:Общий объем памяти (в байтах), выделенный распределителем Redis, включая используемую виртуальную память (т. е. подкачку); распределитель Redis будет представлен позже. used_memory_human просто удобен для отображения.
(2)used_memory_rss:Процесс Redis занимает память операционной системы (в байтах), что согласуется со значениями, которые видят команды top и ps; помимо памяти, выделенной распределителем, used_memory_rss также включает память и требуемые фрагменты памяти самим процессом запускать и т.д. Но не включая виртуальную память.
Таким образом, used_memory и used_memory_rss, первое — это количество, полученное с точки зрения Redis, а второе — это количество, полученное с точки зрения операционной системы. Причина различия между ними заключается в том, что, с одной стороны, фрагментация памяти и выполнение процесса Redis должны занимать память, что делает первый, возможно, меньше второго, а с другой стороны, наличие виртуальной памяти. делает первое больше второго.
В практических приложениях объем данных в Redis будет относительно большим, а память, занимаемая запущенным в это время процессом, будет намного меньше, чем объем данных Redis и фрагментация памяти, поэтому отношение used_memory_rss и used_memory становится равным мера фрагментации памяти Redis Параметр скорости, это параметр mem_fragmentation_ratio.
(3)mem_fragmentation_ratio:Коэффициент фрагментации памяти, представляющий собой отношение used_memory_rss/used_memory.
mem_fragmentation_ratio обычно больше 1, и чем больше значение, тем больше коэффициент фрагментации памяти. mem_fragmentation_ratio
Вообще говоря, mem_fragmentation_ratio находится в относительно здоровом состоянии (для jemalloc) около 1,03; mem_fragmentation_ratio на скриншоте выше очень большой, потому что данные не были сохранены в Redis, а память, работающая в самом процессе Redis, делает used_memory_rss выше, чем used_memory намного больше.
(4)mem_allocator:Распределитель памяти, используемый Redis, указывается во время компиляции, это может быть libc, jemalloc или tcmalloc, по умолчанию используется jemalloc, на скриншоте используется jemalloc по умолчанию.
2. Разделение памяти Redis
Redis — это база данных в памяти, а содержимое, хранящееся в памяти, — это в основном данные (пары ключ-значение).Из предыдущего описания мы можем знать, что помимо данных другие части Redis также будут занимать память.
Объем памяти Redis можно разделить на следующие части:
1. Данные
В базе данных данные являются самой важной частью, память, занимаемая этой частью, будет учитываться в used_memory.
Redis использует пары ключ-значение для хранения данных, а значения (объекты) включают пять типов, а именно строки, хэши, списки, наборы и упорядоченные наборы. Эти 5 типов предоставляются Redis извне.На самом деле внутри Redis каждый тип может иметь 2 или более реализации внутреннего кодирования, кроме того, когда Redis хранит объекты, он не выбрасывает данные напрямую в память, а упаковывает объекты в различные способами: такими как redisObject, SDS и т. д., в этой статье речь пойдет о деталях хранения данных в Redis.
2. Память, необходимая для запуска самого процесса
Сам основной процесс Redis должен занимать память, такую как код, постоянный пул и т. д., эта часть памяти составляет около нескольких мегабайт, что можно не учитывать по сравнению с памятью, занимаемой данными Redis в большинстве производственных сред. Эта часть памяти не выделяется jemalloc, поэтому она не будет учитываться в used_memory.
Дополнительное примечание. В дополнение к основному процессу дочерний процесс, созданный Redis, также будет занимать память, например дочерний процесс, созданный, когда Redis выполняет перезапись AOF и RDB. Разумеется, эта часть памяти не принадлежит процессу Redis и не будет учитываться в used_memory и used_memory_rss.
3. Буферная память
Буферная память включает в себя клиентские буферы, буферы невыполненных копий, буферы AOF и т. д., среди них буфер клиента хранит входные и выходные буферы клиентских соединений, буфер невыполненных копий используется для части функции копирования, буфер AOF используется для воспроизведения AOF.При записи сохраните самую последнюю команду записи. Прежде чем разбираться в соответствующей функции, вам не нужно знать подробности этих буферов, эта часть памяти выделяется jemalloc, поэтому она будет засчитана в used_memory.
4. Фрагментация памяти
Фрагментация памяти создается Redis в процессе выделения и освобождения физической памяти. Например, если данные часто меняются и размер данных сильно различается, освобождаемое redis пространство может не освобождаться в физической памяти, но redis не может его эффективно использовать, что формирует фрагментацию памяти. Фрагментация памяти не будет учитываться в used_memory.
Генерация фрагментации памяти связана с операцией данных, характеристиками данных и т. д., кроме того, она также связана с используемым распределителем памяти: если распределитель памяти спроектирован правильно, генерация фрагментации памяти может максимально сократиться. jemalloc, который будет обсуждаться позже, хорошо справляется с управлением фрагментацией памяти.
Если фрагментация памяти на сервере Redis уже велика, вы можете уменьшить фрагментацию памяти с помощью безопасного перезапуска: после перезапуска Redis повторно считывает данные из файла резервной копии, переупорядочивает их в памяти и повторно выбирает подходящие данные. для каждой единицы памяти данных, чтобы уменьшить фрагментацию памяти.
3. Подробная информация о хранилище данных Redis
1 Обзор
Детали хранения данных Redis включают распределители памяти (например, jemalloc), простые динамические строки (SDS), 5 типов объектов и внутреннюю кодировку, а также redisObject. Прежде чем описывать конкретное содержание, сначала объясните взаимосвязь между этими понятиями.
На следующем рисунке показана модель данных, используемая при выполнении команды set hello world.
Источник изображения: https://searchdatabase.techtarget.com.cn/7-20218/
(1) dictEntry: Redis — это база данных «ключ-значение», поэтому для каждой пары «ключ-значение» будет dictEntry, в которой хранятся указатели на ключ и значение; next указывает на следующий dictEntry, который не имеет ничего общего с этим ключом-значением. Ценность.
(2) Ключ: Как видно в правом верхнем углу рисунка, ключ («привет») не хранится напрямую в виде строки, а хранится в структуре SDS.
(3) redisObject: Value("world") не хранится напрямую в виде строки и не хранится напрямую в SDS, например Key, а сохраняется в redisObject. На самом деле, независимо от того, какой из пяти типов Value, он хранится в redisObject, поле type в redisObject указывает на тип объекта Value, а поле ptr указывает на адрес, где находится объект. Однако видно, что хотя строковый объект был упакован с помощью redisObject, его все же необходимо сохранить через SDS.
На самом деле, кроме полей type и ptr, у redisObject есть и другие поля, не показанные на рисунке, например, поля, используемые для указания внутренней кодировки объекта, она будет подробно описана позже.
(4) jemalloc: будь то объект DictEntry, объект redisObject или SDS, для выделения памяти для хранения требуется распределитель памяти (например, jemalloc). Возьмем для примера объект DictEntry, он состоит из 3-х указателей, которые на 64-битной машине занимают 24 байта, и jemalloc выделит для него 32-байтную единицу памяти.
Давайте представим jemalloc, redisObject, SDS, типы объектов и внутреннее кодирование соответственно.
2. джемаллок
Redis укажет распределитель памяти во время компиляции; распределитель памяти может быть libc, jemalloc или tcmalloc, по умолчанию — jemalloc.
Являясь распределителем памяти по умолчанию в Redis, jemalloc относительно хорошо справляется с уменьшением фрагментации памяти. В 64-битной системе jemalloc делит пространство памяти на три диапазона: малый, большой и огромный; каждый диапазон делится на множество небольших блоков памяти; когда Redis сохраняет данные, он выбирает блок памяти наиболее подходящего размера для хранения.
Блок памяти, разделенный на jemalloc, показан на следующем рисунке:
Источник изображения: http://blog.csdn.net/zhengpeitao/article/details/76573053
Например, если необходимо сохранить объект размером 130 байт, jemalloc поместит его в блок памяти размером 160 байт.
3. передисобъект
Как упоминалось ранее, существует 5 типов объектов Redis, независимо от того, какого типа Redis не будет хранить их напрямую, а будет хранить через объекты redisObject.
Объект redisObject очень важен. Тип, внутреннее кодирование, восстановление памяти, общий объект и другие функции объекта Redis нуждаются в поддержке redisObject. Ниже будет объяснено, как это работает через структуру redisObject.
Определение redisObject выглядит следующим образом (разные версии Redis могут немного отличаться):
?1 2 3 4 5 6 7 | typedef struct redisObject { unsigned type:4; unsigned encoding:4; unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */ int refcount; void *ptr; } robj; |
Значение и функция каждого поля redisObject следующие:
(1) тип
Поле типа представляет тип объекта, занимая 4 бита; в настоящее время оно включает REDIS_STRING (строка), REDIS_LIST (список), REDIS_HASH (хэш), REDIS_SET (набор) и REDIS_ZSET (упорядоченный набор).
Когда мы выполняем команду type, мы получаем тип объекта, считывая поле типа RedisObject, как показано на следующем рисунке:
(2) кодирование
кодирование представляет собой внутреннее кодирование объекта, составляющее 4 бита.
Для каждого типа, поддерживаемого Redis, существует как минимум две внутренние кодировки, например, для строк существует три кодировки: int, embstr и raw. С помощью атрибута кодирования Redis может устанавливать различные кодировки для объектов в соответствии с различными сценариями использования, что значительно повышает гибкость и эффективность Redis. Если взять в качестве примера объект списка, то существует два метода кодирования: сжатый список и двусторонний связанный список; если в списке мало элементов, Redis склонен использовать для хранения сжатый список, поскольку сжатый список занимает меньше памяти и может быть более эффективен, чем двусторонний связанный список.Быстрая загрузка: когда объект списка содержит много элементов, сжатый список будет преобразован в двусторонний связанный список, более подходящий для хранения большого количества элементов.
С помощью команды кодирования объекта вы можете просмотреть метод кодирования, используемый объектом, как показано на следующем рисунке:
Методы кодирования и условия использования, соответствующие пяти типам объектов, будут представлены позже.
(3) лру
lru записывает время последнего обращения к объекту со стороны командной программы, а количество битов, занимаемых разными версиями, разное (например, версия 4.0 занимает 24 бита, а версия 2.6 — 22 бита).
Сравнивая время lru с текущим временем, можно рассчитать время простоя объекта; команда object idletime может отображать время простоя (в секундах). Особенностью команды object idletime является то, что она не изменяет значение lru объекта.
Помимо печати через команду object idletime, значение lru также связано с восстановлением памяти Redis: если в Redis включена опция maxmemory, а алгоритм восстановления памяти — volatile-lru или allkeys-lru, то при восстановлении памяти Redis использование превышает спецификацию maxmemory Когда значение равно , Redis предпочтительно выберет объект с самым длительным временем простоя для освобождения.
(4) количество ссылок
refcountс общими объектами
refcount записывает количество ссылок на объект, а тип — целое число. Роль refcount в основном заключается в подсчете ссылок на объекты и восстановлении памяти. Когда создается новый объект, счетчик ссылок инициализируется равным 1; когда новая программа использует объект, счетчик ссылок увеличивается на 1; когда объект больше не используется новой программой, счетчик ссылок уменьшается на 1; когда счетчик ссылок становится равным 1. 0, память, занятая объектом, будет освобождена.
Объекты, которые используются в Redis несколько раз (refcount>1), называются общими объектами. В целях экономии памяти в Redis при повторном появлении некоторых объектов новая программа не будет создавать новые объекты, а по-прежнему будет использовать исходные объекты. Этот повторно используемый объект является общим объектом. В настоящее время общие объекты поддерживают только целочисленные строковые объекты.
Конкретная реализация общих объектов
Общие объекты Redis в настоящее время поддерживают только строковые объекты с целочисленным значением. Причиной этого на самом деле является баланс между памятью и ЦП (время): хотя совместное использование объектов уменьшит потребление памяти, требуется дополнительное время, чтобы определить, равны ли два объекта. Для целых значений сложность операции оценки составляет O(1), для обычных строк сложность оценки составляет O(n), а для хэшей, списков, наборов и упорядоченных наборов сложность оценки составляет O(n^2).
Хотя общие объекты могут быть только целочисленными строковыми объектами, все 5 типов могут использовать общие объекты (такие как элементы хэшей, списков и т. д.).
Что касается текущей реализации, то при инициализации сервера Redis он создаст 10 000 строковых объектов, значения которых представляют собой целочисленные значения в диапазоне от 0 до 9999; когда Redis необходимо использовать строковые объекты со значениями в диапазоне от 0 до 9999, их можно использовать напрямую. Число 10000 можно изменить, изменив значение параметра REDIS_SHARED_INTEGERS (OBJ_SHARED_INTEGERS в 4.0).
Количество ссылок на общие объекты можно просмотреть с помощью команды object refcount, как показано на следующем рисунке. Страница результатов выполнения команды подтверждает, что в качестве общих объектов будут использоваться только целые числа от 0 до 9999.
(5) птр
Указатель ptr указывает на конкретные данные, например, в предыдущем примере set hello world, ptr указывает на SDS, содержащий строку world.
(6) Резюме
Таким образом, структура redisObject связана с типом объекта, кодировкой, перезапуском памяти и общими объектами; размер объекта redisObject составляет 16 байтов:
4 бит + 4 бит + 24 бит + 4 байта + 8 байт = 16 байт.
4. Паспорт безопасности
Вместо прямого использования строк C (то есть массивов символов, оканчивающихся нулевым символом '\0') в качестве строкового представления по умолчанию, Redis использует SDS. SDS — это аббревиатура от Simple Dynamic String.
(1) Структура паспорта безопасности
Структура ССД следующая:
?1 2 3 4 5 | struct sdshdr { int len; int free ; char buf[]; }; |
Среди них buf представляет массив байтов, используемый для хранения строк, len представляет используемую длину buf, а free представляет неиспользованную длину buf. Ниже приведены два примера.
Источник изображения: «Проектирование и реализация Redis».
Из структуры SDS видно, что длина массива buf = free + len + 1 (где 1 представляет нулевой символ в конце строки), следовательно, пространство, занимаемое структурой SDS, составляет: длина занято свободным + длина, занятая len + длина массива buf=4+4+свободно+len+1=свободно+len+9.
(2) Сравнение строк SDS и C
SDS добавляет поля free и len в строки C, что дает много преимуществ:
- Получить длину строки: SDS - это O (1), строка C - это O (n)
- Переполнение буфера: при использовании API строки C, если длина строки увеличивается (например, операция strcat) и забывается перераспределить память, легко вызвать переполнение буфера; в то время как SDS записывает длину, соответствующий API может вызвать буферизацию. При переполнении области память автоматически перераспределяется, предотвращая переполнение буфера.
- Перераспределение памяти при изменении строк: для строк C, если вы хотите изменить строку, вы должны перераспределить память (сначала освободить, а затем применить), потому что, если перераспределения нет, переполнение буфера памяти произойдет при увеличении длины строки, утечка памяти произойдет при уменьшении длины строки. Для SDS, поскольку len и free могут быть записаны, связь между длиной строки и длиной массива пробелов снимается, и оптимизация может производиться на этой основе: стратегия предварительного выделения пространства (то есть при выделении больше памяти, чем требуется на самом деле) делает Когда длина строки увеличивается, вероятность перераспределения памяти значительно снижается; стратегия отложенного освобождения памяти значительно снижает вероятность перераспределения памяти при уменьшении длины строки.
- Доступ к двоичным данным: SDS может, а строки C — нет. Поскольку строка C использует нулевой символ в качестве конца строки, а для некоторых двоичных файлов (таких как изображения и т. д.) содержимое может включать пустую строку, поэтому доступ к строке C невозможен; и SDS использует длина строки len в качестве идентификатора конца строки, так что с этим проблем нет.
Кроме того, поскольку buf в SDS по-прежнему использует строки C (то есть заканчивается на '\0'), SDS может использовать некоторые функции библиотеки строк C; однако следует отметить, что только тогда, когда SDS используется для хранения текста data Это можно использовать только при хранении двоичных данных ('\0' не обязательно является концом).
(3) Применение SDS и строк C
Redis всегда использует SDS вместо строк C при хранении объектов. Например, команда set hello world, hello и world сохраняются в виде SDS. Команда sadd myset member1 member2 member3, будь то ключ («myset») или элементы в наборе («member1», «member2» и «member3»), сохраняются в виде SDS. Помимо хранения объектов, SDS также используется для хранения различных буферов.
Строки C используются только в ситуациях, когда строка не изменится, например, при печати журнала.
В-четвертых, тип объекта Redis и внутренняя кодировка.
Как было сказано ранее, Redis поддерживает 5 типов объектов, и каждая структура имеет как минимум две кодировки, преимущество этого в том, что с одной стороны интерфейс отделен от реализации, и когда внутреннюю кодировку нужно увеличить или изменить, это не повлияет на использование пользователем. С другой стороны, внутреннее кодирование можно переключать в соответствии с различными сценариями применения для повышения эффективности.
Внутренняя кодировка, поддерживаемая различными типами объектов Redis, показана на следующем рисунке (версия на рисунке — Redis3.0, а внутренняя кодировка добавлена в более позднюю версию Redis, которая опущена; внутренняя кодировка, представленная в этом глава основана на версии 3.0):
Источник изображения: «Проектирование и реализация Redis».
Что касается преобразования внутренней кодировки Redis, то оно соответствует следующим правилам:преобразование кода вRedisОн завершается, когда данные записываются, и процесс преобразования необратим, он может быть преобразован только из кодирования с небольшой памятью в кодирование с большой памятью.
1. Строка
(1) Обзор
Строки являются самым основным типом, поскольку все ключи являются строками, а элементы некоторых других сложных типов, отличных от строк, также являются строками.
Длина строки не может превышать 512 МБ.
(2) Внутреннее кодирование
Существует три типа внутренних кодировок для строковых типов, и сценарии их применения следующие:
- int: 8-байтовое длинное целое. Когда строковое значение является целым числом, значение представляется длинным целым числом.
- embstr:
- raw: строка больше 39 байт
Пример показан ниже:
Длина различия между embstr и raw равна 39; это связано с тем, что длина redisObject составляет 16 байт, а длина sds составляет 9 + длина строки; поэтому, когда длина строки равна 39, длина embstr составляет ровно 16+. 9+39 = 64, jemalloc может выделить блок памяти размером ровно 64 байта.
(3) Преобразование кода
Когда данные int больше не являются целыми или их размер превышает диапазон значений long, они автоматически преобразуются в необработанные.
Для embstr, поскольку его реализация доступна только для чтения, при изменении объекта embstr он будет преобразован в необработанный, а затем изменен.Поэтому, пока объект embstr изменяется, измененный объект должен быть необработанным, независимо от того, достигает 39 байт. Пример показан ниже:
2. Список
(1) Обзор
Списки используются для хранения нескольких упорядоченных строк, и каждая строка называется элементом; список может хранить 2^32-1 элементов. Списки в Redis поддерживают вставку и извлечение с обоих концов и могут получать элементы в указанных позициях (или диапазонах), которые могут выступать в качестве массивов, очередей, стеков и т. д.
(2) Внутреннее кодирование
Внутренняя кодировка списка может быть ziplist или связанным списком.
Двусторонний связанный список: он состоит из структуры списка и нескольких структур listNode; типичная структура показана на следующем рисунке:
Источник изображения: «Проектирование и реализация Redis».
Как видно из рисунка, двусторонний связанный список сохраняет указатель заголовка и указатель хвоста одновременно, и каждый узел имеет указатель на начало и конец; длина списка сохраняется в связанном списке. list; dup, free и match — это узлы, определяющие наборы значений для конкретных типов функций, поэтому связанные списки можно использовать для хранения значений различных типов. Каждый узел в связанном списке указывает на redisObject, тип которого — строка.
Сжатый список: сжатый список разработан Redis для экономии памяти и состоит из ряда специальных кодировок.непрерывный блок памяти(а не двусторонний связанный список, каждый узел является указателем) последовательная структура данных, конкретная структура относительно сложна, опущена. По сравнению с двусторонним связным списком сжатый список позволяет экономить место в памяти, но сложность выше при изменении, добавлении или удалении операций, поэтому при небольшом числе узлов можно использовать сжатый список; количество узлов велико, двусторонний связанный список по-прежнему используется экономически эффективно.
Сжатые списки используются не только для реализации списков, но и для реализации хэшей, упорядоченных списков, они очень широко используются.
(3) Преобразование кода
Сжатые списки используются только при соблюдении обоих следующих условий: количество элементов в списке меньше 512 и размер всех строковых объектов в списке меньше 64 байт. Если одно из условий не выполняется, используется двусторонний список, при этом кодировка может быть преобразована только из сжатого списка в двусторонний связанный список, а обратное невозможно.
На следующем рисунке показаны характеристики преобразования кодировки списка:
Среди них одна строка не может превышать 64 байта, чтобы облегчить равномерное распределение длины каждого узла; 64 байта здесь относятся к длине строки, исключая структуру SDS, поскольку сжатый список хранится в непрерывном, блоки памяти фиксированной длины Строка, для указания длины не требуется структура SDS. Список сжатия упоминается позже, а также будет подчеркнуто, что длина не превышает 64 байта, здесь принцип аналогичен.
3. Хэш
(1) Обзор
Хэш (как структура данных) — это не только один из пяти типов объектов, предоставляемых Redis (параллельно со строками, списками, наборами и упорядоченными комбинациями), но и структура данных, используемая Redis в качестве базы данных Key-Value. Для удобства описания, когда «внутренний хэш» используется далее в этой статье, он представляет один из пяти типов объектов, предоставляемых Redis; использование «внешнего хэша» относится к Redis как к базе данных «ключ-значение». использовал.
(2) Внутреннее кодирование
Внутренняя кодировка, используемая внутренним хэшем, может быть сжатым списком (ziplist) и хеш-таблицей (хэш-таблица); внешний хэш Redis использует только хеш-таблицу.
Списки сжатия были описаны ранее. По сравнению с хеш-таблицей сжатый список используется в сценариях с небольшим количеством элементов и небольшой длиной элемента, его преимущество заключается в централизованном хранении и экономии места, в то же время, хотя сложность операций с элементами также изменилась с От O(n) до O(1), но поскольку количество элементов в хэше невелико, нет существенного недостатка во времени операции.
hashtable: хэш-таблица состоит из 1 структуры dict, 2 структур dicttht, 1 массива указателей dictEntry (называемого ведром) и нескольких структур dictEntry.
При нормальных обстоятельствах (т. е. когда хэш-таблица не подвергается повторному хешированию) взаимосвязь между каждой частью показана на следующем рисунке:
Изображение адаптировано из: «Проектирование и реализация Redis».
Далее описывается каждая часть по порядку снизу вверх:
dictEntry
Структура dictEntry используется для хранения пар ключ-значение и определяется следующим образом:
?1 2 3 4 5 6 7 8 9 | typedef struct dictEntry{ void *key; union { void *val; uint64_tu64; int64_ts64; }v; struct dictEntry *next; }dictEntry; |
Среди них функции каждого атрибута следующие:
- ключ: ключ в паре ключ-значение;
- val: значение в паре ключ-значение, реализованное с использованием объединения (т.е. объединения), сохраненное содержимое может быть указателем на значение, 64-битным целым числом или 64-битным целым числом без знака;
- next: указывает на следующую запись dictEntry, используемую для разрешения коллизий хешей.
В 64-битной системе объект dictEntry занимает 24 байта (key/val/next занимают по 8 байт).
bucket
Bucket — это массив, каждый элемент которого является указателем на структуру dictEntry. Правило расчета размера массива бакетов в redis следующее: если dictEntry больше, то наименьшее 2^n; например, если dictEntry 1000, размер бакета равен 1024; если dictEntry 1500, размер ковша 2048.
dictht
Структура словаря выглядит следующим образом:
?1 2 3 4 5 6 | typedef struct dictht{ dictEntry **table; unsigned long size; unsigned long sizemask; unsigned long used; }dictht; |
Среди них описание функции каждого атрибута выглядит следующим образом:
- Атрибут таблицы — это указатель на корзину;
- Атрибут size записывает размер хеш-таблицы, то есть размер ведра;
- used записывает количество использованных dictEntry;
- Значение атрибута sizemask всегда равно size-1, что вместе с хеш-значением определяет, где в таблице хранится ключ.
dict
Вообще говоря, с помощью структур dicttht и dictEntry можно реализовать функцию обычной хеш-таблицы, однако в реализации Redis над структурой dicttht находится структура dict. Ниже описаны определение и функции структуры dict.
Структура словаря следующая:
?1 2 3 4 5 6 | typedef struct dict{ dictType *type; void *privdata; dictht ht[2]; int trehashidx; } dict; |
Среди них атрибут type и атрибут privdata используются для адаптации к различным типам пар ключ-значение для создания полиморфных словарей.
Атрибут ht и атрибут trehashidx используются для повторного хеширования, то есть когда хеш-таблицу необходимо расширить или сжать. ht — это массив, содержащий два элемента, каждый из которых указывает на структуру dict, поэтому хэш Redis имеет 1 структуру dict и 2 структуры dicttht. Обычно все данные хранятся в ht[0] dict, а ht[1] используется только при перефразировании. Когда dict выполняет операцию повторного хеширования, перефразирует все данные из ht[0] в ht[1]. Затем присвойте ht[1] ht[0] и очистите ht[1].
Следовательно, причина, по которой хеш в Redis имеет структуру dict в дополнение к структурам dicttht и dictEntry, заключается в том, чтобы адаптироваться к разным типам пар ключ-значение, с одной стороны, и перефразировать, с другой стороны.
(3) Преобразование кода
Как упоминалось ранее, внутренний хэш в Redis может использовать либо хеш-таблицу, либо сжатый список.
Сжатые списки используются только при выполнении обоих следующих условий: количество элементов в хэше меньше 512; длина строк ключа и значения всех пар ключ-значение в хеше меньше 64 байт. Если одно из условий не выполняется, то используется хеш-таблица, при этом кодировка может быть преобразована только из сжатого списка в хеш-таблицу, а обратное невозможно.
На следующем рисунке показаны характеристики преобразования хеш-кодирования во внутреннем слое Redis:
4. Коллекция
(1) Обзор
Наборы похожи на списки тем, что они используются для хранения нескольких строк, но наборы отличаются от списков двумя способами: элементы в наборах неупорядочены, поэтому с элементами нельзя манипулировать с помощью индекса; элементы в наборах не могут повторяться.
В наборе может храниться до 2^32-1 элементов; помимо поддержки обычных добавлений, удалений и изменений, Redis также поддерживает пересечение, объединение и различие нескольких наборов.
(2) Внутреннее кодирование
Внутренняя кодировка набора может быть набором целых чисел (intset) или хеш-таблицей (hashtable).
Хеш-таблица упоминалась ранее, поэтому я не буду упоминать ее здесь; следует отметить, что когда набор использует хэш-таблицу, все значения устанавливаются равными нулю.
Структура набора целых чисел определяется следующим образом:
?1 2 3 4 5 | typedef struct intset{ uint32_t encoding; uint32_t length; int8_t contents[]; } intset; |
Среди них кодировка представляет тип содержимого, хранящегося в содержимом.Хотя содержимое (элементы в наборе хранения) имеет тип int8_t, фактическое сохраняемое значение — int16_t, int32_t или int64_t, а конкретный тип определяется кодировкой; длина представляет число элементов.
Целочисленный набор подходит для случаев, когда все элементы набора являются целыми числами и количество элементов набора невелико.По сравнению с хеш-таблицей преимущество целочисленного набора заключается в централизованном хранении и экономии места, в то же время, хотя сложность операций с элементами также уменьшается на O(n) становится O(1), но, поскольку количество наборов невелико, время работы существенно не ухудшается.
(3) Преобразование кода
Набор будет использовать целочисленный набор, только если выполняются оба следующих условия: количество элементов в наборе меньше 512 и все элементы в наборе являются целыми значениями. Если одно из условий не выполняется, используется хеш-таблица, при этом кодирование может быть преобразовано только из набора целых чисел в хэш-таблицу, а обратное невозможно.
На следующем рисунке показаны характеристики преобразования набора кодировок:
5. Заказанный сбор
(1) Обзор
Отсортированные наборы, как и наборы, не могут повторять элементы, но в отличие от наборов элементы в отсортированных наборах упорядочены. В отличие от списков, которые используют индексные индексы в качестве критериев сортировки, отсортированные наборы устанавливают оценку для каждого элемента в качестве критерия сортировки.
(2) Внутреннее кодирование
Внутренняя кодировка упорядоченного набора может быть ziplist или skiplist. ziplist используется как в списках, так и в хешах, что уже упоминалось ранее и не будет упоминаться здесь.
Таблица пропуска — это упорядоченная структура данных, которая может быстро обращаться к узлам, поддерживая несколько указателей на другие узлы в каждом узле. Помимо таблицы переходов, другой типичной реализацией упорядоченной структуры данных является сбалансированное дерево; в большинстве случаев эффективность таблицы переходов сравнима с эффективностью сбалансированного дерева, а реализация таблицы переходов намного проще. чем сбалансированное дерево, поэтому в Redis используется таблица переходов вместо сбалансированного дерева. Таблица пропуска поддерживает средний O(logN), наихудший O(N) поиск узлов для комплексных точек и поддерживает последовательные операции. Реализация таблицы пропуска Redis состоит из двух структур: zskiplist и zskiplistNode: первая используется для сохранения информации таблицы пропуска (например, головной узел, хвостовой узел, длина и т. д.), а вторая используется для представления таблицы пропуска. узел. Конкретная структура относительно сложна.
(3) Преобразование кода
Сжатые списки используются только при выполнении обоих следующих условий: количество элементов в отсортированном наборе меньше 128, длина всех элементов в отсортированном наборе меньше 64 байт. Если одно из условий не выполняется, используется таблица пропуска, причем преобразование кодировки возможно только из сжатого списка в таблицу пропуска, обратное невозможно.
На следующем рисунке показаны характеристики преобразования кодировки упорядоченного набора:
5. Примеры применения
После понимания модели памяти Redis следующие примеры иллюстрируют ее применение.
1. Оцените использование памяти Redis
Чтобы оценить размер памяти, занимаемой данными в Redis, необходимо иметь всестороннее представление о модели памяти Redis, включая хеш-таблицу, sds, redisobject и методы кодирования различных типов объектов, представленные ранее.
Ниже приводится объяснение простейшего строкового типа.
Предположим, что имеется 90 000 пар ключ-значение, длина каждого ключа 7 байт, и длина каждого значения тоже 7 байт (и ни ключ, ни значение не являются целыми числами); оценим занимаемое этими 90 000 ключ- пары значений Пробел. Перед оценкой занимаемого пространства вы можете сначала определить метод кодирования, используемый строковым типом: embstr.
Пространство памяти, занимаемое 90 000 пар ключ-значение, можно разделить на две части: одна часть — это пространство, занимаемое 90 000 dictEntry, а другая часть — пространство, необходимое для пар ключ-значение.
Пространство, занимаемое каждым dictEntry, включает:
1) dictEntry, 24 байта, jemalloc выделит 32-байтовый блок памяти
2) Ключ, 7 байт, поэтому SDS(ключу) требуется 7+9=16 байт, jemalloc выделит 16-байтовый блок памяти.
3) RedisObject, 16 байт, jemalloc выделит 16-байтовый блок памяти
4) Значение, 7 байт, поэтому SDS(значение) требует 7+9=16 байт, jemalloc выделит 16-байтовый блок памяти.
5) Таким образом, для dictEntry требуется 32+16+16+16=80 байт.
Пространство сегмента: размер массива сегментов равен наименьшему числу 2^n больше 90000, что равно 131072; каждый элемент сегмента равен 8 байтам (поскольку размер указателя составляет 8 байтов в 64-разрядных системах).
Таким образом, можно оценить, что объем памяти, занимаемый этими 90 000 пар ключ-значение, составляет: 90 000*80 + 131072*8 = 8248576.
Давайте напишем программу для проверки в Redis:
?1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public class RedisTest { public static Jedis jedis = new Jedis( "localhost" , 6379); public static void main(String[] args) throws Exception{ Long m1 = Long.valueOf(getMemory()); insertData(); Long m2 = Long.valueOf(getMemory()); System.out.println(m2 - m1); } public static void insertData(){ for ( int i = 10000; i < 100000; i++){ jedis.set( "aa" + i, "aa" + i); //key和value长度都是7字节,且不是整数 } } public static String getMemory(){ String memoryAllLine = jedis.info( "memory" ); String usedMemoryLine = memoryAllLine.split( "\r\n" )[1]; String memory = usedMemoryLine.substring(usedMemoryLine.indexOf( ':' ) + 1); return memory; } } |
Результат выполнения: 8247552
Ошибка между теоретическим значением и значением результата составляет 1,2/10 000, что достаточно для расчета требуемого объема памяти. Причина ошибки в том, что Redis выделил определенный объем пространства в корзине до того, как мы вставили 90 000 фрагментов данных, и эти пространства в корзине не использовались.
Для сравнения, если длина ключа и значения увеличивается с 7 байтов до 8 байтов, соответствующий SDS становится 17 байтом, а jemalloc выделяет 32 байта, поэтому количество байтов, занимаемых каждым dictEntry, также составляет 80 байтов. 112 байт. В настоящее время предполагаемый размер памяти для этих 90 000 пар ключ-значение составляет: 90 000*112 + 131072*8 = 11128576.
Код проверки в redis выглядит следующим образом (модифицируется только код, который вставляет данные):
?1 2 3 4 5 | public static void insertData(){ for ( int i = 10000; i < 100000; i++){ jedis.set( "aaa" + i, "aaa" + i); //key和value长度都是8字节,且不是整数 } } |
Текущий результат: 11128576; оценка точная.
Для типов, отличных от строковых, метод оценки использования памяти аналогичен, и его необходимо определять в сочетании с методом кодирования конкретного типа.
2. Оптимизируйте использование памяти
Понимание модели памяти Redis очень полезно для оптимизации использования памяти Redis. Несколько сценариев оптимизации описаны ниже.
(1) Используйте функцию jemalloc для оптимизации
90 000 ключевых значений, описанных в предыдущем разделе, являются примером. Поскольку значение прерывается, когда jemalloc выделяет память, изменение одного байта в строке ключ/значение может привести к значительному изменению использования памяти; это можно использовать при проектировании.
Например, если длина ключа составляет 8 байт, SDS — 17 байт, а jemalloc выделяет 32 байта, в это время, если длина ключа уменьшается до 7 байт, SDS — 16 байт, а jemalloc выделяет 16 байт. ., пространство, занимаемое каждой клавишей, можно уменьшить вдвое.
(2) Используйте целое/длинное целое число
Если это целое/длинное целое, Redis будет использовать хранилище типа int (8 байт) вместо строк, что может сэкономить больше места. Поэтому в сценариях, где вместо строк можно использовать длинные целые/целые числа, попробуйте использовать длинные целые/целые числа.
(3) Общие объекты
Использование общих объектов может сократить количество создаваемых объектов (и уменьшить количество объектов redisObject) и сэкономить место в памяти. В настоящее время общие объекты в Redis включают только 10000 целых чисел (0-9999); количество общих объектов можно увеличить, настроив параметр REDIS_SHARED_INTEGERS; например, если REDIS_SHARED_INTEGERS настроен на 20000, объекты между 0 и 19999 могут быть общими .
Рассмотрим такой сценарий: сайт форума хранит количество просмотров каждого сообщения в redis, и большинство этих просмотров распределяется между 0 и 20 000. В это время, соответствующим образом увеличив параметр REDIS_SHARED_INTEGERS, вы можете использовать общий объект для сохранения пространство памяти.
(4) Избегайте чрезмерного проектирования
Однако следует отметить, что независимо от того, какой сценарий оптимизации используется, необходимо учитывать компромисс между объемом памяти и сложностью проекта, а сложность проекта повлияет на сложность и удобство сопровождения кода.
Если объем данных невелик, нерентабельно усложнять разработку и обслуживание кода для экономии памяти; или возьмите в качестве примера 90 000 пар ключ-значение, упомянутых выше, реально сэкономленное пространство памяти составляет всего несколько МБ. Однако если объем данных составляет десятки миллионов или даже сотни миллионов, необходимо учитывать оптимизацию памяти.
3. Обратите внимание на скорость фрагментации памяти
Скорость фрагментации памяти — важный параметр, который имеет большое значение для оптимизации памяти Redis.
Если уровень фрагментации памяти слишком высок (нормальный jemalloc составляет около 1,03), это означает, что существует много фрагментов памяти и серьезные потери памяти; в это время вы можете рассмотреть возможность перезапуска службы redis, чтобы переупорядочить данные в памяти, чтобы уменьшить объем памяти. фрагментация.
Если скорость фрагментации памяти меньше 1, значит, у redis недостаточно памяти, а часть данных использует виртуальную память (то есть подкачку), так как скорость доступа к виртуальной памяти намного хуже, чем к физической памяти (2-3 порядков), скорость доступа Redis может стать очень низкой. Следовательно, вы должны попытаться увеличить физическую память (вы можете увеличить количество серверных узлов или увеличить память одной машины) или уменьшить данные в Redis.
Для сокращения данных в redis, помимо выбора подходящих типов данных, использования общих объектов и т.д., необходимо также установить разумную политику восстановления данных (maxmemory-policy) для повторного использования.
6. Ссылки
«Разработка, эксплуатация и обслуживание Redis»
«Проектирование и реализация Redis»
https://redis.io/documentation
http://redisdoc.com/server/info.html
https://www.cnblogs.com/lhcpig/p/4769397.html
https://searchdatabase.techtarget.com.cn/7-20218/
http://www.cnblogs.com/mushroom/p/4738170.html
http://www.imooc.com/article/3645
http://blog.csdn.net/zhengpeitao/article/details/76573053
Оригинальность не так проста, если вы считаете, что статья вам полезна,Лайки и комментарии приветствуются. В статье есть упущения, критика и исправления приветствуются.
Добро пожаловать на перепечатку, пожалуйста, укажите оригинальную ссылку:блог woo woo woo.cn на.com/kismet V/afraid/8…