Budowa systemu powiadomień [zamknięte]

170

Jestem na początku budowania systemu powiadomień w stylu Facebooka dla naszej strony (typ gier społecznościowych) i teraz badam, jak najlepiej zaprojektować taki system. Nie interesuje mnie, jak wysyłać powiadomienia do użytkownika ani nic w tym rodzaju (na razie nawet). Badam, jak zbudować system na serwerze (jak przechowywać powiadomienia, gdzie je przechowywać, jak je pobrać itp.).

A więc ... niektóre wymagania, które mamy:

  • w godzinach szczytu mamy około 1 tys. jednocześnie zalogowanych użytkowników (i znacznie więcej gości, ale nie mają oni tutaj znaczenia, ponieważ nie będą mieli powiadomień), które wygenerują wiele zdarzeń
  • będą różne rodzaje powiadomień (użytkownik A dodał Cię jako znajomego, użytkownik B skomentował Twój profil, użytkownik C polubił Twój obraz, użytkownik D pokonał Cię w grze X, ...)
  • większość wydarzeń wygeneruje 1 powiadomienie dla 1 użytkownika (użytkownik X polubił Twój obraz), ale zdarzają się przypadki, gdy jedno zdarzenie wygeneruje wiele powiadomień (na przykład urodziny użytkownika Y)
  • powiadomienia powinny być zgrupowane razem; jeśli na przykład czterech różnych użytkowników lubi jakiś obraz, właściciel tego obrazu powinien otrzymać jedno powiadomienie informujące, że obraz podobał się czterem użytkownikom, a nie cztery oddzielne powiadomienia (tak jak robi to FB)

OK, więc pomyślałem, że powinienem stworzyć coś w rodzaju kolejki, w której będę przechowywać zdarzenia, gdy się pojawią. Wtedy miałbym zadanie w tle ( biegacz ?), Które przeglądałoby tę kolejkę i generowało powiadomienia na podstawie tych zdarzeń. To zadanie przechowuje następnie powiadomienia w bazie danych dla każdego użytkownika (więc jeśli zdarzenie dotyczy 10 użytkowników, byłoby 10 oddzielnych powiadomień). Następnie, gdy użytkownik otwierał stronę z listą powiadomień, czytałem mu wszystkie te powiadomienia (myśleliśmy o tym, żeby ograniczyć to do 100 najnowszych) i grupowałem je razem, a na koniec wyświetlałem.

Rzeczy, które mnie interesują w przypadku tego podejścia:

  • skomplikowane jak diabli :)
  • czy baza danych to najlepsza pamięć masowa tutaj (używamy MySQL), czy powinienem użyć czegoś innego (redis też wydaje się pasować)
  • co mam przechowywać jako powiadomienie? identyfikator użytkownika, identyfikator użytkownika, który zainicjował zdarzenie, typ zdarzenia (żebym mógł je pogrupować i wyświetlić odpowiedni tekst), ale wtedy trochę nie wiem, jak przechowywać rzeczywiste dane powiadomienia (na przykład adres URL i tytuł obrazu, który był lubiany). Czy powinienem po prostu „upiec” te informacje podczas generowania powiadomienia, czy też powinienem zapisać identyfikator rekordu (obraz, profil, ...), na który ma to wpływ, i wyciągnąć informacje z bazy danych podczas wyświetlania powiadomienia.
  • wydajność powinna być tutaj OK, nawet jeśli muszę przetwarzać 100 powiadomień w locie podczas wyświetlania strony powiadomień
  • możliwy problem z wydajnością przy każdym żądaniu, ponieważ musiałbym wyświetlać użytkownikowi liczbę nieprzeczytanych powiadomień (co mogłoby być problemem samo w sobie, ponieważ grupowałbym powiadomienia razem). Można by tego jednak uniknąć, gdybym wygenerował widok powiadomień (gdzie są one zgrupowane) w tle, a nie w locie

Co myślisz o zaproponowanym przeze mnie rozwiązaniu i moich obawach? Proszę o komentarz, jeśli sądzisz, że powinienem wspomnieć o czymś innym, co byłoby tutaj istotne.

Och, używamy PHP dla naszej strony, ale myślę, że nie powinno to być dużym czynnikiem.

