Qual è il modo migliore per implementare un flusso di attività sociale? [chiuso]


265

Sono interessato a sentire le tue opinioni su quale sia il modo migliore per implementare un flusso di attività social (Facebook è l'esempio più famoso). I problemi / le sfide coinvolti sono:

  • Diversi tipi di attività (pubblicazione, commento ..)
  • Diversi tipi di oggetti (post, commento, foto ..)
  • Utenti 1-n coinvolti in ruoli diversi ("Utente x ha risposto al commento dell'utente y sul post dell'utente Z")
  • Viste diverse dello stesso elemento di attività ("hai commentato .." vs. "tuo amico x ha commentato" vs. "utente x ha commentato .." => 3 rappresentazioni di un'attività di "commento")

.. e un po 'di più, specialmente se lo porti ad un alto livello di raffinatezza, come Facebook, ad esempio, combinando diversi elementi di attività in uno ("gli utenti x, y e z hanno commentato quella foto"

Qualsiasi pensiero o suggerimento su schemi, documenti, ecc. Sugli approcci più flessibili, efficienti e potenti per l'implementazione di tale sistema, modello di dati, ecc. Sarebbe apprezzato.

Sebbene la maggior parte dei problemi sia indipendente dalla piattaforma, è probabile che finisca per implementare un tale sistema su Ruby on Rails

Risposte:


143

Ho creato tale sistema e ho adottato questo approccio:

Tabella del database con le seguenti colonne: ID, ID utente, tipo, dati, ora.

  • userId è l'utente che ha generato l'attività
  • tipo è il tipo di attività (ad es. ha scritto post sul blog, foto aggiunta, commentata sulla foto dell'utente)
  • data è un oggetto serializzato con metadati per l'attività in cui è possibile inserire ciò che si desidera

Questo limita le ricerche / ricerche, che puoi fare nei feed, agli utenti, ai tempi e ai tipi di attività, ma in un feed di attività di tipo Facebook, questo non è davvero limitante. E con gli indici corretti sul tavolo le ricerche sono veloci .

Con questo design dovresti decidere quali metadati dovrebbe richiedere ogni tipo di evento. Ad esempio, un'attività di feed per una nuova foto potrebbe essere simile a questa:

{id:1, userId:1, type:PHOTO, time:2008-10-15 12:00:00, data:{photoId:2089, photoName:A trip to the beach}}

Puoi vedere che, sebbene il nome della foto sia sicuramente memorizzato in qualche altra tabella contenente le foto e potrei recuperare il nome da lì, duplicherò il nome nel campo dei metadati, perché non vuoi farlo eventuali join su altre tabelle del database se si desidera velocità. E per visualizzare, diciamo 200, eventi diversi da 50 utenti diversi, hai bisogno di velocità.

Quindi ho delle classi che estendono una classe FeedActivity di base per il rendering dei diversi tipi di voci di attività. Il raggruppamento di eventi verrebbe creato anche nel codice di rendering, per tenere lontana la complessità dal database.


3
Sì, è corretto. Ultimamente ho usato MongoDB ( mongodb.org ) in alcuni progetti, il cui approccio schematico lo rende molto adatto per creare un flusso di attività sociali ben funzionante che segue questo progetto.
heyman,

6
L'apprendista: Sì, potresti voler inserire anche un campo nome utente. Nel nostro sistema, abbiamo visualizzato solo eventi generati dagli amici di un utente e credo che abbiamo già in memoria una mappa del nome utente userid-> degli amici, quindi cercare i nomi utente non richiede un JOIN ed è stato veloce.
heyman,

2
Dovresti gestire quel caso manualmente. Probabilmente è meglio farlo quando la foto viene eliminata (trova l'elemento del feed nel feed dell'utente ed eliminalo / aggiornalo).
heyman,

21
Non capisco cosa c'è di così bello in questa risposta? In che modo la creazione di una semplice tabella si traduce in un feed di attività ponderato simile a Facebook? Tutto ciò che sta facendo è memorizzare tutta l'attività. Che lascia ancora la domanda su come trasformare una tabella di dati in un feed di attività ponderato dinamico?
ChuckKelly,

4
@ChuckKelly: se ricordo bene, nel 2008, quando ho scritto la risposta, il feed di Facebook non è stato affatto ponderato. Era solo un feed cronologico con tutta l'attività dei tuoi amici.
heyman,

117

Questa è un'ottima presentazione che illustra come Etsy.com ha progettato i propri flussi di attività. È il miglior esempio che ho trovato sull'argomento, sebbene non sia specifico per le rotaie.

http://www.slideshare.net/danmckinley/etsy-activity-feeds-architecture


21
^^ Perché devi tornare a SO dopo aver visitato il sito. lol
Stephen Corwin,

1
Ottima presentazione che spiega in dettaglio come funziona il sistema su un sito Web ad alto traffico.
Ramirami,

44

Abbiamo open source il nostro approccio: https://github.com/tschellenbach/Stream-Framework Attualmente è la più grande libreria open source volta a risolvere questo problema.

Lo stesso team che ha creato Stream Framework offre anche un'API ospitata, che gestisce la complessità per te. Dai un'occhiata a getstream.io Ci sono client disponibili per Node, Python, Rails e PHP.

Inoltre, dai un'occhiata a questo post ad alta scalabilità dove spieghiamo alcune delle decisioni di progettazione coinvolte: http://highscalability.com/blog/2013/10/28/design-decisions-for-scaling-your-high-traffic- feeds.html

Questo tutorial ti aiuterà a configurare un sistema come il feed Pinterest usando Redis. È abbastanza facile iniziare.

Per saperne di più sulla progettazione dei feed, consiglio vivamente di leggere alcuni degli articoli su cui abbiamo basato Feedly:

Sebbene Stream Framework sia basato su Python, non sarebbe troppo difficile da usare da un'app Ruby. Potresti semplicemente eseguirlo come un servizio e attaccare una piccola API http di fronte ad esso. Stiamo valutando di aggiungere un'API per accedere a Feedly da altre lingue. Al momento dovrai comunque interpretare il tuo ruolo.


19

I maggiori problemi con i flussi di eventi sono la visibilità e le prestazioni; devi limitare gli eventi visualizzati in modo che siano solo quelli interessanti per quel particolare utente e devi mantenere il tempo necessario per ordinare e identificare quegli eventi gestibili. Ho costruito un piccolo social network; Ho scoperto che su piccola scala, mantenere una tabella "eventi" in un database funziona, ma che diventa un problema di prestazioni con un carico moderato.

Con un flusso più ampio di messaggi e utenti, è probabilmente preferibile utilizzare un sistema di messaggistica, in cui gli eventi vengono inviati come messaggi ai singoli profili. Ciò significa che non è possibile iscriversi facilmente ai flussi di eventi delle persone e vedere eventi precedenti molto facilmente, ma si sta semplicemente eseguendo il rendering di un piccolo gruppo di messaggi quando è necessario eseguire il rendering dello stream per un determinato utente.

Credo che questo fosse il difetto di progettazione originale di Twitter. Ricordo di aver letto che stavano colpendo il database per estrarre e filtrare i loro eventi. Questo ha avuto tutto a che fare con l'architettura e niente a che fare con Rails, che (sfortunatamente) ha dato vita al meme "ruby non scale". Di recente ho visto una presentazione in cui lo sviluppatore ha utilizzato il Simple Queue Service di Amazon come backend di messaggistica per un'applicazione simile a Twitter che avrebbe funzionalità di ridimensionamento molto più elevate; potrebbe valere la pena esaminare SQS come parte del sistema, se i carichi sono abbastanza alti .


Tim, ricordi per caso il nome della presentazione o del presentatore?
Danita,

era alla presentazione di Oreilly e Ignite Boston Associate numero 3 o 4- Credo che il presentatore avesse un libro sul ridimensionamento del RoR con Oreilly. Scusa, non posso essere più specifico!
Tim Howland,

Grazie Tim :) A proposito, cosa intendevi con "piccolo social network"? Quanti utenti o utenti attivi in ​​un determinato momento?
Danita,

3
Nel caso qualcuno ne abbia bisogno, penso che questa sia la presentazione di cui parla Tim: "Dan Chak - Scaling to the Size of your Problems" radar.oreilly.com/2008/09/ignite-boston-4----videos -uplo.html
Danita,

Il più piccolo in questo caso è tale che "select * tra gli eventi in cui event.is è visibile per questo utente" restituisce un risultato in meno di un secondo o due cifre per alcune centinaia di migliaia di righe di eventi.
Tim Howland,

12

Se si desidera utilizzare un software separato, suggerisco il server Graphity che risolve esattamente il problema per i flussi di attività (basandosi sul database neo4j graph).

Gli algoritmi sono stati implementati come server REST autonomo in modo da poter ospitare il proprio server per fornire flussi di attività: http://www.rene-pickhardt.de/graphity-server-for-social-activity-streams-released-gplv3 /

Nel documento e nel benchmark ho mostrato che il recupero di flussi di notizie dipende solo in modo lineare dalla quantità di elementi che si desidera recuperare senza ridondanza che si otterrebbe dalla denormalizzazione dei dati:

http://www.rene-pickhardt.de/graphity-an-efficient-graph-model-for-retrieving-the-top-k-news-feeds-for-users-in-social-networks/

Sul link sopra trovi screencast e un benchmark di questo approccio (che mostra che la grafica è in grado di recuperare più di 10k stream al secondo).


10

Ho iniziato a implementare un sistema come questo ieri, ecco dove devo ...

Ho creato una classe StreamEvent con le proprietà Id , ActorId , TypeId , Date , ObjectId e una tabella hash di ulteriori coppie chiave / valore Dettagli . Questo è rappresentato nel database da una tabella StreamEvent ( Id , ActorId , TypeId , Date , ObjectId ) e una StreamEventDetails tavolo ( StreamEventId , DetailKey , DetailValue ).

L'ActorId , TypeId e ObjectId consentono un evento soggetto-verbo-oggetto da catturare (e successivamente interrogato). Ogni azione può comportare la creazione di più istanze StreamEvent.

Ho quindi creato una sottoclasse per StreamEvent per ogni tipo di evento, ad esempio LoginEvent , PictureCommentEvent . Ognuna di queste sottoclassi ha proprietà più specifiche del contesto come PictureId , ThumbNail , CommenText , ecc. (Qualunque cosa sia necessaria per l'evento) che sono effettivamente archiviate come coppie chiave / valore nella tabella hashtable / StreamEventDetail.

Quando torno indietro questi eventi dal database uso un metodo factory (basato su TypeId ) per creare la classe StreamEvent corretta.

Ogni sottoclasse di StreamEvent ha un Render ( metodo contesto come StreamContext ) che genera l'evento sullo schermo in base alla classe StreamContext passata . La classe StreamContext consente di impostare le opzioni in base al contesto della vista. Se guardi Facebook, ad esempio, il tuo feed di notizie sulla homepage elenca i nomi completi (e i collegamenti al loro profilo) di tutti coloro che sono coinvolti in ogni azione, mentre guardando il feed di un amico vedi solo il loro nome (ma i nomi completi di altri attori) .

Non ho ancora implementato un feed aggregato (home di Facebook) ma immagino che creerò un tabella AggregateFeed che abbia i campi UserId , StreamEventId che è popolato in base a una sorta di algoritmo "Hmmm, potresti trovare questo interessante".

Qualsiasi commento sarebbe apprezzato in modo massiccio.


Sto lavorando a un sistema come questo, sono molto interessato a qualsiasi conoscenza su di esso, hai mai finito il tuo?
JasonDavis,

Bella risposta! Eccellente separazione delle preoccupazioni, pulito ed elegante!
Mosh,

Questo è un buon inizio! È molto simile a come ho iniziato a implementare il mio primo stream. Una volta arrivato al feed aggregato, tuttavia, le cose iniziano a complicarsi rapidamente. Hai ragione, hai bisogno di un algoritmo robusto. La mia ricerca mi ha portato all'algoritmo di Rene Pickhardt (ne parla nella sua risposta qui), che ho poi implementato nel mio servizio, che ora è commerciale (vedi collabinate.com e la mia risposta su questa domanda per ulteriori informazioni).
Mafuba,

10
// una voce per evento reale
eventi {
  id, data e ora, tipo, dati
}

// una voce per evento, per feed contenente quell'evento
events_feeds {
  event_id, feed_id
}

Quando viene creato l'evento, decidi in quali feed viene visualizzato e aggiungi quelli a events_feeds. Per ottenere un feed, seleziona da events_feeds, partecipa agli eventi, ordina per timestamp. Il filtro e l'aggregazione possono quindi essere eseguiti sui risultati di quella query. Con questo modello, è possibile modificare le proprietà dell'evento dopo la creazione senza ulteriore lavoro.


1
Supponiamo che qualcun altro venga aggiunto come amico dopo aver aggiunto l'evento, che deve vedere questo evento nel loro feed? allora questo non funzionerebbe
Joshua Kissoon,


6

Avevo un approccio simile a quello di heyman: una tabella denormalizzata contenente tutti i dati che sarebbero stati visualizzati in un determinato flusso di attività. Funziona bene per un piccolo sito con attività limitate.

Come accennato in precedenza, è probabile che si verifichino problemi di scalabilità man mano che il sito cresce. Personalmente, non sono preoccupato per i problemi di ridimensionamento in questo momento. Mi preoccuperò di ciò in un secondo momento.

Facebook ha ovviamente fatto un ottimo lavoro di ridimensionamento, quindi ti consiglio di leggere il loro blog di ingegneria, in quanto ha un sacco di ottimi contenuti -> http://www.facebook.com/notes.php?id=9445547199

Ho cercato soluzioni migliori rispetto alla tabella denormalizzata che ho menzionato sopra. Un altro modo che ho scoperto per ottenere questo risultato è quello di condensare tutto il contenuto che sarebbe in un determinato flusso di attività in una singola riga. Potrebbe essere archiviato in XML, JSON o in qualche formato serializzato che potrebbe essere letto dalla tua applicazione. Anche il processo di aggiornamento sarebbe semplice. Al momento dell'attività, inserisci la nuova attività in una coda (magari utilizzando Amazon SQS o qualcos'altro) e quindi esegui continuamente il polling della coda per l'elemento successivo. Prendi quell'elemento, analizzalo e posiziona il suo contenuto nell'oggetto feed appropriato memorizzato nel database.

La cosa buona di questo metodo è che devi solo leggere una singola tabella di database ogni volta che quel particolare feed è richiesto, piuttosto che prendere una serie di tabelle. Inoltre, consente di mantenere un elenco finito di attività poiché è possibile estrarre l'elemento attività più vecchio ogni volta che si aggiorna l'elenco.

Spero che questo ti aiuti! :)


Esattamente i miei pensieri, avevo solo bisogno di una convalida dei miei pensieri che probabilmente ho avuto ora, evviva!
Sohail,


3

Penso a Plurk approccio sia interessante: forniscono l'intera sequenza temporale in un formato che assomiglia molto ai grafici azionari di Google Finanza.

Potrebbe valere la pena guardare Ning per vedere come funziona una rete di social network. Le pagine degli sviluppatori sembrano particolarmente utili.


2

L'ho risolto alcuni mesi fa, ma penso che la mia implementazione sia troppo semplice.
Ho creato i seguenti modelli:

HISTORY_TYPE

ID           - The id of the history type
NAME         - The name (type of the history)
DESCRIPTION  - A description

HISTORY_MESSAGES

ID
HISTORY_TYPE - A message of history belongs to a history type
MESSAGE      - The message to print, I put variables to be replaced by the actual values

HISTORY_ACTIVITY

ID
MESSAGE_ID    - The message ID to use
VALUES        - The data to use

Esempio

MESSAGE_ID_1 => "User %{user} created a new entry"
ACTIVITY_ID_1 => MESSAGE_ID = 1, VALUES = {user: "Rodrigo"}

2

Dopo aver implementato i flussi di attività per abilitare le funzionalità di social feed, microblogging e collaborazione in diverse applicazioni, mi sono reso conto che la funzionalità di base è abbastanza comune e potrebbe essere trasformata in un servizio esterno che utilizzi tramite un'API. Se stai creando lo stream in un'applicazione di produzione e non hai esigenze uniche o profondamente complesse, utilizzare un servizio collaudato potrebbe essere il modo migliore per procedere. Lo consiglierei sicuramente per le applicazioni di produzione oltre a implementare la propria soluzione semplice su un database relazionale.

La mia azienda Collabinate ( http://www.collabinate.com ) è da questa realizzazione e per raggiungere questo obiettivo abbiamo implementato un motore di flusso di attività scalabile e ad alte prestazioni su un database grafico. Abbiamo effettivamente utilizzato una variante dell'algoritmo Graphity (adattato dai primi lavori di @RenePickhardt, che ha anche fornito una risposta qui) per costruire il motore.

Se desideri ospitare tu stesso il motore o richiedere funzionalità specializzate, il codice principale è in realtà open source per scopi non commerciali, quindi puoi dare un'occhiata.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.