Слава нації
#StandWithUkraine

Роман Теличко

Блог з програмування, оптимізації та адміністрування систем

Перепост: Redis: лёгкие яблоки

// | 331 переглядів

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  +inf 

Чтобы получить их список просто используем ZRANGEBYSCORE вместо ZCOUNT. Конечно, множество guys_online будет постепенно забиваться, поэтому поставим в крон

ZREMRANGEBYSCORE guys_online -inf  

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

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

Есть другой способ. При сохранении чего-то в кеш добавляем инвалидатор(ы):

SET   
SADD   # cache_key теперь зависит от event_name1 
SADD   # … event_name2 

При возникновении события event_name cтираем все зависимые ключи кеша и инвалидатор, указывающий на них:

my @cache_keys = SMEMBERS ;
DEL @cache_keys  

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

Что дальше?

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

Оригинал: https://habrahabr.ru/blogs/nosql/129185/