Jan Hančič
źródło
Ile czasu zajęło Ci zbudowanie tego systemu powiadomień jako wysiłek jednej osoby. Chcę tylko mieć oszacowanie, aby odpowiednio dopasować ramy czasowe.
Shaharyar
@Shaharyar Myślę, że to zależy od złożoności systemu powiadomień.
tyan
Użyłem tego samego systemu z MySQL do zbudowania systemu powiadamiania opartego na priorytetach. Dobrą rzeczą jest to, że skaluje się do kilku tysięcy użytkowników, jeśli przekracza to, wybucha, szczególnie z Androidem i GCM. Chciałbym poznać alternatywy dla MySQL, takie jak redis, rabbitMQ, Kafka, które naturalnie mają kolejkę wiadomości, rodzaj funkcjonalności.
Ankit Marothi

Odpowiedzi:

168

Powiadomienie dotyczy czegoś (obiekt = wydarzenie, przyjaźń ...), które zostało zmienione (czasownik = dodane, zażądane ..) przez kogoś (aktora) i zgłoszone użytkownikowi (podmiotowi). Oto znormalizowana struktura danych (chociaż korzystałem z MongoDB). Musisz powiadomić niektórych użytkowników o zmianach. Więc są to powiadomienia dla każdego użytkownika… co oznacza, że ​​jeśli było zaangażowanych 100 użytkowników, generujesz 100 powiadomień.

╔═════════════╗      ╔═══════════════════╗      ╔════════════════════╗
║notification ║      ║notification_object║      ║notification_change ║
╟─────────────╢      ╟───────────────────╢      ╟────────────────────╢
║ID           ║—1:n—→║ID                 ║—1:n—→║ID                  ║
║userID       ║      ║notificationID     ║      ║notificationObjectID║
╚═════════════╝      ║object             ║      ║verb                ║
                     ╚═══════════════════╝      ║actor               ║
                                                ╚════════════════════╝

(Dodaj pola czasu tam, gdzie uważasz za stosowne)

Zasadniczo służy to do grupowania zmian według obiektu, aby można było powiedzieć „Masz 3 zaproszenia do znajomych”. Przydatne jest grupowanie według aktorów, dzięki czemu można powiedzieć „Użytkownik James Bond dokonał zmian w Twoim łóżku”. Daje to również możliwość tłumaczenia i liczenia powiadomień według własnego uznania.

