За последние 24 часа нас посетили 9690 программистов и 466 роботов. Сейчас ищут 236 программистов ...

Храним сессии в мемкэше и считаем число юзеров Online

Тема в разделе "Решения, алгоритмы", создана пользователем Sergey89, 24 июл 2008.

  1. Sergey89

    Sergey89 Активный пользователь

    С нами с:
    4 янв 2007
    Сообщения:
    4.796
    Симпатии:
    0
    Продолжая тему кэширования.

    Мемкэш прекрасно подходит для хранения сессий, но как узнать число активных пользователей и выделить среди этих пользователей зарегистрированных? Напрашивается решение, с использованием MEMORY таблицы MySQL. Но вот беда. В MEMORY нельзя использовать поля типа TEXT и соотвественно нельзя сохранить даже небольшой объём данных (ограничиваемся 255 символами поля типа CHAR/VARCHAR).

    Какой отсюда выход? Объединить две методики. В мемкэше хранить данные, в MEMORY таблице только информацию о пользователе.

    PHP:
    1. <?php
    2. require_once './Session/Session.php';
    3. require_once './Cache/Cache.php';
    4. require_once './Db/Db.php';
    5. require_once './Toolkit/Toolkit.php';
    6.  
    7. class MySessionStorage extends MemorySessionStorage {
    8.     private $db;
    9.     private $userId = 0;
    10.  
    11.     public function __construct() {
    12.         parent::__construct(Toolkit::instance()->getCache());
    13.         $this->db = Toolkit::instance()->getDb();
    14.     }
    15.  
    16.     public function setUserId($id) {
    17.         $this->userId = $id;
    18.     }
    19.  
    20.     public function getUserId() {
    21.         return $this->userId;
    22.     }
    23.  
    24.     public function getOnlineCount() {
    25.         $query = '
    26.            SELECT COUNT(*) AS count
    27.            FROM session
    28.            WHERE ses_time > (UNIX_TIMESTAMP() - 300)
    29.        ';
    30.  
    31.         return $this->db->query($query)->fetchOne('count');
    32.     }
    33.  
    34.     public function read($session_id) {
    35.         $query = '
    36.            SELECT user_id FROM session WHERE ses_id = ?
    37.        ';
    38.         $this->userId = $this->db->query($query, $session_id)->fetchOne('user_id');
    39.  
    40.         return parent::read($session_id);
    41.     }
    42.  
    43.     public function write($session_id, $data) {
    44.         $query = '
    45.            REPLACE INTO session (ses_id, user_id, ses_time) VALUES (?, ?, UNIX_TIMESTAMP())
    46.        ';
    47.         $this->db->query($query, array($session_id, $this->userId));
    48.  
    49.         return parent::write($session_id, $data);
    50.     }
    51. }
    52.  
    53. try {
    54.     Toolkit::instance()->setDb(new Db(array('server' => 'localhost', 'user' => 'root', 'database' => 'test')));
    55.     Toolkit::instance()->setCache(Cache::factory('memory'));
    56.  
    57.     $session = new Session(new MySessionStorage());
    58.     $session->start();
    59.    
    60.     if (!$session->has('time')) {
    61.         $session->set('time', time());
    62.     }
    63.  
    64.     if (!$session->getStorage()->getUserId()) {
    65.         $session->getStorage()->setUserId(321);
    66.     }
    67.  
    68.     print 'Online: ' . $session->getStorage()->getOnlineCount();
    69. } catch (Exception $e) {
    70.     print $e->getMessage();
    71. }
    [sql]CREATE TABLE `session` (
    ses_id char(32) NOT NULL default '',
    user_id int(11) NOT NULL default '0',
    ses_time int(10) unsigned NOT NULL default '0',
    PRIMARY KEY (ses_id)
    ) ENGINE=HEAP DEFAULT CHARSET=utf8;[/sql]

    Сорсы тут: http://sergey89.ru/files/php/memcached-sessions.tar.gz

    p.s. может я немного не выспался и не увидел более простого решения. просветите.
     
  2. Dagdamor

    Dagdamor Активный пользователь

    С нами с:
    4 фев 2006
    Сообщения:
    2.095
    Симпатии:
    1
    Адрес:
    Барнаул
    Sergey89
    Все правильно, нефиг в сессиях хранить большие объемы данных. Не для этого они придуманы.
    Сам всегда храню сессии в MEMORY таблице.
     
  3. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    озадачили :?
    хм, я вот раньше делал так:
    таблица session (userid или 0, если %anonymous%; hash=md5(ip+useragent), только если %anonymous%; timestamp последнего действия; location), далее просто таблица.
    при открытии любой страницы, удаляем из таблицы все записи, где timestamp отличается от текущего time() на 5 минут
    засылаем или обновляем свою запись
    выводим все записи, что там есть, и на основе их формируем статистику.

    /me наверно быдлдокодер?
     
  4. Sergey89

    Sergey89 Активный пользователь

    С нами с:
    4 янв 2007
    Сообщения:
    4.796
    Симпатии:
    0
    Это не оптимально. Делать лишний запрос при каждой загрузке страницы. Лучше повесить GC на крон.
     
  5. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    но в целом такой алгоритм имеет право на существование? С сессиями просто как-то замудрено.
     
  6. EugeneTM

    EugeneTM Активный пользователь

    С нами с:
    19 апр 2008
    Сообщения:
    85
    Симпатии:
    0
    Народ вы че.
    Совсем мануал читать перестали.
    VARCHAR хрен знает с какой дремучей версии 64К размер.
    Если кодировка UTF8 - 64K/3
     
  7. Sergey89

    Sergey89 Активный пользователь

    С нами с:
    4 янв 2007
    Сообщения:
    4.796
    Симпатии:
    0
    А ну ка, добавь мне такое поле в HEAP таблицу. Чтобы 64K/3 было.
     
  8. EugeneTM

    EugeneTM Активный пользователь

    С нами с:
    19 апр 2008
    Сообщения:
    85
    Симпатии:
    0
    А какие проблемы?

    [sql]mysql> use test;
    Database changed
    mysql> DROP TABLE IF EXISTS `test`.`tst`;
    Query OK, 0 rows affected (0.83 sec)

    mysql> CREATE TABLE `test`.`tst` (
    -> `vrchar` varchar(64000) default NULL,
    -> KEY `vrchar` USING BTREE (`vrchar`(1024))
    -> ) ENGINE=MEMORY DEFAULT CHARSET=utf8;
    ERROR 1074 (42000): Column length too big for column 'vrchar' (max = 21845); use
    BLOB or TEXT instead

    mysql> DROP TABLE IF EXISTS `test`.`tst`;
    Query OK, 0 rows affected, 1 warning (0.02 sec)

    mysql> CREATE TABLE `test`.`tst` (
    -> `vrchar` varchar(21000) default NULL,
    -> KEY `vrchar` USING BTREE (`vrchar`(1024))
    -> ) ENGINE=MEMORY DEFAULT CHARSET=utf8;
    Query OK, 0 rows affected (0.09 sec)

    mysql>[/sql]
     
  9. Sergey89

    Sergey89 Активный пользователь

    С нами с:
    4 янв 2007
    Сообщения:
    4.796
    Симпатии:
    0
    Точно. Пытался сменить на непустой таблице. Тогда мемкэш в топку %)
     
  10. Dagdamor

    Dagdamor Активный пользователь

    С нами с:
    4 фев 2006
    Сообщения:
    2.095
    Симпатии:
    1
    Адрес:
    Барнаул
    EugeneTM
    Пипец решение... теперь подумай, сколько будет жрать памяти такая таблица, если на сайт зайдет человек 1000, плюс учитывая устаревшие, но еще не удаленные сессии? Таблица-то FIXED типа, а не DYNAMIC, и все поля всегда занимают по максимуму.
     
  11. Sergey89

    Sergey89 Активный пользователь

    С нами с:
    4 янв 2007
    Сообщения:
    4.796
    Симпатии:
    0
    Dagdamor, ну 21 килобайт навряд ли понадобится.
     
  12. EugeneTM

    EugeneTM Активный пользователь

    С нами с:
    19 апр 2008
    Сообщения:
    85
    Симпатии:
    0
    А кто сказал что столько нужно?
    Это во первых.

    Во вторых - даже если 100К на сессию * 1000 = 100М
    Это что - много? Да и memcached не в вакууме живет - тоже память ест. Несколько меньше конечно, но не на порядки.
    У БД плюс возможность обработки запросами , особенно групп записей.
    Велосипедеразм по прикручиванию к индексам memcashed подобного функционала хуже на много.

    Просто смотреть нужно - что от сессий нужно. Если тупо SID держать , нах БД не нужна. А если я дуплю update'ми во время сеанса все телодвижения пользователя и хрен какой сервак их потянет если на диск буду каждый пихать. А по gc handler'у, обработанные одним запросом, итоги по всем устаревшим пользователям в базу пихаю. Причем при обработке цепляю и данные из самой базы. Расскажи мне - как ты это с memcashed сделаешь?
    Только не надо квадратные колеса строить из обновим данные memcashed, потом на клиента их притянем, потом на клиента из БД данных заберем, потом чегонить посчитаем , а потом как запрос построим , да в базу как затолкаем.

    Реальный гемор - контроль max heap size и возможность сваливания в своп.
    Хотя если продуманно сделать - решаемо.
     
  13. Dagdamor

    Dagdamor Активный пользователь

    С нами с:
    4 фев 2006
    Сообщения:
    2.095
    Симпатии:
    1
    Адрес:
    Барнаул
    EugeneTM
    Да, это много. Не на каждом хостинге тебе позволят вот так, за красивые глаза, скушать 100 Мб оперативной памяти.

    Я полностью согласен, что сессии надо хранить в БД, а не в мемкеше. Я говорю, что "тяжелых" сессий быть не должно. Что у тебя там такого хранится, что для каждого пользователя надо постоянно несколько десятков, или даже сотен, килобайт, сессионных данных? Мне в голову приходят только данные формы. Но это надо делать аяксом (храня данные между запросами в браузере клиента, а не на сервере), ну или в худшем случае хранить такие данные в отдельной таблице, обычной, а не MEMORY, только для тех. кому это действительно нужно, а не для всех подряд.
     
  14. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    5
    Адрес:
    Рига, Латвия
    Собственно правильное решение у человека, потому что если вам нужна функциональность "User is online" и при этом в сессии должно храниться достаточно прилично данных, то это база + memcached либо какая-то собственная разработка, которая умеет хранить сессии, делать им expire, обновлять и выдавать из них данные.

    Для базы FIXED не проблема, ведь не нужно пихать в базу кучу полей. Хватает UID + last_access, а это не много весит. При md5 UID сессии + timestamp это 36 байт на юзера. При 10 000 юзеров это ~352 KB, что очень мало. Всё остальное в memcached.
     
  15. EugeneTM

    EugeneTM Активный пользователь

    С нами с:
    19 апр 2008
    Сообщения:
    85
    Симпатии:
    0
    По порядку

    Если у тебя больше 1 000 пользователей одновременно сидят и интенсивно юзают БД, то 100М рамы на фоне остальных потребностей - мелочь. И речь идет уже не о хостинге , а о своих серваках. Потому что не только по памяти тебе не позволят.

    Чего и где хранить - смотреть надо по задаче, а не искать серебрянную пулю на все случаи жизни.

    Про что там хранится - в моем посте написано, читай.
    Про формы там ни слова.
     
  16. Dagdamor

    Dagdamor Активный пользователь

    С нами с:
    4 фев 2006
    Сообщения:
    2.095
    Симпатии:
    1
    Адрес:
    Барнаул
    EugeneTM
    Этот набор слов я должен быть прочитать? ;)
    И где здесь написано, какие данные ты предлагаешь таким макаром хранить в сессиях?
    Главное - с чего ты взял, что я за мемкеш? Я же по-русски написал в начале темы:
     
  17. EugeneTM

    EugeneTM Активный пользователь

    С нами с:
    19 апр 2008
    Сообщения:
    85
    Симпатии:
    0
    Почему сессии использовать для хранить данные?
    К ним можно туеву хучу функционала прицепить , и получить охрененно более эффективное приложение.

    ЗЫ
    Ну а если все что пишут набор слов который ты читать не должен, но ответить считаешь правильным...
    Мож ты просто чего недопонимаешь
     
  18. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    5
    Адрес:
    Рига, Латвия
    Так, всё, оба по углам и перерыв, а то придётся разнимать по плохому! :)