Как я могу улучшить эту ленту новостей PHP/MySQL?
позвольте мне начать с самого начала, сказав, что я знаю, что это не лучшее решение. Я знаю, что это kludgy и Хак функции. но именно поэтому я здесь!
этот вопрос/работа строится обсуждение на Quora с Эндрю Босворт, создатель ленты новостей Facebook.
я создаю ленту новостей сортов. Он построен исключительно в PHP
и MySQL
.
Для MySQL
реляционная модель канала состоит из двух таблиц. Одна таблица функционирует как журнал действий; фактически, она называется activity_log
. Другая таблица -newsfeed
. эти таблицы почти идентичны.
на схема для журнала is activity_log(uid INT(11), activity ENUM, activity_id INT(11), title TEXT, date TIMESTAMP)
...и схема подачи is 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 в качестве промежуточного шага вместо того, чтобы напрямую писать в новостные ленты пользователей. Но это сильно отличается от модели pull в том смысле, что лента новостей пользователя компилируется не при загрузке, а на регулярной основе основа.
это большой вопрос, который, вероятно, заслуживает большого количества назад и вперед, но я думаю, что он может служить пробным камнем для многих важных разговоров, которые должны иметь новые разработчики, такие как я. Я просто пытаюсь понять, что я делаю неправильно, как я могу улучшить, или как мне, возможно, даже начать с нуля и попробовать другой подход.
еще одна вещь, которая меня беспокоит в этой модели, заключается в том, что она работает на основе недавности, а не релевантности. Если кто-то может предложить, как это можно улучшить, чтобы работать в релевантности, я бы все уши. Я использую API Directed Edge для генерации рекомендаций, но кажется, что для чего-то вроде ленты новостей рекомендатели не будут работать (поскольку ранее ничего не было!).
5 ответов
очень классный вопрос. Я на самом деле в середине реализации чего-то подобного. Так что я немного подумаю вслух.
вот недостатки, которые я вижу в своем уме с вашей текущей реализацией:
вы обрабатываете всех друзей для всех пользователей, но в конечном итоге вы будете обрабатывать одних и тех же пользователей много раз из-за того, что у одних и тех же групп людей есть похожие друзья.
Если один из моих друзей публикует что-то, это не будет отображаться в моей ленте новостей в течение не более 5 минут. Тогда как он должен появиться немедленно, верно?
мы читаем всю ленту новостей для пользователя. Разве нам не нужно просто захватить новые действия с тех пор, как мы в последний раз хрустели бревнами?
Это не так хорошо масштабируется.
лента новостей выглядит так же, как и журнал активности, я бы придерживался этого журнала активности таблица.
Если вы shard журналы активности по базам данных, это позволит вам масштабировать проще. Вы также можете отбросить своих пользователей, если хотите, но даже если у вас есть 10 миллионов пользовательских записей в одной таблице, mysql должен быть в порядке. Поэтому всякий раз, когда вы ищете пользователя, вы знаете, какой осколок для доступа к журналам пользователя. Если вы архивируете свои старые журналы так часто и поддерживаете только новый набор журналов, вам не придется так много осколков. Или, может быть, даже на всех. Вы можете управлять многими миллионы записей в MySQL, если вы настроены даже умеренно хорошо.
Я бы использовал memcached для вашей таблицы пользователей и, возможно, даже самих журналов. Memcached позволяет кэшировать записи размером до 1 Мб, и если вы были умны в организации ключей, вы могли бы потенциально получить все самые последние журналы из кэша.
Это будет больше работы, Что касается архитектуры, но он позволит вам работать в режиме реального времени и данные в будущее...особенно, когда вы хотите, чтобы пользователи начали комментировать на каждой проводки. ;)
вы видели эту статью?
вы бы добавили статистический keywording? Я сделал (грубую) реализацию, взорвав тело моего документа, очистив HTML, удалив общие слова и подсчитав наиболее распространенные слова. Я сделал это несколько лет назад просто для удовольствия (как и в любом таком проекте, источник ушел), но он работал для моей временной настройки блога/форума. Возможно, это сработает для вашей ленты новостей...
между вами можно использовать пользовательские флаги и кэширование. Скажем, есть новое поле для пользователя как last_activity. Обновляйте это поле всякий раз, когда пользователь вводит какое-либо действие. Держите флаг, до тех пор, пока вы не извлекли каналы, скажем, feed_updated_on.
теперь обновите функцию $user - >getAllUsers (); чтобы вернуть только пользователей, которые имеют время last_activity позже feed_updated_on. Это исключит всех пользователей, у которых нет журнала действий :). Аналогичный процесс для пользователей друзья.
вы также можете использовать кэширование, как memcache или кэширование на уровне файлов.
или используйте некоторую NoSQL DB для хранения всех каналов как одного документа.
Я пытаюсь создать ленту новостей в стиле Facebook самостоятельно. Вместо того, чтобы создавать другую таблицу для регистрации действий пользователей, я рассчитал "край" из объединения сообщений, комментариев и т. д.
немного математики, я вычисляю "край", используя экспоненциальную модель распада, с истекшим временем, являющуюся независимой переменной, с учетом количества комментариев, лайков и т. д. каждый пост должен сформулировать лямбда-константу. Сначала край будет быстро уменьшаться, но постепенно выравнивается почти до 0 через несколько дней (но никогда не достигнет 0)
при отображении ленты каждое ребро умножается с помощью RAND (). Сообщения с более высоким краем будут появляться чаще
таким образом, более популярные сообщения имеют более высокую вероятность появления в ленте новостей в течение более длительного времени.
вместо запуска задания cron, какой-то скрипт после фиксации. Я не знаю конкретно, каковы возможности PHP и MySQL в этом отношении - если я правильно помню, MySQL InnoDB позволяет более продвинутые функции, чем другие разновидности, но я не помню, есть ли такие вещи, как триггеры в последней версии.
в любом случае, простое разнообразие, которое не полагается на много магии базы данных:
когда пользователь X добавляет содержание:
1) сделать асинхронный вызов с вашей PHP-страницы после фиксации базы данных (асинхронно, конечно, чтобы пользователю, просматривающему страницу, не пришлось ее ждать!)
вызов запускает экземпляр логического скрипта.
2) логика скрипт только через список друзей [A,B,C] пользователя, совершившего новый контент (в отличие от списка всех в БД!) и добавляет действие пользователя X к лентам для каждого из этих пользователей.
вы можете просто хранить эти каналы как прямые файлы JSON и добавляют новые данные в конец каждого. Лучше, конечно, сохранить каналы в кэше с резервной копией в файловую систему или BerkeleyDB или Mongo или что угодно.
Это просто основная идея для каналов, основанных на недавности, а не релевантности. Вы можете хранить данные последовательно таким образом, а затем выполнять дополнительный синтаксический анализ на основе каждого пользователя для фильтрации по релевантности, но это сложная проблема в любом приложении и, вероятно, не может быть легко адресовано анонимным веб-пользователем без подробного знания ваших требований;)
jsh по