Рубрики
Uncategorized

Прочитайте исходный код – Redis 8 – Словарь объектного кодирования с помощью Dabin

Автор оригинала: David Wong.

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

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

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

1. Внедрение словаря

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

1.1 Хэш-таблица

Структура хэш-таблицы, используемая в словаре Redis:

typedef struct dictht {
    DictEntry** table; //hash table array
    Unsigned long size; //hash table size
    Unsigned long sizemask; //hash table size mask for index calculation
    Unsigned long used; //hash represents the number of nodes
} dictht;
  • Атрибут таблицы представляет собой массив. Каждый элемент в массиве является указателем на структуру записи dict, и каждая структура записи dict содержит пару ключ-значение.
  • Атрибут size записывает размер хэш-таблицы, который является размером массива таблиц.
  • Атрибут use записывает количество существующих узлов (пар ключ-значение) в хэш-таблице.
  • Общее значение атрибута маски размера равно размеру-1, который вместе со значением хэша определяет, какой индекс ключа должен быть помещен в массив таблиц.

На рисунке 1 показана пустая хэш-таблица размером 4.

1.2 Узел Хэш-Таблицы

Узлы хэш-таблицы представлены структурой диктанта, и каждая структура записи диктанта содержит пару ключ-значение:

typedef struct dictEntry {
    Void * key; // key
    union {
        Pointer to void * val; // value type
        Unsigned Integers of uint64_t u64; //Value Types
        In64_t s64; // Signed Integers of Value Types
        Floating Point Type of Double d; //Value Type
    } v;//value
    Strct dictEntry * next; // points to the next hash table node to form a linked list
} dictEntry;
  • Атрибут key содержит ключ, в то время как атрибут V содержит значение.
  • Следующий атрибут-это указатель на другой узел хэш-таблицы. Этот указатель может соединять несколько пар значений ключей с одним и тем же значением хэша, чтобы решить проблему конфликта ключей.

На рисунке 2 показано, как два ключа K1 и K0 с одинаковым индексом соединяются вместе с помощью следующего указателя.

1.3 словарь

Структура словаря:

typedef struct dict {
    DictType*type;//type-specific function
    Void * privdata; // private data
    Dictht HT [2]; //hash table (two)
    Long rehashidx; // Logging rehash progress. A value of - 1 indicates that rehash has not proceeded
    Int iterators; // Number of iterators currently iterating
} dict;

Структура типа dict выглядит следующим образом:

typedef struct dictType {
    // Functions for calculating hash values
    unsigned int (*hashFunction)(const void *key);
    // Functions of duplicate keys
    void *(*keyDup)(void *privdata, const void *key);
    // Functions of duplicate values
    void *(*valDup)(void *privdata, const void *obj);
    // Functions of Contrast Bonds
    int (*keyCompare)(void *privdata, const void *key1, const void *key2);
    // Functions for Destroying Keys
    void (*keyDestructor)(void *privdata, void *key);
    // Function of Destruction Value
    void (*valDestructor)(void *privdata, void *obj);
} dictType;

Атрибут type и атрибут private заданы для создания полиморфных словарей для различных типов пар ключ-значение. Среди них:

  • Атрибут type является указателем на структуру типа dict, и каждая структура типа канала содержит кластер функций для управления определенным типом пары ключ-значение. Redis устанавливает различные типы конкретных функций для словарей, которые не используются для использования.
  • Атрибут private содержит необязательные параметры, которые необходимо передать этим типам конкретных функций.

Атрибут HT представляет собой массив, содержащий две хэш-таблицы. Как правило, словари используют только HT [0], а HT [1] используется только при повторном хэшировании HT [0].

Атрибут rehashidx, который записывает текущий ход выполнения повторной обработки, имеет значение – 1, если повторная обработка в данный момент не выполняется. Что касается того, что такое пересказ, не волнуйтесь. Это будет подробно объяснено позже.

На рисунке 3 показан словарь без перефразирования:

