Permettez-moi de commencer dès le départ en disant que je sais que ce n'est pas la meilleure solution. Je sais que c'est maladroit et un hack d'une fonctionnalité. Mais c'est pourquoi je suis là !
Cette question/travail s'appuie sur quelques discussions sur Quora avec Andrew Bosworth , créateur du fil d'actualité de Facebook.
Je suis en train de créer un fil d'actualité en quelque sorte. Il est construit uniquement en PHP
et MySQL
.
Le modèle relationnel du flux est composé de deux tableaux. Une table fonctionne comme un journal d'activité; en fait, il s'appelle activity_log
. L'autre table est newsfeed
. Ces tableaux sont presque identiques.
Le schéma du journal est activity_log(uid INT(11), activity ENUM, activity_id INT(11), title TEXT, date TIMESTAMP)
... et le schéma du flux est newsfeed(uid INT(11), poster_uid INT(11), activity ENUM, activity_id INT(11), title TEXT, date TIMESTAMP)
.
Chaque fois qu'un utilisateur fait quelque chose pertinent pour le fil d'actualités, par exemple en posant une question, il sera connecté à le journal d'activité immédiatement.
Ensuite toutes les X minutes (5 minutes pour le moment, passera à 15-30 minutes plus tard), je cours un travail cron qui exécute le script ci-dessous. Ce script parcourt tous les utilisateurs de la base de données, recherche toutes les activités de tous les amis de cet utilisateur, puis écrit ces activités dans le fil d'actualités.
À l'heure actuelle, le SQL
qui élimine l'activité (appelée dans ActivityLog::getUsersActivity()
) a un LIMIT 100
Imposé pour des raisons de performances *. * Non pas que je sache de quoi je parle.
<?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']);
}
}
}
}
Dans le code client, lors de la récupération du fil d'actualités de l'utilisateur, je fais quelque chose comme:
$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'];
}
Pardonnez maintenant ma compréhension limitée des meilleures pratiques pour développer un fil d'actualités, mais je comprends l'approche que j'utilise pour être une version limitée de ce qu'on appelle fan-out en écriture , limité dans le sens où j'exécute une tâche cron en tant qu'étape intermédiaire au lieu d'écrire directement dans les fils de nouvelles des utilisateurs. Mais cela est très différent d'un modèle pull, dans le sens où le fil d'actualités de l'utilisateur n'est pas compilé en charge, mais plutôt de façon régulière.
C'est une grande question qui mérite probablement beaucoup de va-et-vient, mais je pense qu'elle peut servir de pierre de touche pour de nombreuses conversations importantes que les nouveaux développeurs comme moi doivent avoir. J'essaie juste de comprendre ce que je fais mal, comment je peux m'améliorer, ou comment je devrais peut-être même partir de zéro et essayer une approche différente.
Une autre chose qui me dérange dans ce modèle est qu'il fonctionne en fonction de la récence plutôt que de la pertinence. Si quelqu'un peut suggérer comment cela peut être amélioré pour fonctionner avec pertinence, je serais à votre écoute. J'utilise l'API de Directed Edge pour générer des recommandations, mais il semble que pour quelque chose comme un fil d'actualité, les recommandateurs ne fonctionneront pas (car rien n'a été favorisé auparavant!).
Question vraiment cool. Je suis en train d'implémenter moi-même quelque chose comme ça. Donc, je vais réfléchir un peu à haute voix.
Voici les défauts que je vois dans mon esprit avec votre implémentation actuelle:
Vous traitez tous les amis pour tous les utilisateurs, mais vous finirez par traiter les mêmes utilisateurs plusieurs fois en raison du fait que les mêmes groupes de personnes ont des amis similaires.
Si l'un de mes amis publie quelque chose, il n'apparaîtra pas dans mon fil d'actualités pendant au plus 5 minutes. Alors qu'il devrait apparaître immédiatement, non?
Nous lisons l'intégralité du fil d'actualités pour un utilisateur. N'avons-nous pas juste besoin de saisir les nouvelles activités depuis la dernière fois que nous avons croqué les journaux?
Cela n'évolue pas si bien.
Le fil d'actualité ressemble exactement aux mêmes données que le journal d'activité, je m'en tenir à cette table de journal d'activité.
Si vous partagez vos journaux d'activité sur plusieurs bases de données, cela vous permettra de vous adapter plus facilement. Vous pouvez également partager vos utilisateurs si vous le souhaitez, mais même si vous avez 10 millions d'enregistrements d'utilisateurs dans une table, mysql devrait bien faire des lectures. Ainsi, chaque fois que vous recherchez un utilisateur, vous savez à partir de quel fragment accéder aux journaux de l'utilisateur. Si vous archivez vos anciens journaux de temps en temps et ne maintenez qu'un nouvel ensemble de journaux, vous n'aurez pas à en partager autant. Ou peut-être même du tout. Vous pouvez gérer plusieurs millions d'enregistrements dans MySQL si vous êtes réglé même modérément bien.
J'utiliserais memcached pour votre table d'utilisateurs et peut-être même les journaux eux-mêmes. Memcached autorise des entrées de cache jusqu'à 1 Mo, et si vous étiez intelligent dans l'organisation de vos clés, vous pourriez potentiellement récupérer tous les journaux les plus récents du cache.
Ce serait plus de travail en ce qui concerne l'architecture, mais cela vous permettra de travailler en temps réel et d'évoluer à l'avenir ... surtout lorsque vous souhaitez que les utilisateurs commencent à commenter sur chaque publication. ;)
Avez-vous vu cet article?
Souhaitez-vous ajouter des mots clés statistiques? J'ai fait une implémentation (grossière) en éclatant le corps de mon document, en supprimant le HTML, en supprimant les mots courants et en comptant les mots les plus courants. Je l'ai fait il y a quelques années juste pour le plaisir (comme pour tout projet de ce type, la source a disparu), mais cela a fonctionné pour ma configuration temporaire de blog/forum de test. Peut-être que cela fonctionnera pour votre fil d'actualités ...
J'essaie de créer moi-même un fil d'actualité sur Facebook. Au lieu de créer un autre tableau pour enregistrer les activités des utilisateurs, j'ai calculé le `` bord '' à partir de l'UNION des messages, des commentaires, etc.
Avec un peu de mathématiques, je calcule le `` bord '' en utilisant un modèle de décroissance exponentielle, avec le temps écoulé étant la variable indépendante, en tenant compte du nombre de commentaires, de goûts, etc. chaque article doit formuler la constante lambda. L'Edge diminuera rapidement au début, mais s'aplatit progressivement à presque 0 après quelques jours (mais n'atteindra jamais 0)
Lors de l'affichage du flux, chaque bord est multiplié à l'aide de Rand (). Les messages avec un bord supérieur apparaîtront plus souvent
De cette façon, les articles les plus populaires ont une plus grande probabilité d'apparaître dans le fil d'actualité, pendant plus longtemps.
Au lieu d'exécuter une tâche cron, un script de post-validation quelconque. Je ne sais pas précisément quelles sont les capacités de PHP et MySQL à cet égard - si je me souviens bien, MySQL InnoDB permet des fonctionnalités plus avancées que les autres variétés mais je ne me souviens pas s'il y a des choses comme les déclencheurs dans la dernière version.
de toute façon, une variété simple qui ne repose pas sur beaucoup de magie de base de données:
lorsque l'utilisateur X ajoute du contenu:
1) faites un appel asynchrone à partir de votre page PHP après la validation de la base de données (asynchrone bien sûr pour que l'utilisateur visualisant la page n'ait pas à l'attendre!)
L'appel démarre une instance de votre script logique.
2) le script logique passe niquement à travers la liste d'amis [A, B, C] de l'utilisateur qui a validé le nouveau contenu (par opposition à la liste de tout le monde dans la base de données!) Et ajoute l'action de l'utilisateur X aux flux de chacun de ces utilisateurs.
Vous pouvez simplement stocker ces flux sous forme de fichiers JSON directs et ajouter de nouvelles données à la fin de chacun. Mieux vaut bien sûr garder les flux en cache avec une sauvegarde sur le système de fichiers ou BerkeleyDB ou Mongo ou tout ce que vous voulez.
Il s'agit simplement d'une idée de base pour les flux basés sur la récence et non sur la pertinence. Vous POUVEZ stocker les données de manière séquentielle de cette manière, puis effectuer une analyse supplémentaire par utilisateur pour filtrer par pertinence, mais c'est un problème difficile dans n'importe quelle application et probablement pas un qui peut être facilement résolu par un utilisateur Web anonyme sans détails connaissance de vos besoins;)
jsh
entre vous pouvez utiliser les drapeaux utilisateur et la mise en cache. Disons, ayez un nouveau champ pour l'utilisateur en tant que last_activity. Mettez à jour ce champ chaque fois que l'utilisateur entre dans une activité. Gardez un indicateur, jusqu'à ce que vous ayez récupéré les flux, disons-le feed_updated_on.
Maintenant, mettez à jour la fonction $ user-> getAllUsers (); pour renvoyer uniquement les utilisateurs dont la durée last_activity est postérieure à feed_updated_on. Cela exclura tous les utilisateurs qui n'ont pas de journal d'activité :). Processus similaire pour les amis des utilisateurs.
Vous pouvez également utiliser la mise en cache comme memcache ou la mise en cache au niveau du fichier.
Ou utilisez une base de données nosql pour stocker tous les flux en un seul document.