База данных Redis появилась в 2009 году, но ей все еще предстоит пройти проверку временем и полезностью в реальном мире, хотя многие разработчики и признают, что уже активно используют ее в повседневной практике.
Почему Redis?
Обычно при упоминании Redis у многих возникают ассоциации с базами данных NoSQL, но это в корне неверно. У Redis нет с ними ничего общего: ни в плане своего позиционирования, ни в плане исполнения. MongoDB, например, хранит данные на диске.
Выделение места для записей подразумевает, что эти данные должны быть сохранены: аккаунты пользователей, записи в блог, разрешения и так далее. Большая часть данных любых приложений относится к этой категории.
Тем не менее есть и исключения. Было бы крайне неэффективно хранить, например, содержимое корзины пользователя или информацию о последней посещенной странице. В краткосрочной перспективе такая информация была бы полезной, но нагружать ею базы данных, основанные на транзакционных системах — не очень разумно. Благо, существует такое понятие как RAM (ОЗУ или оперативное запоминающее устройство).
Redis — это резидентная база данных (такая, которая хранит записи прямо в оперативной памяти) в виде пар ключ-значение. Чтение и запись в память происходит намного быстрее, чем в случае с дисками, поэтому такой подход отлично подходит для хранения второстепенных данных.
Это улучшает пользовательский опыт, но одновременно делает базы данных чистыми. Если же в будущем решается, что такие данные тоже нужно хранить, то их всегда можно записать на диск (например, в базу данных SQL).
В этом руководстве познакомимся с библиотекой Python для Redis под названием redis-py. В среде Python его называют просто redis. Официальная документация этой библиотеки — просто одна страница с перечислением всех методов в алфавитном порядке.
Если вы планируете использовать Redis с каким-либо из фреймворков, то рекомендуется выбирать конкретную библиотеку: например, Flask-Redis, а не redis-py. Однако все они преимущественно повторяют синтаксис redis-py и имеют несколько минимальных отличий.
Установка Redis
Что бы протестировать работу Redis рекомендую использовать облачное решение. Зарегистрируйтесь на Redis Labs, они дают бесплатный сервер для обучения и тестирования.
- Пройдите регистрацию.
- Подтвердите почту.
- Создайте подписку (сервер).
4. Создайте базу данных:
После активации приложения вам понадобятся хост(Endpoint) и пароль (Default User Password).
Далее установим redis:
pip install redis
Строка подключения к Redis
Как и в случае с обычными базами данных подключить экземпляр Redis можно с помощью строки подключения. Вот как такая выглядит в Redis:
redis://:hostname.redislabs.com@mypassword:12345/0
Разберем по пунктам:
[CONNECTION_METHOD]:[HOSTNAME]@[PASSWORD]:[PORT]/[DATABASE]
CONNECTION_METHOD
— это суффикс, который нужен во всех URI Redis. Он определяет способ подключения к приложению.redis://
— стандартное соединение,rediss://
— подключается по SSL,redis-socket://
— зарезервированный тип для сокетов Unix иredis-sentinel://
— тип подключения для кластеров Redis с высоким уровнем доступности.HOSTNAME
— URL или IP приложения Redis. Если вы используете облачное решение, то, скорее всего, вам нужен адрес AWS EC2. (Такова особенность современного капитализма, где все весь мелкий бизнес — это реселлеры с заранее настроенным ПО).PASSWORD
— у приложения Redis есть пароль, но нет пользователей. Скорее всего, это связано с тем, что в случае с резидентной базой данных сложно было хранить их имена.PORT
— выбранный порт.DATABASE
— если не уверены, что здесь указать, просто напишите 0.
Создание клиента Redis
URI есть. Теперь нужно подключиться к Redis, создав объект клиента:
import redis
r = redis.StrictRedis(
host='redis-17449.c55.eu-central-1-1.ec2.cloud.redislabs.com', # из Endpoint
port=17449, # из Endpoint
password='qwerty' # ваш пароль
)
Но почему StrictRedis, вы можете спросить? Есть два вида создания клиентов Redis: redis.Redis()
и redis.StrictRedis()
. StrictRedis пытается правильно применять типы данных. Старые экземпляры так не умеют. redis.Redis()
— обратно совместимая с устаревшими экземплярами Redis версия с любыми наборами данных, а redis.StrictRedis()
— нет. Если сомневаетесь — используйте StrictRedis.
Есть множество других аргументов, которые можно (и нужно) передать в redis.StrictRedis()
, чтобы упростить себе жизнь. Обязательно передайте decode_responses=True
, ведь это избавит необходимости явно расшифровывать каждое значение из базы. Также не лишним будет указать кодировку:
import redis
r = redis.StrictRedis(
host='redis-17449.c55.eu-central-1-1.ec2.cloud.redislabs.com',
port=17449,
password='qwerty',
charset="utf-8",
decode_responses=True
)
Пример использования Redis
Хранилище ключ-значение Redis очень напоминает словари Python, отсюда и расшифровка — Remote Dictionary Service (удаленный сервис словарей). Ключи — это всегда строки, но значениями могут быть разные типы данных. Заполним приложение Redis несколькими записями, чтобы лучше понять, как они работают:
import redis
import time
r = redis.StrictRedis(
host='redis-17449.c55.eu-central-1-1.ec2.cloud.redislabs.com',
port=17449,
password='qwerty',
charset="utf-8",
decode_responses=True
)
r.set('ip_address', '127.0.0.0')
r.set('timestamp', int(time.time()))
r.set('user_agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11)')
r.set('last_page_visited', 'home')
r.set([KEY], [VALUE])
— это основной синтаксис, чтобы задавать одиночные значения. Первый параметр — это ключ, а второй — присваиваемое ему значение.
По аналогии с обычными базами данных подключение к Redis осуществляется с помощью графического интерфейса, по типу TablePlus для проверки данных. Вот как выглядит тестовая база после выполнения кода выше:
KEY | VALUE | TYPE | TTL |
---|---|---|---|
user_agent | Mozilla/5.0 (Macintosh; Intel Mac OS X 11) | STRING | -1 |
last_page_visited | home | STRING | -1 |
ip_address | 127.0.0.0 | STRING | -1 |
timestamp | 1610803181 | STRING | -1 |
Кажется, операция прошла успешно. Узнать кое-что о Redis можно, просто взглянув на таблицу. Начнем с колонки type.
Типы данных в Redis
В Redis могут храниться данные 5 типов:
STRING
(строка) — любое значение, сохраняемое с помощьюr.set()
, хранится в виде строки. Вы также можете обратить внимание на то, что значениеtimestamp
будет целым числом в Python, но здесь оно выступает строкой. Вы можете подумать, что это не очень удобно, но строки в Redis — это чуть больше, чем может показаться на первый взгляд. Во-первых, они являются бинарно-безопасными, что значит, что с их помощью можно хранить почти что угодно, вплоть до изображений и сериализованных объектов. У строк также есть несколько встроенных функций, которые позволяют управлять ими так, будто бы это целые числа (например INC для инкремента).LIST
(список) — списки являются изменяемыми массивами строк в Redis, которые сортируются в порядке появления. После создания списка новые элементы могут добавляться в конец с помощью командыRPUSH
или в начало с помощьюLPUSH
. Можно также ограничить максимальное количество элементов в списке с помощью командыLTRIM
. Если вы знакомы с методом вытеснения из кэша LRU, например, то должны быть знакомы с этой командой.SET
(множество) — несортированный набор строк. По аналогии с множествами в Python в Redis элементы не могут повторяться. У множеств также есть уникальные функции по объединению и пересечению, что позволяет эффективно и быстро объединять данные из разных наборов.ZSET
— те же множества, но уже сортированные. Однако в них по-прежнему могут храниться только уникальные значения. После создания порядок в них можно менять, что удобно для ранжирования уникальных элементов, например. СтруктураZSET
похожа на сортированные словари в Python за исключением того, что у них есть ключ для каждого значения (что было бы ненужным, ведь все множества уникальны).HASH
(хэши) — хэши в Redis являются парами ключ-значение. Эта структура позволяют присваивать ключам значение из ключа и значения. Однако вложенными хэши быть не могут.
Срок хранения данных
В базе данных Redis есть четвертая колонка под названием ttl
. В нашем примере для всех записей в ней было значение -1
. Когда же там положительное значение, то оно указывает на количество секунд до истечения срока действия данных. Redis — отличное хранилище для временно полезных данных, однако не таких, которые нужны в долгосрочной перспективе. Вот когда полезно устанавливать срок действия — это позволяет не нагружать приложение информацией, которая быстро становится нерелевантной.
Вернемся к примеру, где хранится информация о сессии пользователя и зададим срок действия данных:
...
r.set('last_page_visited', 'home', 86400)
В этот раз передадим третье значение в r.set()
. Оно указывает на количество секунд, которое должно пройти до истечения срока действия данных. Снова проверим базу данных:
KEY | VALUE | TYPE | TTL |
---|---|---|---|
user_agent | Mozilla/5.0 (Macintosh; Intel Mac OS X 11) | STRING | -1 |
last_page_visited | home | STRING | 86400 |
ip_address | 127.0.0.0 | STRING | -1 |
timestamp | 1610803181 | STRING | -1 |
Работа с каждым типом данных
Теория — это прекрасно, но мы знаем, зачем вы здесь: чтобы воспользоваться кодом, который можно будет применить в своем приложении. Рассмотрим несколько примеров распространенного использования 5 типов данных в Redis.
Строки
Если строки включают целые числа, то есть несколько методов, с помощью которых можно их изменять так, будто бы это целые числа. Это incr()
, decr()
и incrby()
:
# Создать строковое значение
r.set('index', '1')
print(f"index: {r.get('index')}")
# Увеличить строку на 1
r.incr('index')
print(f"index: {r.get('index')}")
# Уменьшить строку на 1
r.decr('index')
print(f"index: {r.get('index')}")
# Увеличить строку на 3
r.incrby('index', 3)
print(f"index: {r.get('index')}")
Это присвоит значение ‘1’ ключу index
, увеличит его на 1, уменьшит на 1 и в итоге увеличит еще на 3:
index: 1
index: 2
index: 1
index: 4
Списки
Добавим элементы в список Redis с помощью комбинации .lpush()
и rpush()
, а также удалим их оттуда с помощью .lpop()
. Классика:
r.lpush('my_list', 'A')
print(f"my_list: {r.lrange('my_list', 0, -1)}")
# Добавить вторую строку в список справа
r.rpush('my_list', 'B')
print(f"my_list: {r.lrange('my_list', 0, -1)}")
# Вставить третью строку в список справа
r.rpush('my_list', 'C')
print(f"my_list: {r.lrange('my_list', 0, -1)}")
# Удалить из списка 1 экземпляр, значение которого "C"
r.lrem('my_list', 1, 'C')
print(f"my_list: {r.lrange('my_list', 0, -1)}")
# Вставить строку в наш список слева
r.lpush('my_list', 'C')
print(f"my_list: {r.lrange('my_list', 0, -1)}")
# Вытащить первый элемент нашего списка и переместить его в конец
r.rpush('my_list', r.lpop('my_list'))
print(f"my_list: {r.lrange('my_list', 0, -1)}")
Вот как будет выглядеть список на разных этапах:
my_list: ['A']
my_list: ['A', 'B']
my_list: ['A', 'B', 'C']
my_list: ['A', 'B']
my_list: ['C', 'A', 'B']
my_list: ['A', 'B', 'C']
Множества
Множества являются мощным инструментом отчасти благодаря их способности взаимодействовать между собой. Дальше создаются два отдельных множества и на них применяются операции .sunion()
и .sinter()
:
# Добавить элемент в set 1
r.sadd('my_set_1', 'Y')
print(f"my_set_1: {r.smembers('my_set_1')}")
# Добавить элемент в set 1
r.sadd('my_set_1', 'X')
print(f"my_set_1: {r.smembers('my_set_1')}")
# Добавить элемент в set 2
r.sadd('my_set_2', 'X')
print(f"my_set_2: {r.smembers('my_set_2')}")
# Добавить элемент в set 2
r.sadd('my_set_2', 'Z')
print(f"my_set2: {r.smembers('my_set_2')}")
# Объединение set 1 и set 2
print(f"sunion: {r.sunion('my_set_1', 'my_set_2')}")
# Пересечение set 1 и set 2
print(f"sinter: {r.sinter('my_set_1', 'my_set_2')}")
Первая объединяет наборы без повторов, а вторая — выбирает общие элементы:
my_set_1: {'Y'}
my_set_1: {'X', 'Y'}
my_set_2: {'X'}
my_set2: {'X', 'Z'}
sunion: {'X', 'Y', 'Z'}
sinter: {'X'}
Сортированные множества
Добавление элементов в сортированное множество с помощью .zadd()
предполагает интересный синтаксис. Обратите внимание на то, что для добавления записей требуется словарь в формате {[VALUE]: [INDEX]}
:
# Создали отсортированный set с 3 значениями
r.zadd('top_songs_set', {'Never Change - Jay Z': 1,
'Rich Girl - Hall & Oats': 2,
'The Prayer - Griz': 3})
print(f"top_songs_set: {r.zrange('top_songs_set', 0, -1)}")
# Добавили элемент в set с конфликтующим значением
r.zadd('top_songs_set', {"Can't Figure it Out - Bishop Lamont": 3})
print(f"top_songs_set: {r.zrange('top_songs_set', 0, -1)}")
# Индекс сдвига значения
r.zincrby('top_songs_set', 3, 'Never Change - Jay Z')
print(f"top_songs_set: {r.zrange('top_songs_set', 0, -1)}")
У элементов в сортированном множестве никогда не может быть одного и того же индекса, так что при попытке добавить элемент на место существующего индекса, текущий элемент (и все после него) сдвигаются, чтобы дать место новому. Также есть возможность менять индексы после их создания:
top_songs_set: ['Never Change - Jay Z', 'Rich Girl - Hall & Oats', 'The Prayer - Griz']
top_songs_set: ['Never Change - Jay Z', 'Rich Girl - Hall & Oats', "Can't Figure it Out - Bishop Lamont", 'The Prayer - Griz']'
top_songs_set: ['Rich Girl - Hall & Oats', "Can't Figure it Out - Bishop Lamont", 'The Prayer - Griz', 'Never Change - Jay Z']
Хэши
Это просто добавление и получение данных из хэшей:
record = {
"name": "PythonRu",
"description": "Redis tutorials",
"website": "https://pythonru.com/"
}
r.hmset('business', record)
print(f"business: {r.hgetall('business')}")
Вывод такой же, как и ввод:
business: {'name': 'PythonRu', 'description': 'Redis tutorials', 'website': 'https://pythonru.com/'}