Как я могу улучшить этот канал новостей PHP / MySQL? - PullRequest
70 голосов
/ 12 ноября 2010

Позвольте мне сразу начать, сказав, что я знаю, что это не лучшее решение.Я знаю, что это глупо и чертовски многозначительно. Но вот почему я здесь!

Этот вопрос / работа строится на некотором обсуждении Quora с Эндрю Босвортом , создателем новостной ленты Facebook.

Я создаю ленту новостей сортов.Он построен исключительно в PHP и MySQL.

alt text


MySQL

Реляционная модель для канала состоит из двух таблиц.Одна таблица функционирует как журнал активности;на самом деле он называется activity_log.Другая таблица - newsfeed. Эти таблицы почти идентичны.

Схема для журнала - activity_log(uid INT(11), activity ENUM, activity_id INT(11), title TEXT, date TIMESTAMP)

... и схема дляфид - это newsfeed(uid INT(11), poster_uid INT(11), activity ENUM, activity_id INT(11), title TEXT, date TIMESTAMP).

Каждый раз, когда пользователь делает что-то соответствующее ленте новостей, например, задает вопрос, он регистрируется в журнале активности немедленно.


Генерация новостных лент

Затем каждые X минут (в данный момент 5 минут изменится на 15-30 минут спустя), Я запускаю задание cron , которое выполняет приведенный ниже скрипт.Этот сценарий проходит по всем пользователям в базе данных, находит все действия для всех друзей этого пользователя, а затем записывает эти действия в ленту новостей.

В данный момент SQL, который отбрасываетактивность (называемая ActivityLog::getUsersActivity()) имеет LIMIT 100, наложенную по соображениям производительности *.* Не то, чтобы я знал, о чем говорю.

<?php

$user = new User();
$activityLog = new ActivityLog();
$friend = new Friend();
$newsFeed = new NewsFeed();

// Get all the users
$usersArray = $user->getAllUsers();
foreach($usersArray as $userArray) {

  $uid = $userArray['uid'];

  // Get the user's friends
  $friendsJSON = $friend->getFriends($uid);
  $friendsArray = json_decode($friendsJSON, true);

  // Get the activity of each friend
  foreach($friendsArray as $friendArray) {
    $array = $activityLog->getUsersActivity($friendArray['fid2']);

    // Only write if the user has activity
    if(!empty($array)) {

      // Add each piece of activity to the news feed
      foreach($array as $news) {
        $newsFeed->addNews($uid, $friendArray['fid2'], $news['activity'], $news['activity_id'], $news['title'], $news['time']);
      }
    }
  }
}

Отображение новостных лент

В коде клиента при получении новостной ленты пользователя я делаю что-то вроде:

$feedArray = $newsFeed->getUsersFeedWithLimitAndOffset($uid, 25, 0);

foreach($feedArray as $feedItem) {

// Use a switch to determine the activity type here, and display based on type
// e.g. User Name asked A Question
// where "A Question" == $feedItem['title'];

}

Улучшение новостной ленты

Теперь простите мое ограниченное понимание лучших практик по разработке новостной ленты, но я понимаю подход, который я использую, чтобы бытьограниченная версия так называемого разветвления при записи , ограниченная в том смысле, что я выполняю задание cron в качестве промежуточного этапа вместо прямой записи в новостные ленты пользователей.Но это очень отличается от модели извлечения, в том смысле, что новостная лента пользователя не компилируется при загрузке, а скорее на регулярной основе.

Это большой вопрос, который, вероятно, заслуживает большого количества обратнойи далее, но я думаю, что это может послужить пробным камнем для многих важных разговоров, которые нужны новым разработчикам, таким как я.Я просто пытаюсь выяснить, что я делаю не так, как я могу улучшить, или как я, возможно, даже должен начать с нуля и попробовать другой подход.

Еще одна вещь, которая меня беспокоит в этой моделиявляется то, что он работает на основе актуальности, а не актуальности.Если кто-нибудь может подсказать, как это можно улучшить, чтобы работать в релевантности, я был бы всем заинтересован.Я использую API Directed Edge для генерации рекомендаций, но кажется, что для чего-то вроде новостной ленты рекомендатели не будут работать (так как ранее ничего не было одобрено!).

Ответы [ 5 ]

12 голосов
/ 30 июня 2011

Действительно классный вопрос. Я на самом деле в процессе реализации чего-то подобного сам. Итак, я собираюсь немного подумать.

Вот недостатки, которые я вижу в вашей текущей реализации:

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

  2. Если кто-то из моих друзей что-то опубликует, он не будет отображаться в моей ленте новостей не более 5 минут. Принимая во внимание, что это должно появиться немедленно, правильно?

  3. Мы читаем всю ленту новостей для пользователя. Разве нам не нужно просто захватывать новые действия с тех пор, как мы в последний раз хрустнули журналы?

  4. Это не так хорошо масштабируется.

