Redis – key-value база данных, которая мало подходит для решения общих задач. В отличии от классических SQL решений (реляционных баз данных), Redis слабо подходит для хранения и агрегации долгохранимых данных и поддержания связи между ними.
Примеры работы с SQL базами данных из Python можно посмотреть здесь.
Несмотря на то, что Redis не может стать полноценной заменой SQL базе данных, он может быть отличным дополнением. Так, к примеру, за счёт простоты своего устройства и ориентации на работе с примитивными данными Redis имеет огромную скорость и позволяет обрабатывать в сотни раз больше запросов за секунду.
Redis как простой key-value на примере кеша
По сути, Redis хранит информацию парами: ключ-значение. И в самом первом приближении значением являются байтовые строки. Так, к примеру, мы можем построить на Redis кеш данных, полученных от нашей основной базы данных. Или даже закешировать целиком ответ веб-сервера - какую-то страницу. Redis хранит мнформацию в оперативной памяти, поэтому будьте аккуратны и кешируйте только частоиспользуемые страницы / данные.
Для того, чтобы работать с обычными байтовыми строками, нам понадобится библиотека для взаимодействия и пара запросов: на запись значения и на чтение:
import redis
def set_get_example():
conn = redis.Redis(host='localhost', port=6379, db=0)
conn.set('Привет', 'Мир')
print(conn.get('Привет')) # b'Мир'
conn.delete('Привет')
print(conn.get('Привет')) # None
Создаём соединение, указав: хост, порт и номер базы данных (в редисе они обозначены целыми числами) –
redis.Redis(host='localhost', port=6379, db=0)
.
Устанавливать значения можно через conn.set('Привет', 'Мир')
–
пусть мы и используем в коде строки, Redis приведёт их к байтам. Первым параметром идёт ключ, вторым – значение.
Для получения значения воспользуемся conn.get('Привет')
, который в нашем случае вернёт байты закодированной в UTF-8
строки, если данный ключ существует. Если не существует, то результат будет None
. Так, к примеру, мы удалили
ключ с помощью conn.delete('Привет')
.
Также помним, что кеш нужно периодически обновлять. Как вариант – обнулять его по таймеру. В Redis для этого есть возможность указать время жизни ключа (expire):
from time import sleep
import redis
def set_get(expire=1):
conn = redis.Redis(host='localhost', port=6379, db=0)
conn.set('Привет', 'Мир', ex=expire)
print('После создания:', conn.get('Привет'))
sleep(expire)
data = conn.get('Привет')
if data is None:
print('По истечению указанного срока ключ пропал')
В данном примере мы по умолчанию указываем время жизни ключа в 1 секунду, по прошествию которой ключ пропадёт.
Hash-и или "одноуровневые словари" в Redis
В случае, если нам нужно хранить оперативные данные в более сложном виде – разделённые полями, то нам подойдёт тип данных "хеш". В Redis он позволяет хранить в качестве значений пары ключ-значение (да, этакий редис внутри значения редиса).
def hashes():
conn = redis.Redis(host='localhost', port=6379, db=0)
conn.hmset('user-1', {
'name': 'Joe',
'sex': 'male',
'age': 29,
})
conn.hmset('user-2', {
'name': 'Rachel',
'sex': 'female',
'age': 28,
})
for key in conn.scan_iter('user-*'):
user = conn.hgetall(key)
print(f'{user[b"name"]}: {user[b"age"]}')
В данном примере мы храним по ключам "user-$id" информацию о пользователях. Это также может быть, например, информация о персонаж онлайн в компьютерной игре. Однако, правильность ключей (уникальность для каждого пользователя) нам придётся поддерживать на уровне логики приложения - за счёт добавления в ключ суффикса идентификатора пользователя.
Как можно увидеть из примера, задаём значения ключей мы с помощью команды hmset
- для нескольких полей,
либо hset
- для одного поля хеша. Мы также можем получить сразу все поля указанного хеша с помощью команды hgetall
,
либо для части - hmget
, а для одного поля воспользоваться командой hget
.
Так как пользователей может быть много, для получения информации по ним следует использовать scan_iter
. Данная
команда позволит итеративно пройтись по всем ключам, соответствующим указанному шаблону.
Также заметьте, что ключи полученных словарей будут байтовыми строками. Как уже говорилось раньше, в Redis ключами являются байтовые строки.
Множества в Redis
Как и в Python, в Redis есть тип значений "set" - "множество". И также поддерживаются операции из теории множеств: пересечение, объединение и т.д. Возможно, сложно представить, что в реальном сервисе может потребоваться подобное. Однако, такое вполне случается.
Например, если нам нужно хранить информацию об онлайне пользователей, чтобы была возможность потом посмотреть: в какие дни больше интереса к сервису, как часто пользователи возвращаются на следующий день и прочие метрики. Если у нас возникла такая задача, то мы можем по событию пользователя добавлять его в специальный set, в котором будет храниться информация по онлайну пользователей за сутки. Взяв два таких множества, мы можем, например, получить информацию по возвратам относительно предыдущего дня:
def user_stat():
conn = redis.Redis(host='localhost', port=6379, db=0)
for n in range(3000):
conn.sadd('online-stat-2020-01-01', *[n*3000 + x for x in range(0, 100) if x % 2])
for n in range(3000):
conn.sadd('online-stat-2020-01-02', *[n*3000 + x for x in range(0, 100) if x % 3])
start_time = time()
conn.sinterstore(
'online-stat-returned-2020-01-02',
'online-stat-2020-01-01',
'online-stat-2020-01-02'
)
print(
'Пользователей, что были онлайн 2020-01-01 и 2020-01-02:',
conn.scard('online-stat-returned-2020-01-02')
) # Пользователей, что были онлайн 2020-01-01 и 2020-01-02: 99000
cur_id, data = conn.sscan('online-stat-returned-2020-01-02', count=10)
print('Вот некоторые из них:', data) # Вот некоторые из них: [b'2394071', b'4011089', ...]
cur_id, data = conn.sscan('online-stat-returned-2020-01-02', cursor=cur_id, count=10)
print('И ещё несколько:', data) # И ещё несколько: [b'5706071', b'8361005', b'5820019', ...]
print('Затраченное время:', time() - start_time) # Затраченное время: 0.07380294799804688
Для начала с помощью sadd
добавляем 3000 раз сразу по 50 записей идентификаторы пользователей для первого дня.
И аналогично для второго, но уже по 66 записей, чтобы были пересечения но не полные.
Для хранения пересечения множеств также будет создано множество online-stat-returned-2020-01-02
.
Само пересечение создаём командой sinterstore
. Размер (мощность) множества получаем командой Redis scard
.
Также можно итеративно получить значения с помощью sscan
. Только не забудьте указать количество получаемых значений,
иначе можно затратить очень много памяти. Первым параметром вернётся идентификатор курсора, который можно использовать
для следующего запроса, чтобы прочитать дальше.
Сортированные множества в Redis
Они уже были ранее описаны в заметке "Сортированные множества в Redis".
Если коротко, сортированные множества в Redis подходят для хранения уникальных элементов в отсортированном по какому-то значению порядке. То может быть множество последних залогиненных пользователей (по unixtime времени логина, например). Или же топ игроков на сервере ММО игры (по количеству очков опыта). А может и рейтинговая таблица чемпионата по футболу. Суть одна - мы эффективно храним уникальные элементы для получения их в порядке какой-то метрики.
def scored_set():
conn = redis.Redis(host='localhost', port=6379, db=0)
conn.zadd('football', {'Спартак': 2})
conn.zadd('football', {'Локомотив': 1})
conn.zadd('football', {'Ценит': 0})
for team in conn.zrange('football', 0, 100):
print(team.decode())
conn.zincrby('football', 0, 'Спартак')
conn.zincrby('football', 1, 'Локомотив')
for team, score in conn.zrevrangebyscore('football', 100, 0, withscores=True):
print((team.decode(), score))
Так в данном примере мы добавляем в сортированное множество, находящееся по ключу football
элементы - названия клубов
с их "метриками" - очками побед в некоей выдуманной лиге. Делается это командой zadd
- вообще команды, связанные
с сортированными множествами, начинаются с 'z'.
Для получения элементов с минимальными значениями используем zrange
с параметрами "от" и "до" (в значениях метрики).
Также не забываем про то, что элементы также хранятся в виде байтов - декодируем.
Предположим две команды сыграли матч - добавляем очков командой zincrby
(увеличить на столько-то тому-то).
И чтобы получить таблицу лидеров - запрашиваем элементы со значением метрики от больших к меньшим, от 100 до 0.
Поможет с этим команда с занятным названием - zrevrangebyscore
.
Также можете почитать "Маленькую книгу о Redis".
Про списки будет отдельная заметка с более развёрнутым примером. Само собой, она будет здесь.