1. Введение и применение
Redis — это база данных K-K в памяти, написанная на языке ANSI C, с отличной производительностью, сетевой поддержкой и устойчивостью, а также предоставляющая API на нескольких языках. Его обычно используемые типы - это в основном String, List, Hash, Set и ZSet.
Redis обычно имеет следующие приложения в интернет-компаниях:Строка: Кэш, Текущий лимит, Счетчик, Распределенная блокировка, Распределенная сессия
Хэш: хранить информацию о пользователе, посещения домашней страницы пользователя, комбинированный запрос
Список: Хронология списка подписчиков Weibo, простая очередь
Набор: Нравится, Не нравится, Тег, Дружба
Zset: Таблица лидеров
В другом примере, когда в электронной коммерции проводится большое продвижение, для обеспечения стабильности системы будут использоваться некоторые специальные схемы.Следующие схемы могут быть рассмотрены для вычета запасов:
На приведенном выше рисунке инвентаризация вычитается непосредственно в Redis, а журнал синхронизируется с базой данных через воркер.При проектировании воркера синхронизации необходимо учитывать вопросы параллельной обработки и повторной обработки.Из приведенных выше сценариев приложений видно, что Redis очень эффективен и стабилен.Как реализован нижний уровень Redis?
2. Объект Redis redisObject
Когда мы выполняем команду set hello world, мы получаем следующую модель данных:
dictEntry: Redis присваивает dictEntry каждой паре ключ-значение, которая имеет указатели на ключ и значение, а next указывает на следующий dictEntry для формирования связанного списка.Этот указатель может связать несколько пар ключ-значение с одним и тем же хешем. значение вместе.Это решает проблему коллизии хэшей (метод цепных адресов).sds: Ключ "hello" хранится в SDS (Simple Dynamic String), который будет подробно описан позже.
redisObject: значение val "world" сохраняется в файле redisObject. На самом деле обычно используемые redis 5 типов хранятся в redisObject, поле type в redisObject указывает на тип объекта Value, а поле ptr указывает на адрес, где находится объект.
Объект redisObject очень важен.Тип, внутреннее кодирование, восстановление памяти, общий объект и другие функции объекта Redis требуют поддержки redisObject. Преимущество этой схемы заключается в том, что для различных сценариев использования можно задать множество различных реализаций структур данных для пяти наиболее часто используемых типов, тем самым оптимизируя эффективность использования объектов в различных сценариях.
Будь то объект dictEntry, объект redisObject или SDS, для выделения памяти для хранения требуется распределитель памяти (например, jemalloc). Являясь распределителем памяти по умолчанию в Redis, jemalloc относительно хорошо справляется с уменьшением фрагментации памяти. Например, в 64-битной системе jemalloc делит пространство памяти на три диапазона: малый, большой и огромный; каждый диапазон делится на множество небольших блоков памяти; когда Redis сохраняет данные, он выбирает блок памяти наиболее подходящего размера. хранить.
Как упоминалось ранее, каждый объект Redis представлен структурой redisObject, а его указатель ptr указывает на структуру данных базовой реализации, а структура данных определяется атрибутом кодирования. Например, мы выполняем следующую команду, чтобы получить кодировку, соответствующую хранилищу «hello»:
Все типы структур данных Redis следующие: 3. СтрокаБазовая реализация строкового объекта может быть int, raw, embstr (приведенная выше таблица соответствует введению имени). Кодировка embstr выделяет непрерывное пространство, вызывая функцию выделения памяти один раз, в то время как raw необходимо вызывать дважды.
Строковые объекты с кодировкой Int и строковые объекты с кодировкой embstr будут преобразованы в необработанные строковые объекты с кодировкой при определенных условиях. embstr:Простая динамическая строка (SDS), эта структура больше похожа на C++ String или Java ArrayList, а ее длина динамически изменяется:
struct sdshdr {
// buf 中已占用空间的长度
int len;
// buf 中剩余可用空间的长度
int free;
// 数据空间
char buf[]; // ’\0’空字符结尾
};
получить: sdsrange---O(n) установить: sdscpy — O (n) создать: sdsnew---O(1) len: sdslen---O(1)
Постоянная сложность для получения длины строки: поскольку SDS записывает длину в атрибуте len, временная сложность получения длины SDS составляет всего O(1).
Распределение предварительного пространства: если SDS изменен, его можно разделить на следующие две ситуации:
1. Если длина SDS (значение len) меньше 1 МБ, программа выделит неиспользуемое пространство того же размера, что и атрибут len.В это время значения атрибута free и len совпадают. Например, len SDS станет равным 15 байтам, программа также выделит 15 байт неиспользуемого пространства, а фактическая длина массива буферов SDS станет 15+15+1=31 байт (дополнительный байт). сохраняет нулевые символы).
2. Если длина SDS (значение len) больше или равна 1 МБ, программа выделит 1 МБ неиспользуемого пространства. Например, после модификации len SDS становится равным 30 МБ, тогда его фактическая длина составляет 30 МБ + 1 МБ + 1 байт.
Ленивое свободное пространство: после выполнения sdstrim (перехвата строки) SDS не освобождает лишнее пространство немедленно. пригодиться. Операции перераспределения памяти, которые манипулируют строками, в некоторых случаях избегаются за счет ленивого освобождения места.
Избегайте переполнения буфера: при использовании строковых операций C, если длина строки увеличивается (например, операция strcat) и забывают перераспределить память, легко вызвать переполнение буфера; а SDS записывает длину, соответствующая операция может привести к переполнению буфера. , память будет автоматически перераспределена, что предотвратит переполнение буфера.
4. Список
Базовой реализацией объекта List является quicklist (быстрый список, представляющий собой комбинацию сжатого списка ziplist и двустороннего связанного списка linkedlist). Списки в Redis поддерживают вставку и извлечение с обоих концов и могут получать элементы в указанных позициях (или диапазонах), которые могут выступать в качестве массивов, очередей, стеков и т. д.
typedef struct listNode {
// 前置节点
struct listNode *prev;
// 后置节点
struct listNode *next;
// 节点的值
void *value;
} listNode;
typedef struct list {
// 表头节点
listNode *head;
// 表尾节点
listNode *tail;
// 节点值复制函数
void *(*dup)(void *ptr);
// 节点值释放函数
void (*free)(void *ptr);
// 节点值对比函数
int (*match)(void *ptr, void *key);
// 链表所包含的节点数量
unsigned long len;
} list;
rpush: listAddNodeHead ---O(1) lpush: listAddNodeTail ---O(1) push: listInsertNode ---O(1) index : listIndex ---O(N) pop: ListFirst/listLast ---O(1) llen: listLength ---O(N)
4.1 связанный список (двусторонний связанный список)
Эта структура похожа на LinkedList в Java.Если вам интересно, вы можете прочитать исходный код.
Из рисунка видно, что двусторонний связанный список Redis имеет следующие характеристики: узел имеет указатель prev, next, указатель head и указатель tail, а также сложность получения pre-node, post-node, head node. и хвостовой узел - это O (1). Количество узлов, полученных свойством len, также равно O(1).По сравнению с двусторонним связным списком сжатый список позволяет экономить место в памяти, но сложность выше при изменении, добавлении или удалении операций, поэтому при небольшом числе узлов можно использовать сжатый список; количество узлов велико, двусторонний связанный список по-прежнему используется экономически эффективно.
4.2 ziplist (сжатый список)
Когда ключ списка содержит лишь небольшое количество элементов списка и представляет собой небольшое целочисленное значение или строку относительно небольшой длины, тогда Redis использует ziplist (сжатый список) в качестве базовой реализации ключа списка.
ziplist разработан Redis для экономии памяти.Это последовательная структура данных, состоящая из ряда специально закодированных смежных блоков памяти (вместо того, чтобы каждый узел был указателем, как двусторонний связанный список);специфическая структура относительно сложна, интересна читатели См. Анализ модели памяти хеш-структуры Redis.В новой версии в списке используется quicklist вместо ziplist и linkedlist: quickList представляет собой гибрид zipList и linkedList. Он делит linkedList на сегменты, каждый сегмент использует zipList для компактного хранения, а несколько zipList объединяются с помощью двусторонних указателей. Поскольку дополнительное пространство связанного списка относительно велико, указатели prev и next будут занимать 16 байт (указатель 64-битной системы — 8 байт), а память каждого узла выделяется отдельно, что усугубит фрагментацию списка. memory., что влияет на эффективность управления памятью. Глубина сжатия по умолчанию для быстрого списка равна 0, что означает отсутствие сжатия. Для поддержки быстрых операций push/pop первый и последний два zip-списка быстрого списка не сжимаются, и в настоящее время глубина равна 1. Чтобы еще больше сэкономить место, Redis также сжимает ziplist и использует для сжатия алгоритм LZF.5. Хэш
Базовой реализацией объекта Hash может быть ziplist (сжатый список) или хеш-таблица (словарь или также называемая хеш-таблицей).
Ziplist (сжатый список) будет использоваться только в том случае, если объект Hash удовлетворяет следующим двум условиям: 1. Количество элементов в хэше меньше 512. 2. Длина строк ключа и значения всех пар ключ-значение. в хеше меньше 64 байт.Хеш-таблица с хеш-таблицей может выполнять операции чтения и записи O(1), поэтому она очень эффективна. Исходный код выглядит следующим образом:
typedef struct dict {
// 类型特定函数
dictType *type;
// 私有数据
void *privdata;
// 哈希表
dictht ht[2];
// rehash 索引
// 当 rehash 不在进行时,值为 -1
int rehashidx; /* rehashing not in progress if rehashidx == -1 */
// 目前正在运行的安全迭代器的数量
int iterators; /* number of iterators currently running */
} dict;
typedef struct dictht {
// 哈希表数组
dictEntry **table;
// 哈希表大小
unsigned long size;
// 哈希表大小掩码,用于计算索引值
// 总是等于 size - 1
unsigned long sizemask;
// 该哈希表已有节点的数量
unsigned long used;
} dictht;
typedef struct dictEntry {
void *key;
union {void *val;uint64_t u64;int64_t s64;} v;
// 指向下个哈希表节点,形成链表
struct dictEntry *next;
} dictEntry;
typedef struct dictType {
// 计算哈希值的函数
unsigned int (*hashFunction)(const void *key);
// 复制键的函数
void *(*keyDup)(void *privdata, const void *key);
// 复制值的函数
void *(*valDup)(void *privdata, const void *obj);
// 对比键的函数
int (*keyCompare)(void *privdata, const void *key1, const void *key2);
// 销毁键的函数
void (*keyDestructor)(void *privdata, void *key);
// 销毁值的函数
void (*valDestructor)(void *privdata, void *obj);
} dictType;
Приведенный выше исходный код можно упростить до следующей структуры:
Эта структура похожа на HashMapЕсли словарь в Redis использует хэш-таблицу в качестве базовой реализации, каждый словарь будет иметь две хэш-таблицы, одну для обычного использования, а другую только для повторного хэширования (повторного хеширования). По мере манипулирования хэш-таблицей ключи постепенно увеличиваются или уменьшаются. Чтобы удерживать коэффициент загрузки хеш-таблицы в разумных пределах, Redis будет увеличивать или уменьшать размер хэш-таблицы (перехешировать), то есть разделять все значения ключа в ht[0] на несколько, прогрессивно Перефразировка ht[1].
6. Установить
Базовой реализацией объектов коллекции Set может быть intset (коллекция целых чисел) или хеш-таблица (словарь или также называемая хэш-таблицей).
intset (набор целых чисел) Когда набор содержит только целые числа и элементов немного, intset (набор целых чисел) используется в качестве базовой реализации объекта коллекции Set.typedef struct intset {
// 编码方式
uint32_t encoding;
// 集合包含的元素数量
uint32_t length;
// 保存元素的数组
int8_t contents[];
} intset;
sadd: intsetAdd---O(1) smembers: intsetGetO(1) --- O(N) srem: intsetRemove --- O(N) slen: intsetlen ---O(1) Базовая реализация intset — это упорядоченный неповторяющийся массив, содержащий элементы коллекции. Тип целочисленного массива в структуре intset может быть 16-битным, 32-битным или 64-битным. Если все целые числа в массиве имеют длину 16 бит, при добавлении нового 32-битного целого числа весь 16-битный массив будет обновлен до 32-битного массива. Обновление может улучшить гибкость intset и сэкономить память, но это необратимо.
7.ZSet
Базовая реализация объектов упорядоченного набора ZSet может быть ziplist (сжатый список) или skiplist (список пропуска).
Когда количество элементов в упорядоченном наборе относительно велико или члены представляют собой относительно длинные строки, Redis использует список пропуска (список пропуска) в качестве базовой реализации объекта ZSet.typedef struct zskiplist {
// 表头节点和表尾节点
struct zskiplistNode *header, *tail;
// 表中节点的数量
unsigned long length;
// 表中层数最大的节点的层数
int level;
} zskiplist;
typedef struct zskiplistNode {
// 成员对象
robj *obj;
// 分值
double score;
// 后退指针
struct zskiplistNode *backward;
// 层
struct zskiplistLevel {
// 前进指针
struct zskiplistNode *forward;
// 跨度---前进指针所指向节点与当前节点的距离
unsigned int span;
} level[];
} zskiplistNode;
zadd---zslinsert--- среднее O(logN), наихудшее O(N) zrem---zsldelete--- среднее O(logN), худшее O(N) zrank--zslGetRank--- среднее O(logN), худшее O(N)
Сложность времени поиска в списке пропусков составляет LogN, что сравнимо со сбалансированным двоичным деревом, но проще в реализации. Список пропусков — это упорядоченная структура данных, которая поддерживает несколько указателей на другие узлы в узле, чтобы обеспечить быстрый доступ к узлам.