Кто желает знать кто из пользователей онлайн, но ещё не задумывался как
Для кого не проблема использовать Redis (по причине хостинга например)
Решение вопроса в сети пользователь или нет — это наверное как правило установка временной метки при обращении пользователя к приложению, а при необходимости узнать его (пользователя) текущий статус — сверка с этой временной меткой. Какой подход выбрать — решать Вам, но тот подход который предлагаю я — прост и не использует SQL базу данных, вместо этого используется Redis и одна из его встроенных возможностей — время жизни ключа (expire).
path:"/tmp/redis.sock" — использовать socket-подключение, если это возможно
driver: :hiredis — драйвер hiredis быстрее
db:15 — использовать определённую базу данных, по умолчанию используется нулевая, но я рекомендую оставить её для тестирования, прикладных задач, чего нибудь другого. Нет проблем в использовании именно нулевой базы данных — суть в том, чтобы она была строго определена под онлайн-пользователей и больше ни для чего.
Gemfile
# Gemfile
gem 'redis'
gem 'hiredis'# optional
Не забудьте запустить bundle
Устанавливаем в online
метод current_user
Метод current_user скорее всего уже используется Вами — это тот метод, который возвращает текущего пользователя или nil — если пользователь не вошёл.
defcurrent_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
defset_onlineif !!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 )
endendend
В сети?
# app/models/user.rbdefonline?# если время жизни ключа истекло - то вернёт false, иначе true
$redis_onlines.exists( self.id )
end
Небольшой бонус — список онлайн пользователей
# app/cpntrollers/application_controller.rbdefall_who_are_in_touch
$redis_onlines.keys
# => [ "123", "234", "1", "23" ]# вернёт массив с id онлайн пользователейend
На этом и всё
Небольшая переработка для anonymous
Для отслеживания анонимных посетителей (тех кто не зарегистрировался/не вошёл) подход аналогичный, с небольшим дополнением.
Установка в online
# app/controllers/application_controller.rbdefset_onlineif !!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 )
endelse
$redis_onlines.pipelined do# не вошедшему пользователю добавляем префикс "ip:" и записываем его id адрес
$redis_onlines.set( "ip:#{request.remote_ip}", nil )
$redis_onlines.expire( "ip:#{request.remote_ip}", 10*60 )
endendend
в сети?
# app/models/user.rbdefonline?
$redis_onlines.exists( "user:#{self.id}" )
end
список пользователей в сети
# app/cpntrollers/application_controller.rb# все вошедшие пользователи онлайн (массив с их id)defall_signed_in_in_touch
ids = []
$redis_onlines.scan_each( match:'user*' ){|u| ids << u.gsub("user:", "") }
ids
end# количество не вошедших пользователей онлайнdefall_anonymous_in_touch
$redis_onlines.scan_each( match:'ip*' ).to_a.size
end# количество всех пользователей онлайнdefall_who_are_in_touch
$redis_onlines.dbsize
end
Ну и совсем чуть чуть по поводу размера базы данных
9000+9000
Redis хранит данные в оперативной памяти, поэтому перебор с размером базы данных может плохо сказаться на работе всего сервера. Для оценки использовалась пустая база данных (выполнил FLUSHALL перед этим) и вот этот небольшой скрипт на ruby. Для 9000 онлайн пользователей и 9000 онлайн анонимусов получилось так: