Использование Redis EXPIRE для отслеживания онлайн-аудитории в Rails

КомуТем кто на Ruby on Rails Кто желает знать кто из пользователей онлайн, но ещё не задумывался как Для кого не проблема использовать Redis (по причине хостинга например)     Решение вопроса в сети пользователь или нет — это наверное как правило установка временной метки при обращении пользователя к приложению, а при необходимости узнать его (пользователя) текущий статус — сверка с этой временной меткой. Какой подход выбрать — решать Вам, но тот подход который предлагаю я — прост и не использует SQL базу данных, вместо этого используется Redis и одна из его встроенных возможностей — время жизни ключа (expire).Собственно реализацияИнит# config/initializers/redis.rb $redis_onlines = Redis.new Выше простейший подход, но я рекомендую следующий# config/initializers/redis.rb

$redis_onlines = Redis.new path:»/tmp/redis.sock», db: 15, driver: : hiredis path:»/tmp/redis.sock» — использовать socket-подключение, если это возможно driver: : hiredis — драйвер hiredis быстрее db: 15 — использовать определённую базу данных, по умолчанию используется нулевая, но я рекомендую оставить её для тестирования, прикладных задач, чего нибудь другого. Нет проблем в использовании именно нулевой базы данных — суть в том, чтобы она была строго определена под онлайн-пользователей и больше ни для чего. Gemfile# Gemfile

gem 'redis' gem 'hiredis' # optional Не забудьте запустить bundleУстанавливаем в onlineметод current_userМетод current_user скорее всего уже используется Вами — это тот метод, который возвращает текущего пользователя или nil — если пользователь не вошёл.def current_user @current_user ||= User.find_by_id (session[ : user_id ]) end # app/controllers/application_controller.rb after_filter: set_online # Для Rails 4 используйте: # after_action: set_online # после каждого запроса выполнить set_online

private

def set_online if! current_user # обёртка в pipelined для ускорения, два запроса пойдут как один без ожидания # ответа, использование multi-exec вместо pipelined # не даёт такого прироста в производительности $redis_onlines.pipelined do # не нужно значение, нужен только ключ $redis_onlines.set (current_user.id, nil) # устанавливаем время жизни ключа — 10 минут, через 10 мину ключ удалиться $redis_onlines.expire (current_user.id, 10×60) end end end В сети? # app/models/user.rb def online? # если время жизни ключа истекло — то вернёт false, иначе true $redis_onlines.exists (self.id) end Небольшой бонус — список онлайн пользователей# app/cpntrollers/application_controller.rb

def all_who_are_in_touch $redis_onlines.keys # => [ »123»,»234»,»1»,»23» ] # вернёт массив с id онлайн пользователей end На этом и всёНебольшая переработка для anonymous     Для отслеживания анонимных посетителей (тех кто не зарегистрировался/не вошёл) подход аналогичный, с небольшим дополнением.Установка в online # app/controllers/application_controller.rb def set_online if! current_user $redis_onlines.pipelined do # вошедшему пользователю к ключу добавляем префикс «user:» перед id $redis_onlines.set («user:#{current_user.id}», nil) $redis_onlines.expire («user:#{current_user.id}», 10×60) end else $redis_onlines.pipelined do # не вошедшему пользователю добавляем префикс «ip:» и записываем его id адрес $redis_onlines.set («ip:#{request.remote_ip}», nil) $redis_onlines.expire («ip:#{request.remote_ip}», 10×60) end end end в сети?# app/models/user.rb

def online? $redis_onlines.exists («user:#{self.id}») end список пользователей в сети# app/cpntrollers/application_controller.rb # все вошедшие пользователи онлайн (массив с их id) def all_signed_in_in_touch $redis_onlines.scan_each (match: 'user*').to_a end

# количество не вошедших пользователей онлайн def all_anonymous_in_touch $redis_onlines.scan_each (match: 'ip*').to_a.size end

# количество всех пользователей онлайн def all_who_are_in_touch $redis_onlines.dbsize end Ну и совсем чуть чуть по поводу размера базы данных9000+9000Redis хранит данные в оперативной памяти, поэтому перебор с размером базы данных может плохо сказаться на работе всего сервера. Для оценки использовалась пустая база данных (выполнил FLUSHALL перед этим) и вот этот небольшой скрипт на ruby. Для 9000 онлайн пользователей и 9000 онлайн анонимусов получилось так: пустая база данных: 810.75K 18000 записей: 3.49M Аналогично для 65000 + 65000130000 записей: 18.66M Немножечко ссылок по теме (англ.): Redis по поводу pipelined set | exists | expire | scan scan_each redis-rb неплохая статья для начинающих по Rails + Redis

© Habrahabr.ru