Продолжение предыдущей статьиЗачем использовать Redis, сегодня поговорим о конкретных типах данных и командах Redis. Эта статья является важной основой для глубокого понимания Redis. Пожалуйста, держитесь смирно, и длинный текст впереди предупредит вас.
Эта серия основана на: redis-3.2.12
В этой статье будут представлены не все команды, в основном те, которые часто встречаются в работе.
Большинство материалов, которые мы обычно читаем, просто и грубо рассказывают нам, что делает эта команда, и эта команда требует несколько параметров. Этот метод будет только знать, что это такое, и не знаю, почему.В этой статье, от временной сложности команды до цели, до того, какую структуру использует соответствующий тип для сохранения данных в нижнем слое Redis, я надеюсь что каждый будет знать более глубоко и иметь лучшее понимание при его использовании.
-
При чтении обратите внимание: хотя временная сложность многих команд равна O(n), обратите внимание на конкретное значение ее n.
-
В этой статье OBJECT ENCODING xxx будет использоваться для проверки внутренней кодировки Redis, которая на самом деле представляет собой значение, представленное кодировкой в структуре read redisObject. redisObject обеспечивает унифицированное представление для разных типов данных.
Тип строки
Следует сказать, что это наиболее широко используемый тип данных в Redis. Некоторые из команд этого типа имеют очень широкие сценарии использования. Например:
- Кэш, вот где он часто используется;
- Технология счетчика/ограничителя скорости;
- Общий сеансовый сервер также основан на этом типе данных.
Примечание. В таблице описаны только 12 команд в String и перечислены только некоторые сценарии использования.
Нам часто говорят, что такие команды, как MSET/MGET, следует использовать с осторожностью, потому что их временная сложность равна O(n), но на самом деле обратите внимание, что n представляет собой количество ключей, установленных или прочитанных на этот раз, поэтому, если вы прочитано не так много ключей, и содержимое каждого ключа не очень велико, поэтому использование команд пакетной обработки может сэкономить время сетевых запросов и передачи.
внутренняя структура
Как данные типа String окончательно сохраняются в Redis? Если вы хотите изучить его подробно, вы должны начать сSDS
Давайте поговорим об этой структуре, но сегодня мы не будем показывать детали этой части исходного кода, а только поговорим о структуре данных, хранящихся в ней. В итоге заданные нами строки будут храниться в одной из трех форм.
- INT, 8-байтовое длительное целое число, максимальное значение: 0x7ffffffffffffffl
- Embstr, строка меньше или равная 44 байтам
- Raw
Объедините код, чтобы увидеть, как Redis принимает решения об этих трех структурах данных. Когда мы используем команду на стороне клиентаSET test hello,redis
, клиент сохранит команду в buf, а затем выполнит команды в том порядке, в котором они были получены. Одна из этих функций:processMultibulkBuffer()
, который внутренне вызываетcreateStringObject()
функция:
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {
// 检查保存的字符串长度,选择对应类型
if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
return createEmbeddedStringObject(ptr,len);
else
return createRawStringObject(ptr,len);
}
Неважно, если вы не знаете язык C, вот строка, которую мы вводимhello,redis
Превышает ли длина 44, превышает ли она типraw
, если нет, выберитеembstr
. Экспериментируйте, чтобы увидеть:
127.0.0.1:6379> SET test 12345678901234567890123456789012345678901234 // len=44
OK
127.0.0.1:6379> OBJECT encoding test
"embstr"
127.0.0.1:6379> SET test 123456789012345678901234567890123456789012345 // len=45
OK
127.0.0.1:6379> OBJECT encoding test
"raw"
Как видите, как только оно превысит 44, базовый тип станет таким:raw
. Подождите, разве мы не упоминали, что естьint
тип? Совсем не видно его следа от функции? Не волнуйтесь, когда введенная нами команда действительно вот-вот начнет выполняться, то есть вызовет функциюsetCommand()
, вызываетtryObjectEncoding()
функция, функция этой функции состоит в том, чтобы попытаться сжать входную строку, продолжайте смотреть на код:
robj *tryObjectEncoding(robj *o) {
... ...
len = sdslen(s);
// 长度小于等于20,并且能够转成长整形
if(len <= 20 && string2l(s,len,&value)) {
o->encoding = OBJ_ENCODING_INT;
}
... ...
}
Эта функция была сильно уменьшена мной, но мы можем видеть, что она оценивает, меньше или равна ли длина 20, и пытается преобразовать ее в целое число, см. пример.
9223372036854775807это наибольшее целое число, которое может быть представлено 8-битными байтами, и его шестнадцатеричная форма:0x7fffffffffffffffL
127.0.0.1:6379> SET test 9223372036854775807
OK
127.0.0.1:6379> OBJECT encoding test
"int"
127.0.0.1:6379> SET test 9223372036854775808 // 比上面大1
OK
127.0.0.1:6379> OBJECT encoding test
"embstr"
На этом процесс выбора типа для String завершен. Для нас эталонным значением является то, что когда мы используем тип String для сохранения данных, мы должны учитывать, что базовый слой соответствует разным типам.Разные типы будут выполнять разные процессы в Redis, и их соответствующая эффективность выполнения и потребление памяти будут разными. .
Тип хеша
Мы часто используем его для хранения структурированных данных, таких как кешированная информация, связанная с пользователем. Если используется обычный тип String, строку нужно сериализовать и десериализовать, что, несомненно, увеличит дополнительные накладные расходы, и каждый раз можно прочитать только всю ее.
- Кэшируйте структурированные данные, такие как информация о статье, и гибко изменяйте одно из его полей, например, количество прочтений.
Структурированные данные, хранящиеся в типе Hash, очень похожи на запись в MySQL, мы можем легко изменить поле, но это более гибко, и каждая запись может содержать разные поля.
внутренняя структура
Во внутренних данных типа Hash могут быть два типа структур данных:
- ZipList, более компактный, ограничения: длина ключа и поля не более 64, а количество полей в ключе не более 512
- HashTable
Для Hash Redis сначала устанавливает его для использования по умолчанию.ZipList
Структура данных, а затем решить, нужно ли ее изменить в соответствии с условиями.
void hsetCommand(client *c) {
int update;
robj *o;
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
hashTypeTryConversion(o,c->argv,2,3);// 根据长度决策
... ...
update = hashTypeSet(o,c->argv[2],c->argv[3]);// 根据元素个数决策
addReply(c, update ? shared.czero : shared.cone);
... ...
}
hashTypeLookupWriteOrCreate()
будет вызываться внутреннеcreateHashObject()
Создание хэш-объектов.
robj *createHashObject(void) {
unsigned char *zl = ziplistNew();
robj *o = createObject(OBJ_HASH, zl);
o->encoding = OBJ_ENCODING_ZIPLIST;// 设置编码 ziplist
return o;
}
hashTypeTryConversion()
Функция основана на том, превышает ли онаhash_max_ziplist_value
Длина предела (64), чтобы определить низкоуровневую структуру данных.
void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {
int i;
if (o->encoding != OBJ_ENCODING_ZIPLIST) return;
for (i = start; i <= end; i++) {
// 检查 field 与 value 长度是否超长
if (sdsEncodedObject(argv[i]) &&
sdslen(argv[i]->ptr) > server.hash_max_ziplist_value)
{
hashTypeConvert(o, OBJ_ENCODING_HT);
break;
}
}
}
то в функцииhashTypeSet()
Проверьте, не превышает ли количество полейhash_max_ziplist_entries
лимит (512).
int hashTypeSet(robj *o, robj *field, robj *value) {
int update = 0;
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
... ...
// 检查field个数是否超过512
if (hashTypeLength(o) > server.hash_max_ziplist_entries)
hashTypeConvert(o, OBJ_ENCODING_HT);
} else if (o->encoding == OBJ_ENCODING_HT) {
... ...
}
... ...
return update;
}
Чтобы проверить приведенную выше логику:
127.0.0.1:6379> HSET test name qweqweqwkejkksdjfslfldsjfkldjslkfqweqweqwkejkksdjfslfldsjfkldjsl
(integer) 1
127.0.0.1:6379> HSTRLEN test name
(integer) 64
127.0.0.1:6379> OBJECT encoding test
"ziplist"
127.0.0.1:6379> HSET test name qweqweqwkejkksdjfslfldsjfkldjslkfqweqweqwkejkksdjfslfldsjfkldjslq
(integer) 0
127.0.0.1:6379> HSTRLEN test name
(integer) 65
127.0.0.1:6379> OBJECT encoding test
"hashtable"
Что касается предела настройки ключа, превышающего 64, и количества полей, превышающего 512, вы можете проверить это самостоятельно.
Тип списка
Тип списка также очень широко используется, в основном для суммирования общих сценариев:
- Очередь сообщений: LPUSH + BRPOP (функция блокировки)
- Кэш: пользователи записывают различные записи, самая большая особенность заключается в том, что он может поддерживать пейджинг.
- Стек: LPUSH + LPOP
- Очередь: LPUSH + RPOP
- Ограниченная очередь: lpush + ltrim, который может поддерживать объем данных в очереди
внутренняя структура
Типы данных List реализованы на низком уровне следующим образом:
- QuickList: это LinkedList с ZipList в качестве узла
- ZipList (экономия памяти),Нашел где-то используемый в версии 3.2.12
- связанный список,Нашел где-то используемый в версии 3.2.12
В некоторых статьях в Интернете говорилосьLinkedList
существуетRedis 4.0
Более поздние версии больше не используются, на самом деле я нашелRedis 3.2.12
Структура также не используется в версии (не используется напрямую как структура хранения данных), в т.ч.ZipList
существует3.2.12
Версии больше не используются напрямую для хранения данных.
Мы проводим эксперимент для проверки следующего, мы создали список1000Элементы, длина значения каждого элемента превышает64символы.
127.0.0.1:6379> LLEN test
(integer) 1000
127.0.0.1:6379> OBJECT encoding test
"quicklist"
127.0.0.1:6379> LINDEX test 0
"qweqweqwkejkksdjfslfldsjfkldjslkfqweqweqwkejkksdjfslfldsjfkldjslq" // 65个字符
Изменяем ли мы количество элементов списка и длину значения элемента, его структураQuickList
. Если вы мне не верите, давайте взглянем на код:
void pushGenericCommand(client *c, int where) {
int j, waiting = 0, pushed = 0;
robj *lobj = lookupKeyWrite(c->db,c->argv[1]);
... ...
for (j = 2; j < c->argc; j++) {
c->argv[j] = tryObjectEncoding(c->argv[j]);
if (!lobj) {
// 创建 quick list
lobj = createQuicklistObject();
quicklistSetOptions(lobj->ptr, server.list_max_ziplist_size,
server.list_compress_depth);
dbAdd(c->db,c->argv[1],lobj);
}
listTypePush(lobj,c->argv[j],where);
pushed++;
}
... ...
}
Первоначально звонитеcreateQuicklistObject()
Настройка его низкоуровневой структуры данных:quick list
. В последующем процессе нет места для трансформации структуры.
Тип набора
Одной из важных особенностей типа Set является то, что он может быть дедуплицирован и неупорядочен. Его коллективный характер может быть широко использован в социальном плане.
- Общая забота
- всеобщий интерес
- дедупликация данных
внутренняя структура
Низкоуровневая реализация набора использует две структуры данных:
- IntSet, все элементы набора являются целыми числами (не могут превышать максимальное целое число), а элементы набора меньше 512.
- HashTable
Код этой команды выглядит следующим образом, два важных вызова для определения типа:setTypeCreate()
иsetTypeAdd()
.
void saddCommand(client *c) {
robj *set;
... ...
if (set == NULL) {
// 初始化
set = setTypeCreate(c->argv[2]);
} else {
... ...
}
for (j = 2; j < c->argc; j++) {
// 内部会检查元素个数是否扩充到需要改变低层结构
if (setTypeAdd(set,c->argv[j])) added++;
}
... ...
}
Давайте взглянем на исходный код создания объекта структуры Set:
robj *setTypeCreate(robj *value) {
if (isObjectRepresentableAsLongLong(value,NULL) == C_OK)
return createIntsetObject(); // 使用IntSet
return createSetObject(); // 使用HashTable
}
isObjectRepresentableAsLongLong()
Внутренне оцените его целочисленный диапазон, если это целое число и не превышает максимальное целое число, оно будет использоватьсяIntSet
Сохранить. В противном случаеHashTable
. Затем проверяется количество элементов.
int setTypeAdd(robj *subject, robj *value) {
long long llval;
if (subject->encoding == OBJ_ENCODING_HT) {
... ...
} else if (subject->encoding == OBJ_ENCODING_INTSET) {
if (isObjectRepresentableAsLongLong(value,&llval) == C_OK) {
uint8_t success = 0;
subject->ptr = intsetAdd(subject->ptr,llval,&success);
if (success) {
/* Convert to regular set when the intset contains
* too many entries. */
if (intsetLen(subject->ptr) > server.set_max_intset_entries)
setTypeConvert(subject,OBJ_ENCODING_HT);
return 1;
}
} else {
/* Failed to get integer from object, convert to regular set. */
setTypeConvert(subject,OBJ_ENCODING_HT);
... ...
return 1;
}
}
... ...
return 0;
}
Взгляните на пример, вот самое большое целочисленное критическое значение:
127.0.0.1:6379> SADD test 9223372036854775807
(integer) 1
127.0.0.1:6379> OBJECT encoding test
"intset"
127.0.0.1:6379> SADD test 9223372036854775808
(integer) 1
127.0.0.1:6379> OBJECT encoding test
"hashtable"
Что касается теста количества наборов, пожалуйста, завершите наблюдение самостоятельно.
Тип набора сортировки
Сегодняшние приложения имеют некоторые функции, такие как рейтинговые списки, такие как инвестиционные веб-сайты, показывающие ранжирование суммы инвестиций, торговые веб-сайты, показывающие рейтинг потребления, и так далее. SortSet отлично подходит для этого. Обычно используется для решения следующих задач:
- Все виды списка
- Установите вес задачи выполнения, и фоновый скрипт выполнит соответствующие операции в соответствии с порядком сортировки.
- Поиск диапазона, чтобы найти, в каком диапазоне коллекции находится значение.
внутренняя структура
Хотя упорядоченный набор тоже является набором, низкоуровневая структура данных отличается от структуры набора.Он также имеет две структуры данных, а именно:
- ZipList, эта структура используется, когда количество элементов в отсортированном наборе меньше или равно 128 или длина элемента меньше или равна 64.
- SkipList
Этот процесс преобразуется в следующее:
void zaddGenericCommand(client *c, int flags) {
if (zobj == NULL) {
if (xx) goto reply_to_client; /* No key + XX option: nothing to do. */
if (server.zset_max_ziplist_entries == 0 ||
server.zset_max_ziplist_value < sdslen(c->argv[scoreidx+1]->ptr))
{
zobj = createZsetObject();// skip list
} else {
zobj = createZsetZiplistObject();// zip list
}
dbAdd(c->db,key,zobj);
} else {
... ...
}
... ...
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
if (zzlLength(zobj->ptr) > server.zset_max_ziplist_entries)
zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);// 根据个数转化编码
if (sdslen(ele->ptr) > server.zset_max_ziplist_value)
zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);// 根据长度转化编码
}
}
Здесь членов более 64 Пример:
127.0.0.1:6379> ZADD test 77 qwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwer // member长度是 64
(integer) 1
127.0.0.1:6379> OBJECT encoding test
"ziplist"
127.0.0.1:6379> ZADD test 77 qwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwerq // member长度是65
(integer) 1
127.0.0.1:6379> OBJECT encoding test
"skiplist"
Когда длина нашего члена превышает 64 бита, низкоуровневая структура данных состоит изZipList
превратился вSkipList
. Для проверки количества оставшихся элементов попробуйте.
Глобальные общие команды
Для глобальных команд, независимо от того, к какому типу данных относится соответствующий ключ, можно выполнять операции. На что следует обратить вниманиеKEYSЭту команду нельзя использовать в сети, поскольку в Redis используется однопоточный механизм: если в памяти слишком много данных, операция будет серьезно заблокирована, что приведет к тому, что вся служба Redis не сможет ответить.
Суммировать
- Redis различные командные временные сложности каждого типа, некоторые отношения с количеством соответствующих элементов; некоторые отношения с количеством запросов;
- Разумно располагайте релевантное количество и длину элементов и стремитесь использовать самую простую структуру данных в нижней части Redis;
- Обратите внимание на временную сложность, разберитесь во внутренних элементах вашего Redis и избегайте блокировки;
- Чем проще данные, тем лучше производительность;
- Нижние уровни каждого типа данных Redis соответствуют множеству структур данных, а модификации и расширения не знают о верхних слоях.
В первой рассказывается о том, зачем используется Redis, в этой статье также рассказывается о большинстве команд, а также о некоторых их реализациях в исходном коде Redis, далее мы остановимся на некоторых операциях в конкретной практике. Я надеюсь, что все должны помочь,Ожидайте любой формы критики и поощрения.
публика:dayuTalk