Лента новостей выглядит точно так же, как и журнал активности, я бы придерживался этой таблицы журнала активности.

Если вы осколите свои журналы активности между базами данных, это позволит вам легче масштабировать. При желании вы также можете осколить своих пользователей, но даже если у вас есть 10 миллионов записей пользователей в одной таблице, mysql будет хорошо выполнять чтение. Таким образом, всякий раз, когда вы ищете пользователя, вы знаете, из какого сегмента получен доступ к журналам пользователя. Если вы архивируете свои старые журналы очень часто и сохраняете только новый набор журналов, вам не нужно будет так много разбрасывать. Или, может быть, даже вообще. Вы можете управлять многими миллионами записей в MySQL, если настроены даже умеренно хорошо.

Я бы использовал memcached для вашей таблицы пользователей и, возможно, даже для самих журналов. Memcached позволяет кэшировать записи размером до 1 МБ, и если вы умны в организации ваших ключей, вы можете извлечь все самые последние журналы из кэша.

Это было бы больше работы, если речь идет об архитектуре, но это позволит вам работать в режиме реального времени и масштабироваться в будущем ... особенно, если вы хотите, чтобы пользователи начали комментировать каждая публикация. ;)

Вы видели эту статью?

http://bret.appspot.com/entry/how-friendfeed-uses-mysql

0 голосов
/ 01 июля 2011

Вместо того, чтобы запускать задание cron, какой-то скрипт пост-фиксации. Я не знаю точно, каковы возможности PHP и MySQL в этом отношении - если я правильно помню, MySQL InnoDB допускает более расширенные возможности, чем другие варианты, но я не помню, есть ли такие вещи, как триггеры в последней версии.

в любом случае, простой вариант, который не зависит от большого количества магии базы данных:

когда пользователь X добавляет контент:

1) делать асинхронный вызов со своей страницы PHP после фиксации базы данных (конечно, асинхронно, чтобы пользователь, просматривающий страницу, не должен был ждать этого!)

Вызов запускает экземпляр вашего логического скрипта.

2) логический скрипт проходит только через список друзей [A, B, C] пользователя, который зафиксировал новый контент (в отличие от списка всех в БД!) И добавляет действие пользователя X для каналов каждого из этих пользователей.

Вы можете просто сохранить эти каналы в виде простых файлов JSON и добавить новые данные в конец каждого. Лучше, конечно, хранить каналы в кэше с резервной копией в файловую систему, или в BerkeleyDB, или в Mongo, или как вам угодно.

Это просто базовая идея для каналов, основанных на актуальности, а не релевантности. Вы МОЖЕТЕ хранить данные последовательно таким образом, а затем выполнять дополнительный анализ для каждого пользователя для фильтрации по релевантности, но это сложная проблема в любом приложении, и, вероятно, она не может быть легко решена анонимным веб-пользователем без подробного знание ваших требований;)

JSH

0 голосов
/ 30 июня 2011

Я пытаюсь создать ленту новостей в стиле Facebook самостоятельно.Вместо того, чтобы создавать другую таблицу для регистрации действий пользователей, я вычислял «край» из СОЮЗА постов, комментариев и т. Д.

Немного математики, я вычисляю «край», используя модель экспоненциального затухания,с истекшим временем, являющимся независимой переменной, с учетом количества комментариев, лайков и т. д. каждый пост должен формулировать лямбда-константу.Сначала ребро будет быстро уменьшаться, но через несколько дней постепенно сглаживается почти до 0 (но никогда не достигнет 0)

При отображении подачи каждое ребро умножается с помощью RAND ().Посты с более высоким краем будут появляться чаще

Таким образом, более популярные посты с большей вероятностью будут появляться в новостной ленте в течение более длительного времени.

0 голосов
/ 26 июня 2011

между вы можете использовать пользовательские флаги и кеширование.Допустим, есть новое поле для пользователя как last_activity.Обновляйте это поле каждый раз, когда пользователь вводит какие-либо действия.Сохраняйте флаг до того момента, пока вы не получите каналы, скажем, что feed_updated_on.

Теперь обновите функцию $ user-> getAllUsers ();возвращать только тех пользователей, у которых время last_activity позже, чем feed_updated_on.Это исключит всех пользователей, у которых нет журнала активности :).Аналогичный процесс для друзей пользователей.

Вы также можете использовать кэширование, например, memcache или кеширование на уровне файлов.

Или использовать некоторую базу данных nosql для хранения всех каналов как одного документа.

0 голосов
/ 12 ноября 2010

Вы бы добавили статистический ключевой текст? Я сделал (грубую) реализацию, взорвав тело моего документа, убрав HTML, удалив общие слова и посчитав самые распространенные слова. Я сделал это несколько лет назад просто для удовольствия (как и в любом таком проекте, исходный код пропал), но это сработало для моей временной настройки test-blog / forum. Может быть, это будет работать для вашей ленты новостей ...

...