2 алгоритм вставки

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

# Calculate the hash value of the key using the hash function set by the dictionary
hash = dict->type->hashFunction(key);
# Using the sizemask attribute and hash value of the hash table, the index value is calculated.
# depending on the situation, use HT [0] or HT [1]
index = hash & dict[x].sizemask;

Как показано на рисунке 4, если в словарь добавлена пара значений ключа [k0, v0], порядок вставки будет следующим:

hash = dict-type->hashFunction(k0);
index = hash & dict->ht[0].sizemask; # 8 & 3 = 0

Вычисляется, что значение ключа [k0, v0] соответствует позиции, в которой индекс массива хэш-таблиц равен 0, как показано на рис. 5:

2.1 Ключевой Конфликт

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

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

Возьмем, к примеру, каштан. Предположим, мы добавляем пары ключей [k2, v2] в хэш-таблицу, показанную на рис. 6, и вычисляем, что значение индекса K2 равно 2, что противоречит k1. Поэтому мы используем следующий указатель для соединения узлов, где расположены K2 и K1, как показано на рисунке 7.

3 перефразирование и прогрессивное перефразирование

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

Для коэффициента нагрузки он может быть рассчитан по следующей формуле:

# Load factor = hash table number of saved nodes / hash table size
load_factor = ht[0].used / ht[0].size;

3.1 Расширение и сжатие Хэш – таблицы

Расширение производственных мощностей

Для расширения хэш-таблицы исходный код выглядит следующим образом:

if (d->ht[0].used >= d->ht[0].size &&
    (dict_can_resize ||
     d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))
{
    return dictExpand(d, d->ht[0].used*2);
}

При выполнении следующих условий программа автоматически начинает расширять хэш-таблицу:

  • В настоящее время сервер не выполняет повторную обработку;
  • Количество сохраненных узлов в хэш-таблице больше, чем размер хэш-таблицы.
  • Параметр Dict_can_resize равен 1, или коэффициент загрузки превышает заданное соотношение (по умолчанию 5);

сокращаться

Исходный код сокращения хэш-таблицы выглядит следующим образом:

int htNeedsResize(dict *dict) {
    long long size, used;
    Size = dictSlots (dict); the sum of the sizes of the two hash tables // HT [2]
    Used = dictSize (dict); // HT [2] The sum of the number of saved nodes in two hash tables
    # DICT_HT_INITIAL_SIZE defaults to 4 and HASHTABLE_MIN_FILL defaults to 10.
    return (size > DICT_HT_INITIAL_SIZE &&
            (used*100/size < HASHTABLE_MIN_FILL));
}
void tryResizeHashTables(int dbid) {
    if (htNeedsResize(server.db[dbid].dict))
        dictResize(server.db[dbid].dict);
    if (htNeedsResize(server.db[dbid].expires))
        dictResize(server.db[dbid].expires);
}

Когда сумма размеров хэш-таблицы HT [] больше, чем DICT_HT_INITIAL_SIZE (по умолчанию 4), а отношение количества сохраненных узлов к общему размеру меньше 4, HASHTABLE_MIN_FILL (по умолчанию 10, то есть 10%) уменьшает хэш-таблицу.

3.2 перефразировать

Расширение и сжатие хэш-таблиц выполняются путем выполнения операций повторного хэширования. Хэш – таблицы выполняют следующие шаги повторного хэширования:

  1. Выделите место для хэш-таблицы словарей HT [1], размер которой зависит от выполняемой операции и количества пар ключей, содержащихся в настоящее время в HT [0].

    1. Если выполняется операция расширения, размер HT [1] равен ** первый больше или равен 2 ^ n из HT [0]. используется x2.
    2. Если выполняется операция сжатия, размер HT [1] будет на первые 2 ^ n больше или равен HT [0]. используйте.
  2. Все пары ключ-значение, хранящиеся в HT [0], преобразуются в HT [1]: перестановка относится к пересчету значения хэша и значения индекса ключа, а затем переносу пар ключ-значение в указанное расположение хэш-таблицы HT [1].
  3. Когда все пары ключей, содержащиеся в HT [0], переносятся в HT [1], HT [0] становится пустой таблицей, освобождает HT [0], устанавливает HT [1] в HT [0] и создает новую пустую хэш-таблицу в HT [1] для подготовки к следующему повторному хэшу.

