NoSQL обычно воспринимается как альтернатива реляционным БД, однако, многие из них, особенно, те, что попроще, могут не только заменять, но и отлично дополнять их. На самом деле, чтобы использовать какое-то NoSQL-решение вместо привычной БД, нужен либо новый проект, либо возможность переписать старый практически полностью. Редкие случаи, в повседневной разработке. В то же время можно легко сорвать множество низко висящих плодов.
Речь пойдёт о Redis, потому, что он хорош, поддерживает всякие полезные структуры и просто мне нравится. Я расскажу о нескольких вариантах облегчить себе жизнь с помощью него, применяемых мной в реальных проектах.
Ключ — значение
Основа редиса, может использоваться для замены memcached. Кеш, сессии и т.п. Частенько персистентность бывает нелишней: долгоиграющий кеш, сессии. Но это слишком очевидно и потому скучно, идём дальше.
Счётчики
Задача — есть какие-то сущности, к примеру, посты, для которых нужно отображать количество просмотров. Решение простецкое — при просмотре поста выполняем
INCR post:
и получаем в качестве ответа число хитов, при отсутствии ключа он будет создан, значение увеличено до 1 и возвращено, так что нам даже не нужна никакая инициализация. И всё работает очень быстро, потому как редис висит в памяти. Нам также не нужно беспокоится о сохранении чего-то куда-то, редис сохранит.
Можно использовать GET для получения значения счётчика без прибавления и MGET для получения сразу нескольких. Последнее удобно при отображении списка постов.
Топы
Немного усложним предыдущий пример. Пусть, вдобавок к числу хитов нам нужно выводить топ, список самых популярных постов. В таком случае обычные ключи уже недостаточно хороши, используем упорядоченные множества, к счастью, там тоже есть инкремент. Предыдущая команда меняется на:
ZINCRBY post 1
Эта команда, несмотря на свою кажущуюся простоту, делает сразу несколько вещей. Во-первых, создает упорядоченное множество 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
Т.к. это всё-таки множество хоть и упорядоченное, предыдущая запись с таким же id в guys_online будет заменена и останется только одна запись user_id — timestamp последнего хита. Чтобы получить количество ребят онлайн (за последние 15 минут):
ZCOUNT guys_online <unix_timestamp-15*60> +inf </unix_timestamp-15*60>
Чтобы получить их список просто используем ZRANGEBYSCORE вместо ZCOUNT. Конечно, множество guys_online будет постепенно забиваться, поэтому поставим в крон
ZREMRANGEBYSCORE guys_online -inf <unix_timestamp-15*60> </unix_timestamp-15*60>
Кеш с инвалидацией по событию
Обычный cпособ реализации инвалидации по событию — при возникновении события пробегаться по всем зависимым ключам кеша и стирать их. Минус здесь в излишней зависимости — обработчик события должен знать о куче кусочков кеша. При кешировании какого-то нового кусочка необходимо добавить его инвалидацию в обработчик события, а то и в несколько обработчиков. Ужасно, неудобно, запутанная связность кода.
Есть другой способ. При сохранении чего-то в кеш добавляем инвалидатор(ы):
SET
SADD # cache_key теперь зависит от event_name1
SADD # … event_name2
При возникновении события event_name cтираем все зависимые ключи кеша и инвалидатор, указывающий на них:
my @cache_keys = SMEMBERS ;
DEL @cache_keys
От лишней зависимости избавились теперь инвалидирующие события определяются там же где пишется кеш. И общий обработчик для событий можно написать. Кстати, примерно так всё и делается только в промышленных масштабах в cacheops.
Что дальше?
Дальше можно почитать статью с аналогичной идеей, но с другими примерами от создателя редиса. Можно приспособить редис к своим задачам, а можно обратить внимание на то, как легко и естественно решаются многие задачи с помощью редиса, и поностальгитровать с лёгким содроганием по временам когда приходилось всё это впихивать в рамки реляционных БД.