Некоторые структуры данных Redis
Роман АлексеевОбычно все используют Redis как кэш. Присваивают ключу значение, читают значение по ключу. Максимум - присваивают время жизни ключа. Но когда у всех всё одинаково - скучно. Поэтому расскажу про некоторые другие структуры данных в Redis.
К тому же Redis Labs снес раздел сайта с оригинальной документацией от автора.
Hash
Словарь.
Реализуется как хэш-таблица либо (для маленьких хэшей) как упакованный бинарный формат, по смыслу напоминающий "количество:ключ:значение:ключ:значение".
Варианты использования:
1) Сохраняем некий объект целиком по одному ключу, читаем весь объект или часть полей объекта по ключ.
hset cusomer_1 id 123 name Alice age 15
hgetall customer_1 или hmget customer_1 name age или hget customer_id age
Объект будет найден за O(1), а в БД пришлось бы искать в индексе за O(log n).
2) Очень быстро за O(1) ищем во множестве значений по ключу.
Например, строим хэш вида email:id и заполняем его миллионами таких пар.
При логине в приложении мгновенно находим идентификатор пользователя.
hset user_logins alice@mail.ru 1 bob@mail.ru 2 charlie@mail.ru 3 david@mail.ru 4 eve@mail.ru 6 frank@mail.ru 7
hget user_logins charlie@mail.ru // возвращает 3
Либо строим хэш вида короткая_ссылка:длинная_ссылка и получаем тот же эффект (константное время) для хранилища коротких ссылок.
hset short_long_links abc github.com/qwer xyz stackoverflow.com/asdf
hget short_long_links abc // возвращает github.com/qwer
В обоих случаях строковый индекс в БД работал бы существенно медленнее за O(log n).
List
Связанный список.
Реализуется как связанный список массивов.
Варианты использования:
1) Выводим набор "последних" записей, например записей в блоге, комментариев и т.д.
Добавляем в список идентификаторы записей в порядке поступления. Это не хэш, ключей и значений здесь нет. Затем берем 3 последних записи.
rpush article_ids 1 2 3 4 5 6 7 8 9 // добавляем в конец списка
lrange article_ids -3 -1 // возвращает 7 8 9 - три статьи из конца списка
2) Храним только несколько последних записей. Например последние добавленные пользователи.
При добавлении пользователей, добавляем их имена в список. После каждого добавления сразу отрезаем от списка всех, кроме N последних.
rpush latest_users alice bob charlie david eve frank // добавляем в конец списка
ltrim latest_users -3 -1 // обрезаем начало списка кроме 3х последних
lrange latest_users 0 -1 // смотрим весь список, в нем только david eve frank
3) Реализуем очередь. Добавляем команды в список, затем поочередно получаем и удаляем полученные команды из списка.
rpush commands first second third // добавляем команды в конец списка
lpop commands 1 // за O(1) вернет и удалит first, в списке останется second, third
Set
Множество уникальных элементов.
Пока множество маленькое, его реализация больше напоминает массив, когда множество вырастает - преращается в хэш-таблицу.
Варианты использования:
1) Храним и получаем только уникальные элементы, повторяющиеся отбрасываем. Например всех, пользователей, которые посещали сегодня страницу.
sadd page_1_viewers_2024-11-01 alice bob charlie charlie charlie // записываем пользователя каждый раз, когда он заходит на страницу
smembers page_1_viewers_2024-11-01 // вернет alice bob charlie
2) Находим общие элементы множеств. Например, общих друзей в социальной сети.
sadd alice_friends charlie david frank // друзья Алисы в первом множестве
sadd bob_friends david eve frank // Друзья Боба во втором множестве
sinter alice_friends bob_friends // возвращает общих друзей david frank
Работает и для нескольких множеств одной командой.
3) Находим различающиеся элементы множеств. Например чего не хватает игроку для завершения миссии.
sadd mission_requirements steal_artifact_1 open_door_1 open_door_2 // что нужно сделать
sadd player_1_achievements steal_artifact_1 open_door_1 // что сделано
sdiff mission_requirements player_1_achievements // вернет open_door_2 - что осталось сделать
4) Получаем случайный элемент множества.
Например, выбираем победителя лотереи.
sadd users alice bob charlie david eve frank
srandmember users // возвращает случайного пользователя
Sorted Set
Множество, элементы в котором отсортированы по весу (score).
Реализовано как комбинация хэш-таблицы и структуры данных SkipList (детали в статье).
Варианты использования:
1) Выводим несколько максимальных или минимальных элементов по весу. Например, топ рекордов.
Когда пилот Формулы-1 завершает круг, можно сохранить его время и затем вывести топ.
zadd track_1_totals 533.64 pilot_1 525.22 pilot_2 523.87 pilot_3 520.03 pilot_4 530.12 pilot_5
zrange track_1_totals 0 2 withscores // вернет троих лучших pilot_4 520.03 pilot_3 523.87 pilot_2 525.22 и их результаты.
Или можно узнать на каком месте находится один из пилотов .
zrank track_1_totals pilot_1 withscore // возвращает 4 и его результат 533.64
Другой вариант - игрок в стрелялке убивает противника или футбольная команда выигрывает матч и получает три очка.
zadd league_scores 1 team_1 0 team_2 0 team_3 1 team_4 // предыдущие результаты, у команд team_1 и team_4 было по одному очку
zincrby league_scores 3 team_2 // после сыгранного матча, вторая команда набирает три очка
zrange league_scores 0 -1 rev withscores // выводим в обратном порядке team_2 3 team_1 1 team_4 1 team_3 0
2) Реализуем очередь с приоритетами. Добавляем команды c разными приоритетами, затем поочередно получаем и удаляем команды из списка в порядке приоритета.
zadd commands 10 command_1 20 command_2 100 command_3 30 command_4
zpopmax commands 1 // вернет и удалит command_3 , в списке останется command_4 command_2 command_1
3) Реализуем ограничение по количеству запросов за период времени (rate limit), есть множество реализаций с различными свойствами, например эта.
4) Добавляем вторичные индексы для объектов. Например, добавляем несколько пользователей как отдельные хэши. Затем добавляем их возрасты в Sorted Set. После чего можем получить идентификаторы всех пользователей старше 18 лет.
hset 1 name alice age 15 ...
hset 2 name bob age 25 ...
hset 3 name charlie age 35 ...
zadd user_age 15 alice 25 bob 35 charlie // индексируем возраст
zrange user_age 18 +inf byscore // вернет bob charlie, остальную информацию о которых можно получить из хэшей.
На этом пока всё.