Использование 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+9000

Redis хранит данные в оперативной памяти, поэтому перебор с размером базы данных может плохо сказаться на работе всего сервера. Для оценки использовалась пустая база данных (выполнил FLUSHALL перед этим) и вот этот небольшой скрипт на ruby. Для 9000 онлайн пользователей и 9000 онлайн анонимусов получилось так:

  1. пустая база данных: 810.75K
  2. 18000 записей: 3.49M

Аналогично для 65000 + 65000

  1. 130000 записей: 18.66M

Немножечко ссылок по теме (англ.):

ссылка на оригинал статьи http://habrahabr.ru/post/216047/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *