Pull to refresh

Redis: лёгкие яблоки

Reading time 3 min
Views 10K
NoSQL обычно воспринимается как альтернатива реляционным БД, однако, многие из них, особенно, те, что попроще, могут не только заменять, но и отлично дополнять их. На самом деле, чтобы использовать какое-то NoSQL-решение вместо привычной БД, нужен либо новый проект, либо возможность переписать старый практически полностью. Редкие случаи, в повседневной разработке. В то же время можно легко сорвать множество низко висящих плодов.

Речь пойдёт о Redis, потому, что он хорош, поддерживает всякие полезные структуры и просто мне нравится. Я расскажу о нескольких вариантах облегчить себе жизнь с помощью него, применяемых мной в реальных проектах.

Ключ — значение

Основа редиса, может использоваться для замены memcached. Кеш, сессии и т.п. Частенько персистентность бывает нелишней: долгоиграющий кеш, сессии. Но это слишком очевидно и потому скучно, идём дальше.

Счётчики

Задача — есть какие-то сущности, к примеру, посты, для которых нужно отображать количество просмотров. Решение простецкое — при просмотре поста выполняем
INCR post:<id>
и получаем в качестве ответа число хитов, при отсутствии ключа он будет создан, значение увеличено до 1 и возвращено, так что нам даже не нужна никакая инициализация. И всё работает очень быстро, потому как редис висит в памяти. Нам также не нужно беспокоится о сохранении чего-то куда-то, редис сохранит.

Можно использовать GET для получения значения счётчика без прибавления и MGET для получения сразу нескольких. Последнее удобно при отображении списка постов.

Топы

Немного усложним предыдущий пример. Пусть, вдобавок к числу хитов нам нужно выводить топ, список самых популярных постов. В таком случае обычные ключи уже недостаточно хороши, используем упорядоченные множества, к счастью, там тоже есть инкремент. Предыдущая команда меняется на:
ZINCRBY post 1 <id>

Эта команда, несмотря на свою кажущуюся простоту, делает сразу несколько вещей. Во-первых, создает упорядоченное множество post, если его нет, во-вторых, добавляет в него элемент со счетом 0, если ещё не было и, в-третьих, увеличивает его счёт на 1. Т. е. делает всё необходимое для построения упорядоченного множества постов с количествами просмотров в качестве счетов.

Чтобы получить id 10 самых популярных постов достаточно выполнить:
ZREVRANGE post 0 9 WITHSCORES
Можно отбросить WITHSCORES, если числа просмотров нам не нужны.

Усложним задачу ещё немного, теперь мы хотим, чтобы старые посты со временем опускались если их перестают просматривать. Легко — просто будем периодически списывать по X% с каждого счёта (псевдокод на перле):
my $x = X / 100;
my %posts = ZRANGE post 0 -1 WITHSCORES;
while (my ($id, $score) = each %posts) {
    ZINCRBY post -$score*$x $id;
}

Ставим это в крон раз в день, готово. Старьё будет экспоненциально затухать, освобождая место новому.

Список посетителей на сайте

Может быть довольно хлопотной задачей при реализации традиционными способами. С редисом — легко. Каким-нибудь образом определим для юзера его id, это может быть действительно id из соответствующей таблицы, id сессии или ip + useragent. При хите сохраняем время последнего захода:
ZADD guys_online <unix_timestamp> <user_id>

Т.к. это всё-таки множество хоть и упорядоченное, предыдущая запись с таким же id в guys_online будет заменена и останется только одна запись user_id — timestamp последнего хита. Чтобы получить количество ребят онлайн (за последние 15 минут):
ZCOUNT guys_online <unix_timestamp-15*60> +inf

Чтобы получить их список просто используем ZRANGEBYSCORE вместо ZCOUNT. Конечно, множество guys_online будет постепенно забиваться, поэтому поставим в крон
ZREMRANGEBYSCORE guys_online -inf <unix_timestamp-15*60>

Кеш с инвалидацией по событию

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

Есть другой способ. При сохранении чего-то в кеш добавляем инвалидатор(ы):
SET <cache_key> <data>
SADD <event_name1> <cache_key>  # cache_key теперь зависит от event_name1
SADD <event_name2> <cache_key>  # … event_name2

При возникновении события event_name cтираем все зависимые ключи кеша и инвалидатор, указывающий на них:
my @cache_keys = SMEMBERS <event_name>;
DEL @cache_keys <event_name>

От лишней зависимости избавились теперь инвалидирующие события определяются там же где пишется кеш. И общий обработчик для событий можно написать. Кстати, примерно так всё и делается только в промышленных масштабах в cacheops.

Что дальше?

Дальше можно почитать статью с аналогичной идеей, но с другими примерами от создателя редиса. Можно приспособить редис к своим задачам, а можно обратить внимание на то, как легко и естественно решаются многие задачи с помощью редиса, и поностальгитровать с лёгким содроганием по временам когда приходилось всё это впихивать в рамки реляционных БД.
Tags:
Hubs:
+54
Comments 33
Comments Comments 33

Articles