Как делался счётчик посещений на iNternets.kz

Игорь Бородихин 2010 M09 11
2685
4
0
0

В продолжение предыдущего поста Асинхронность PHP сегодня я расскажу, как применялись эти знания на практике. Больше двух лет назад впервые увидел свет очередной мой проект - каталог казахстанских...

В продолжение предыдущего поста Асинхронность PHP сегодня я расскажу, как применялись эти знания на практике.

Больше двух лет назад впервые увидел свет очередной мой проект - каталог казахстанских сайтов iNternets.kz. Тогда это действительно был только каталог сайтов, но очень быстро пришло понимание того, что такой сайт не особо интересен нашим веб-мастерам и недостаточно конкурентоспособен. Я начал обвешивать его дополнительными функциями - проверка ТИЦ и PR, оценка семантичности вёрстки и, как естественное продолжение каталога сайтов - система статистики.

Изначально было принято решение, что основным станет невидимый счётчик, а графический будет опцией и размещение любого счётчика (как и обратной ссылки) будет сугубо добровольным.

Первая версия счётчика производила запись в таблицу MySQL напрямую, из этой же таблицы с помощью чудовищных COUNT'ов и группировок вытаскивались отчёты. Когда я разместил этот счётчик на двух сайтах, посещаемостью 2000 уникальных визитов в сутки, время ответа скрипта счётчика составило больше секунды, а отчёты создавались за 8-10 секунд. Отвратительно.

Во второй версии данные записывались уже в несколько табличек (отдельно лог по разрешениям экранов, по достигнутым целям, по странам и т.п.). Кроме того, таблички чистились от старых данных (больше месяца). Это ускорило генерацию отчётов, но счётчик по-прежнему тормозил.

В третьей версии решено было сделать счётчик асинхронным - общий по всем сайтам лог посещений записывался в базу SQLite, а cron-скрипт раз в минуту разбирал её и обновлял таблицы в MySQL'е. Это позволило сильно ускорить генерацию счётчика и я расхрабрился настолько, что установил его на сайт с ежедневной посещаемостью около 20 000.

Этот шаг вскрыл ещё одну проблему. Теперь за выделенную минуту не успевала обрабатываться очередь в SQLite-базе. Не спасала блокировка с помощью pid-файла: очередь росла быстрее, чем скрипт её обрабатывал.

Чтобы решить эту проблему, я стал записывать посещения в 10 разных файлов по схеме:
$db_filename = (time() %10) . ".log";
А обработку очереди я разделил на 10 независимых "потоков" (потоками в полном смысле этого слова такое решение назвать никак нельзя, но и полноценные потоки здесь, в принципе, не нужны):

$dh = opendir($counter_log_dir);
while($file = readdir($dh)) {
if(preg_match("/\.log$/i", $file)) {
system("php -q counter_thread.php " . $file)
}

closedir($dh);

Теперь очередь успевает обработаться за минуту, в отчётах всегда актуальные данные, но встала проблема с хостингом. Хостер активно урезает проекту CPU, так что следующим шагом будет переход на VPS или хотя бы отселение на отдельный аккаунт (сейчас рядом с iNternets'ами проживает достаточно прожорливый новостной сайт, о котором я, может быть, расскажу в следующих постах).

Оцените пост

0

Комментарии

0
только сейчас увидел эту запись. а когда вы использовали SQLite, разве вы не сталкнулись с такой проблемой как lock файла базы данных при обращении к одной и той же базе из нескольких потоков? И не совсем понятно для чего был нужен этот шаг? Разве ни проще было напрямую вставлять в MySql в какой либо queue table, где можно было создать колонку thread_id со значениями [1-10]. И далее так же обрабатывать эти записи используя cron и уже заполнять таблицы с окончательными отчётами. Так же я предложил бы использовать что то вроде Redis или MongoDB, но это только если есть VPS.
0
Проблем с локами пока не возникало. SQLite работает значительно быстрее MySQL на моём хостинге. Очередь в MySQL была промежуточным шагом, скрипт отвечал через 800-900 мсек, что на мой взгляд неприлично много. Redis и MongoDB я бы использовал с радостью, но всё упирается shared-хостинг :)
0
Да, почему вероятность локов довольно низка. Имя базы формируется как time()%10. Вероятность того, что в одну секунду в один файл попытаются писать два счётчика достаточно низка. Если же всё-таки локи возникнут я заменю имя файла на rand(0, 10) - вероятность станет ещё ниже. Потоки, обрабатывающие очередь, в общем-то, в основном читают из файлов, что, согласно документации, файлы не лочит.
0
да, я имел в виду именно запись в SQLite.
800-900ms это конечно медленно :(
Показать комментарии