Построение системы уведомлений [закрыто]

170

Я нахожусь в начале создания системы уведомлений в стиле Facebook для нашей страницы (типа социальных игр), и сейчас я изучаю, как лучше всего разработать такую ​​систему. Меня не интересует, как отправлять уведомления пользователю или что-то в этом роде (на данный момент даже). Я изучаю, как построить систему на сервере (как хранить уведомления, где хранить их, как получать их и т.д. ...).

Итак ... некоторые требования, которые у нас есть:

  • в пиковое время у нас есть около 1k одновременно зарегистрированных пользователей (и еще много гостей, но здесь они не имеют значения, так как у них не будет уведомлений), которые будут генерировать много событий
  • будут различные типы уведомлений (пользователь A добавил вас в друзья, пользователь B прокомментировал ваш профиль, пользователю C понравилось ваше изображение, пользователь D избил вас в игре X, ...)
  • большинство событий генерируют 1 уведомление для 1 пользователя (пользователю X понравилось ваше изображение), но будут случаи, когда одно событие будет генерировать много уведомлений (например, это день рождения пользователя Y)
  • уведомления должны быть сгруппированы вместе; если, например, четыре разных пользователя любят какое-то изображение, владелец этого изображения должен получить одно уведомление о том, что изображение понравилось четырем пользователям, а не четырем отдельным уведомлениям (как FB)

Итак, я подумал, что мне нужно создать какую-то очередь, где я буду хранить события, когда они происходят. Тогда у меня будет фоновая работа ( gearman ?), Которая будет просматривать эту очередь и генерировать уведомления на основе этих событий. Затем это задание будет хранить уведомления в базе данных для каждого пользователя (поэтому, если событие затрагивает 10 пользователей, будет 10 отдельных уведомлений). Затем, когда пользователь открывал страницу со списком уведомлений, я читал все эти уведомления для него (мы надеемся ограничить это до 100 последних уведомлений) и группировал их вместе, а затем, наконец, отображал их.

Что меня беспокоит в этом подходе:

  • сложный как черт :)
  • Является ли база данных лучшим хранилищем здесь (мы используем MySQL) или я должен использовать что-то еще (redis тоже кажется подходящим)
  • что я должен хранить как уведомление? идентификатор пользователя, идентификатор пользователя, который инициировал событие, тип события (чтобы я мог сгруппировать их и отобразить соответствующий текст), но тогда я вроде не знаю, как хранить фактические данные уведомления (например, URL-адрес и заголовок изображения, которое было понравилось). Должен ли я просто «испечь» эту информацию при создании уведомления или сохранить идентификатор затрагиваемой записи (изображение, профиль, ...) и извлечь информацию из БД при отображении уведомления.
  • производительность должна быть в порядке, даже если мне нужно обрабатывать 100 уведомлений на лету при отображении страницы уведомлений
  • возможная проблема с производительностью при каждом запросе, потому что мне пришлось бы отображать количество непрочитанных уведомлений для пользователя (что само по себе может быть проблемой, поскольку я собирал уведомления вместе). Этого можно избежать, если бы я генерировал представление уведомлений (где они сгруппированы) в фоновом режиме, а не на лету

Так что вы думаете о моем предложенном решении и моих проблемах? Пожалуйста, прокомментируйте, если вы считаете, что я должен упомянуть что-либо еще, что будет иметь отношение к делу.

О, мы используем PHP для нашей страницы, но это не должно быть большим фактором здесь, я думаю.

Ян Ханчич
источник
Сколько времени вам понадобилось для создания этой системы уведомлений одним человеком усилий. Я просто хочу иметь оценку, чтобы сделать сроки соответственно.
Шахарьяр,
@ Shaharyar Я думаю, это зависит от сложности системы уведомлений.
Тян
Я использовал ту же систему с MySQL для создания системы уведомлений на основе приоритетов. Хорошо то, что он масштабируется до нескольких тысяч пользователей, а если он идет больше, он взрывается, особенно с Android и GCM. Я хотел бы знать альтернативы MySQL, такие как Redis, RabbitMQ, Kafka, которые, естественно, имеют очередь сообщений, вид функциональности.
Анкит Мароти

Ответы:

168

Уведомление о том, что кто-то (субъект) что-то (объект = событие, дружба ..) был изменен (глагол = добавлен, запрошен ..) и передан пользователю (субъекту). Вот нормализованная структура данных (хотя я использовал MongoDB). Вам необходимо уведомить определенных пользователей об изменениях. Таким образом, это уведомления для каждого пользователя. Это означает, что если в нем участвует 100 пользователей, вы генерируете 100 уведомлений.

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

(Добавьте поля времени, где вы считаете нужным)

Это в основном для группировки изменений по объектам, чтобы вы могли сказать «У вас есть 3 запроса на добавление в друзья». А группировка по актерам полезна, чтобы вы могли сказать: «Пользователь Джеймс Бонд внес изменения в вашей постели». Это также дает возможность переводить и считать уведомления, как вам нравится.

Но, поскольку объект - это просто идентификатор, вам нужно было бы получать всю дополнительную информацию об объекте, который вы хотите, с помощью отдельных вызовов, если только объект на самом деле не изменяется, и вы хотите показать эту историю (например, «пользователь изменил название события на ...» «)

