Как я могу улучшить этот новостной канал PHP/MySQL?

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

Этот вопрос/работа строится некоторое обсуждение 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, которое выполняет script ниже, Этот script проходит через всех пользователей в базе данных, находит все действия для всех пользователей этого пользователя, а затем записывает эти действия в ленту новостей.

В настоящий момент 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 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'];

}

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

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

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

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

Ответ 1

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

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

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

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

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

  • Это не очень хорошо масштабируется.

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

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

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

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

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

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

Ответ 2

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

Ответ 3

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

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

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

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

Ответ 4

Я пытаюсь создать фид новостей в стиле Facebook самостоятельно. Вместо создания другой таблицы для регистрации действий пользователей я вычислил "край" из UNION сообщений, комментариев и т.д.

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

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

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

Ответ 5

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

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

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

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

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

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

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

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

JSH