Ale ponieważ obiekt jest tylko identyfikatorem, musiałbyś uzyskać wszystkie dodatkowe informacje o obiekcie za pomocą oddzielnych wywołań, chyba że obiekt faktycznie się zmienia i chcesz pokazać tę historię (na przykład „użytkownik zmienił tytuł zdarzenia na ... ")

Ponieważ powiadomienia są zbliżone do czasu rzeczywistego dla użytkowników witryny, powiązałbym je z klientem nodejs + websockets z aktualizacją php push do nodejs dla wszystkich słuchaczy w miarę dodawania zmian.

Artjom Kurapov
źródło
1
notification_object.object identyfikuje typ zmiany, np. ciąg „przyjaźń”. Rzeczywiste odniesienie do zmienionego obiektu z dodatkowymi danymi, o których mówię, znajduje się w notification_change.notificationObjectID
Artjom Kurapov
2
To może być głupie pytanie, ale przy takiej konfiguracji co zrobić, gdy użytkownik zobaczy lub zareaguje na powiadomienie? Czy po prostu usuwasz go z bazy danych, czy po prostu używasz dat, aby sprawdzić, czy użytkownik zalogował się od czasu utworzenia powiadomienia?
Jeffery Mills,
4
Wiem, że ten temat jest już dość stary, ale jestem trochę zaintrygowany pierwszym stołem, jaki dokładnie jest cel tego stołu? Jaka jest zaleta posiadania tego jako oddzielnej tabeli w porównaniu z umieszczeniem identyfikatora użytkownika w tabeli notification_object? Innymi słowy, kiedy utworzysz nowy wpis w powiadomieniu, a kiedy po prostu dodasz obiekt i zmienisz na istniejące powiadomienie o tej strukturze?
Bas Goossen
3
@JefferyMills Możesz mieć pole statusu, takie jak is_notification_readw notificationtabeli i odpowiednio je oznaczyć, jeśli tak jest unread, readlub deleted.
Kevin
2
Miałem również problem ze zrozumieniem niektórych aspektów tego rozwiązania i zadałem osobne pytanie na jego temat: dba.stackexchange.com/questions/99401/ ...
user45623
27

To naprawdę abstrakcyjne pytanie, więc myślę, że będziemy musieli je po prostu przedyskutować, zamiast wskazywać, co należy, a czego nie.

Oto, co myślę o Twoich obawach:

  • Tak, system powiadomień jest złożony, ale nie tak diabli. Możesz mieć wiele różnych podejść do modelowania i wdrażania takich systemów, od średniego do wysokiego poziomu złożoności;

  • Osobiście zawsze staram się, aby rzeczy były oparte na bazie danych. Czemu? Ponieważ mogę zagwarantować pełną kontrolę nad wszystkim, co się dzieje - ale to tylko ja, możesz mieć kontrolę bez podejścia opartego na bazie danych; zaufaj mi, będziesz chciał mieć kontrolę nad tą sprawą;

  • Pozwólcie, że zilustruję prawdziwy przypadek dla Ciebie, abyś mógł zacząć od czegoś. W zeszłym roku wymodelowałem i wdrożyłem system powiadomień w jakiejś sieci społecznościowej (oczywiście nie takiej jak facebook). W jaki sposób przechowywałem tam powiadomienia? Miałem notificationstabelę, w której zachowałem generator_user_id(identyfikator użytkownika, który generuje powiadomienie), target_user_id(rodzaj oczywiste, prawda?), notification_type_id(Który odwołuje się do innej tabeli z typami powiadomień) i wszystko potrzebne rzeczy, które musimy wypełnić w naszych tabelach (znaczniki czasu, flagi itp.). Moja notification_typestabela była powiązana z notification_templatestabelą, która zawierała określone szablony dla każdego typu powiadomienia. Na przykład miałem POST_REPLYtyp, który miał podobny szablon {USER} HAS REPLIED ONE OF YOUR #POSTS. Stamtąd właśnie potraktowałem plik{}jako zmienna i #jako odsyłacz;

  • Tak, wydajność powinna i musi być w porządku. Kiedy myślisz o powiadomieniach, myślisz o przepychaniu się serwera od stóp do głów. Albo jeśli zamierzasz to zrobić z żądaniami Ajaxa lub czymkolwiek, będziesz musiał martwić się o wydajność. Ale myślę, że to drugi problem;

Model, który zaprojektowałem, nie jest oczywiście jedynym, który możesz naśladować, ani też najlepszy. Mam nadzieję, że przynajmniej moja odpowiedź podąży za tobą we właściwym kierunku.

Daniel Ribeiro
źródło
Dlaczego nie miałbym mieć kontroli z jakimś innym magazynem danych?
Jan Hančič
Cóż, tego nie powiedziałem. Powiedziałem tylko, że mogę zagwarantować kontrolę danych tylko przy podejściu opartym na bazie danych; ale to tylko ja. Sformułuję to inaczej.
Daniel Ribeiro,
@DanielRibeiro symbole zastępcze ({...}) w szablonie powiadomienia muszą zastąpić dane symboli zastępczych z innego zestawu tabel w bazie danych dla różnych typów powiadomień. Np. Jeden szablon to „{user} polubił twoje zdjęcie”, a inny to „Twoja {nazwa strony} ma nowe polubienie”. Itd. {PageName} i {user} oraz inne symbole zastępcze będą mapowane z innej tabeli bazy danych, więc jaki powinien być schemat, aby dynamicznie uzyskać wartość zastępczą.
Ashish Shukla
DanielRibeiro, w jaki sposób zastąpiłeś symbole zastępcze zgodnie z pytaniem @Ashish Shukla,
Shantaram Tupe
@AshishShukla Czy użyłeś lub zastąpiłeś symbole zastępcze i jak?
Shantaram Tupe
8
╔════════════════════╗
║notification        ║
╟────────────────────╢
║Username            ║
║Object              ║
║verb                ║
║actor               ║
║isRead              ║
╚════════════════════╝

Wygląda na to, że jest to dobra odpowiedź, a nie dwie kolekcje. Możesz zapytać o nazwę użytkownika, obiekt i isRead, aby uzyskać nowe zdarzenia (takie jak 3 oczekujące zaproszenia do znajomych, 4 zadawane pytania itp.)

Daj mi znać, jeśli jest problem z tym schematem.

Kaphy
źródło
3
W górnej odpowiedzi zastosowano znormalizowaną strukturę danych, co oznacza brak nadmiarowości w tabelach. Czy twoja odpowiedź to robi?
Aaron Hall
4

Osobiście nie rozumiem zbyt dobrze diagramu zaakceptowanej odpowiedzi, więc dołączę diagram bazy danych w oparciu o to, czego mogłem się nauczyć z zaakceptowanej odpowiedzi i innych stron.

wprowadź opis obrazu tutaj

Ulepszenia są dobrze odbierane.

Jason Glez
źródło
Wygląda na to, że message_template byłby w tabeli NotificationType. Wydaje się również, że main_url byłby w tabeli powiadomień, więc możesz wyeliminować tabelę Notification_Message. Czy możesz wyjaśnić, dlaczego masz samą tabelę NotificationMessage?
Jeff Ryan,