Пример:

Предполагая, что программа хочет расширить HT [0] словаря, показанного на рисунке 8, программа выполнит следующие действия: 1) Текущее значение HT [0]. пользователь равен 4, а 2^3 – первая n-я степень, большая или равная 8. Таким образом, программа устанавливает размер хэш-таблицы HT [1] равным 8. На рисунке 9 приведен словарь HT [1] после выделения места.

2) Перефразируйте в HT [1], как показано на рисунке 10.

3) Отпустите HT [0], установите HT [1] в HT [0], а затем назначьте пустую хэш-таблицу HT [1]. Как показано на рисунке 11:

На этом этапе операция расширения хэш-таблицы была завершена, и программа успешно изменила размер хэш-таблицы с 4 на 8.

3.3 прогрессивная перестановка

Для повторного выполнения Redis это не одноразовое централизованное завершение, а постепенное и многократное завершение, поэтому оно также называется повторным выполнением Redis. Прогрессивная перефразировка

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

Поэтому, чтобы избежать перестановки, влияющей на производительность сервера, Redis делит ключевые значения в HT [0] на перестановку в HT [1] несколько раз и постепенно.

При инкрементном повторении используется переменная счетчика индексов rehashidx. Подробные шаги заключаются в следующем:

  1. Выделите место для HT [1], чтобы словарь содержал хэш-таблицы HT [0] и HT [1].
  2. Сохраните переменную счетчика индексов rehashidx в поле и установите ее значение равным 0, чтобы указать начало перестановки.
  3. Во время повторного хэширования каждый раз, когда выполняется операция ТВОРОГА со словарем, программа перемещает все пары ключей хэш-таблицы ht[0] по индексу rehashidx в ht[1] в дополнение к указанной операции. Когда повторная обработка завершена, программа добавляет значение rehashidx к единице.
  4. Поскольку словарь продолжает работать, в определенный момент времени все пары ключ-значение HT [0] будут преобразованы в HT [1], когда программа установит значение атрибута rehashidx равным – 1, что указывает на завершение преобразования.

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

Кроме того, при переписывании словаря, удалении, поиске, обновлении и других операциях будут выполняться две хэш-таблицы. Например, если вы ищете ключ в словаре, программа теперь будет искать его в HT [0], а если нет, перейдите в HT [1].

Следует отметить, что все новые пары ключей хранятся в HT [1], и никакие дополнительные операции с HT [0] не выполняются. Это гарантирует, что количество пар ключей, содержащихся в HT [0], уменьшается и не увеличивается. При повторной операции количество пар ключей в конечном итоге станет пустыми таблицами.

На рисунках 12-17 показан полный инкрементный процесс повторной обработки:

1) Словарь без перефразирования

2) Пары Ключ-значение по индексу перефразирования 0

3) Пары Ключ-значение по индексу перефразирования 1

4) Пары Ключ-значение по индексу перефразирования 2

5) Пары Ключ-значение по индексу перефразирования 3

6) Повторный просмотр завершен

резюме

  1. Поля широко используются для реализации различных функций Redis, включая базы данных и хэш-ключи.
  2. Словари в Redis используют хэш-таблицы в качестве базовой реализации. В каждом словаре есть две хэш-таблицы, одна для обычного использования и одна только для повторного использования.
  3. Хэш-таблица использует метод адреса цепочки для разрешения конфликтов ключей, и несколько пар ключ-значение, назначенных одному и тому же индексу, связаны в односторонний список.
  4. При расширении или сжатии хэш-таблицы повторная обработка выполняется постепенно.