Поскольку уведомления для пользователей на сайте близки к реальному времени, я бы связал их с клиентом nodejs + websockets с php, отправляющим обновление для nodejs для всех слушателей по мере добавления изменений.

Артём Курапов
источник
1
Notification_object.object идентифицирует тип изменения, например, строку «дружба». Фактическая ссылка на измененный объект с его дополнительными данными, о которых я говорю, находится в уведомлении_change.notificationObjectID
Artjom Kurapov
2
Это может быть глупый вопрос, но с этой настройкой, что вы делаете, когда пользователь видел или действовал в уведомлении? Вы просто удаляете его из базы данных или просто используете даты, чтобы увидеть, вошел ли пользователь в систему с момента создания уведомления?
Джеффри Миллс
4
Я знаю, что эта тема уже довольно старая, но я немного озадачен первой таблицей, какова цель этой таблицы? В чем преимущество наличия этой отдельной таблицы по сравнению с помещением идентификатора пользователя в таблицу messages_object? Другими словами, когда вы создадите новую запись в уведомлении, и когда вы просто добавите объект и перейдете в существующее уведомление с этой структурой?
Бас Гуссен
3
@JefferyMills Вы можете иметь поле состояния, как is_notification_readв notificationтаблице, и пометить его соответствующим образом, если оно есть unread, readили deleted.
Кевин
2
Я также изо всех сил пытался понять некоторые аспекты этого решения и сделал отдельный вопрос по этому поводу: dba.stackexchange.com/questions/99401/…
user45623
27

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

Вот что я думаю о ваших проблемах:

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

  • Pesonally, я всегда стараюсь делать вещи на основе базы данных. Зачем? Потому что я могу гарантировать полный контроль над всем, что происходит - но это только я, вы можете иметь контроль без подхода, управляемого базой данных; поверь мне, ты захочешь контролировать дело;

  • Позвольте мне привести пример из реальной ситуации для вас, чтобы вы могли начать с чего-то. В прошлом году я смоделировал и внедрил систему уведомлений в какой-то социальной сети (конечно, не в Facebook). Как я раньше там хранил уведомления? У меня была notificationsтаблица, в которой я хранил generator_user_id(идентификатор пользователя, генерирующего уведомление), target_user_id(вроде бы, не так ли?), notification_type_id(Который ссылается на другую таблицу с типами уведомлений) и все что необходимо для заполнения наших таблиц (временные метки, флаги и т. д.). Моя notification_typesтаблица имела отношение к notification_templatesтаблице, в которой хранились определенные шаблоны для каждого типа уведомлений. Например, у меня был POST_REPLYтип, у которого был тип шаблона {USER} HAS REPLIED ONE OF YOUR #POSTS. Оттуда я просто лечил{}в качестве переменной и в #качестве ссылки;

  • Да, производительность должна и должна быть в порядке. Когда вы думаете об уведомлениях, вы думаете о том, что сервер работает с головы до ног. Либо, если вы собираетесь делать это с AJAX-запросами или чем-то еще, вам придется беспокоиться о производительности. Но я думаю, что это второй раз;

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

Даниэль Рибейро
источник
Почему бы мне не контролировать другое хранилище данных?
Ян Ханчич
Ну, я этого не говорил. Я сказал, что могу гарантировать контроль данных только при использовании подхода, основанного на базе данных; но это только я Я перефразирую это.
Даниэль Рибейро
@DanielRibeiro заполнители ({...}) в шаблоне уведомлений должны заменить данные заполнителей из другого набора таблиц в базе данных для различных типов уведомлений. Например, один шаблон "{user} понравился вашей фотографии.", Другой шаблон "Ваш {Pagename} имеет новый лайк." И т. Д. {PageName} и {user} и другие заполнители будут отображаться из другой таблицы базы данных, поэтому какой должна быть схема для динамического получения значения заполнителей.
Ашиш Шукла
Даниэль Рибейро, как вы заменили заполнители в соответствии с просьбой @Ashish Shukla,
Shantaram
@AshishShukla Вы использовали или заменили заполнители, и как?
Шантарам Тупе
8
╔════════════════════╗
║notification        ║
╟────────────────────╢
║Username            ║
║Object              ║
║verb                ║
║actor               ║
║isRead              ║
╚════════════════════╝

Это выглядит хорошим ответом, а не иметь 2 коллекции. Вы можете запросить по имени пользователя, объекту и isRead, чтобы получить новые события (например, 3 ожидающих запроса на добавление в друзья, 4 задаваемых вопроса и т. Д.)

Дайте мне знать, если есть проблемы с этой схемой.

Kaphy
источник
3
В верхнем ответе использовалась нормализованная структура данных, что означает отсутствие избыточности в таблицах. Ваш ответ делает это?
Аарон Холл
4

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

введите описание изображения здесь

Улучшения хорошо приняты.

Джейсон Глез
источник
Похоже, message_template будет в таблице NotificationType. Также кажется, что main_url будет в таблице уведомлений, тогда вы можете удалить таблицу Notification_Message. Можете ли вы объяснить причину, по которой у вас есть таблица NotificationMessage?
Джефф Райан