Вернемся к теме интерактивных сайтов с обратной стороны, серверной. В ней есть огромный простор для творчества, так как в отличии от клиентской части отсутствуют ограничения, накладываемыми браузерами. С "простором" же приходит и неоднозначность/неопределенность, вариантов как реализовать одно и то же множество, так что возможно приводимые мной примеры Вам окажутся не по душе - и это нормально, правильный путь не единственный, их много :)
Приступим!
Внутренние сервисы
Напомню, что обычно на внутренние сервисы ложится реализация всей или большей части бизнес-логики приложения. Они получают пользовательские запросы в стандартизированном виде через прослойки в виде внешних интерфейсов и, при необходимости взаимодействуя друг с другом и остальными компонентами системы, определяют какой ответ необходимо отправить и какие другие действия предпринять.
Я не буду здесь особо вдаваться в возможные детали реализации самой бизнес-логики - она практически всегда уникальна, скорее заслуживает внимания её "обертка" - сам процесс, принимающий и создающий внутренние запросы.
Вообще создание внутренних сервисов очень хорошо ложится на так называемую модель "акторов", система разбивается на некие логические примитивы, общающиеся между собой исключительно передачей сообщений. По сути процессы с определенными разработчиками наборами входящих и исходящих сообщений и алгоритмом преобразования одних в другие. При таком подходе группа одинаково функционирующих акторов (вероятно распределенная по нескольким серверам для отказоустойчивости и возможности масштабирования) и образует внутренний сервис.
На практике есть масса способов воплотить эту модель в жизнь, перечислю с пояснениями наиболее заслуживающие внимания на мой взгляд:
- Функциональные языки программирования, в Erlang и Scala модель акторов является практически "сердцем" всего языка и связанной платформы; у обоих есть библиотеки для реализации надежных, высокопроизводительных и масштабируемых акторов (OTP и Akka, соответственно). Если не боитесь кардинально отличающейся от нынче модного ООП парадигмы разработки, этот вариант наиболее жизнеспособный, рекомендую.
- Асинхронный HTTP-сервер, в частности Tornado и node.js - они основаны на epoll и помимо эффективной обработки HTTP-запросов умеют и эффективно их отправлять посредством идущего в комплекте асинхронного же клиента. При таком подходе по сути получается несколько "уровней" HTTP-серверов, первый из которых публично доступен для общения с внешним миром и в ответ на каждый входящий запрос обращается сразу к нескольким внутренним HTTP-сервисам (вероятно параллельно) и на их основе составляет ответ пользователю. Этот подход одно время активно пропагандировали на конференциях ребята из одного крупного отечественного сайта с вакансиями. Особенным бонусом этого варианта является возможность использовать в роли внутреннего сервиса какую-то старую, доставшуюся по наследству (legacy), систему, которая с одной стороны по-прежнему нужна, а с другой - человек, который в ней разбирался уже давно уволился.
- С++ и Thrift - хоть одного из участников этой пары можно легко заменить на альтернативу, вместе они смотрятся наиболее органично: потенциально высокопроизводительная реализация бизнес-логики на С++ плюс проверенная в деле многими крупными и очень крупными проектами обертка для создания серверов и клиентов, легко общающихся из разных языков программирования (речь о Thrift, если не очевидно). Если в команде проекта есть гуру C++ - этот вариант Ваш, в противном случае не рекомендую, т.к. очень легко накосячить.
Иногда внутренние сервисы возможно сделать совсем изолированными, то есть без взаимодействия с другими компонентами системы. Но в большинстве случаев это не так, зачастую для принятия решения им необходимы внешние данные.
База данных и кэширование
По большому счету интерактивные сайты не особо сильно отличаются от статичных с точки зрения организации хранения данных.
Из особенностей хочу отметить более-менее четкое разграничение стабильной информации и свежей, актуальной лишь короткое время. Для социальной сети это могут быть, например, профили пользователей (стабильная) и сообщения (свежая).
В соответствии с этим стоит выбирать хранилище данных и политику кэширования:
- Стабильная информация, которая редко обновляется и в тысячи раз чаще читается, прекрасно поддается кэшированию и возможно даже прекрасно будет себя чувствовать в реляционной СУБД.
- Свежую информацию вероятно вообще важнее доставить в кратчайшие сроки получателю, а сохранять в персистентном виде можно вообще постфактум для архива, на маловероятный случай когда она повторно понадобится. Про кэширование лучше вообще забыть. Для этого самого "архива" часто используют нереляционные распределенные базы данных вроде HBase, Cassandra или Riak. А про оперативную доставку получателю поговорим в следующем разделе.
- Хранилища данных в памяти вроде memcached или Redis с отключенной персистентностью можно использовать независимо для временного хранения каких-то побочных данных (восстановимых производных данных или просто чего-то не особо важного, вроде счетчиков пользователей онлайн).
Потоки сообщений
Одной из ключевых задач интерактивного сайта является доставка сообщений пользователем в реальном времени, причем их источник может быть как внешний, так и внутренний, зачастую это просто другие пользователи.
Часть системы, отвечающую за маршрутизацию таких сообщений, обычно назвают брокером сообщений (message broker). Для доставки сообщений в браузер чаще всего используют интерфейс сериализованных данных, подробно обсуждавшийся в одной из предыдущих статей серии. Когда пользователь устанавливает соединение с этим интерфейсом, он, в свою очередь, напрямую или через внутренний сервис регистрируется в брокере сообщений для оперативного получения сообщений, предназначенных соответствующему пользователю.
Предлагаю рассмотреть типичные сценарии маршрутизации сообщений, они довольно просты:
- Конкретный получатель, к сообщению (которое обычно никак не анализируется брокером) прикрепляется метка-идентификатор, обозначающий кому именно оно предназначено. Такое сообщение получит только процесс, зарегистрировавшийся с аналогичным идентификатором. Типичный пример использования - личные сообщения от пользователя к пользователю.
- Группа получателей, актуально для проектов, где пользователи взаимодействуют не на глобальном пространстве, а разбиты на части по какому-то признаку. Скажем это может быть какой-то B2B сервис и сообщения ходят только между сотрудниками одной компании-клиента. Обычно используется такие же метки, как и при конкретном получателе, только с одной из сторон (обычно принимающей) вместо конкретного идентификатора указывается какой-то паттерн, вроде
CompanyA.*
. - Публичные сообщения - получают все пользователи, метки не используются. Обычно это уведомления о глобальных для сайта событиях или публикации каких-то материалов.
Реализаций брокеров сообщений есть много разных, общий принцип работы у всех примерно одинаковый и соответствует трем изложенным выше пунктам. Для интернет-проектов очень рекомендую RabbitMQ, в нем эти стратегии маршрутизации называются direct, topic и fanout exchange, соответственно.
Отправлять сообщения через брокер в большинстве случаев будут различные внутренние сервисы в случае возникновения определенных событий (читай: получения ими определенных входящих сообщений и попадания в определенную ветвь алгоритма их обработки). Какую стратегию маршрутизации использовать - тоже на их совести.
К слову, внутренние сервисы также могут подписываться на получение части сообщений из брокера, например для асинхронного создания "архива" событий, отправки почтовых уведомлений или выполнения ресурсоемких задач вроде конвертации медиа-файлов.
При получении сообщения клиентская часть меняет соответствующим образом текущую версию открытой страницы. От открытия дополнительного всплывающего окна до просто смены цифры в количестве чего-нибудь.
Будьте аккуратны с публичными сообщениями - их количество в единицу времени может рости очень быстро с увеличением размеров аудитории. Горизонтально масштабируемый брокер сообщений очень важен, если в Вашем проекте в основном используются именно публичные сообщения.
Заключение
Таким образом наша цепь замыкается - между браузерами любых пользователей можно в "мягком" реальном времени пересылать любые сообщения, пропуская их через бизнес-логику для регулирования данного процесса, и, при необходимости, использовать постоянные и временные хранилища данных.
Как я уже упоминал в первой статье серии, серверная часть у интерактивного сайта не так уж и кардинально отличается от любого другого - примерно те же компоненты, примерно так же работают и взаимодействуют. Разница в деталях.
В следующей, заключительной, статье серии мы по второму кругу пройдемся по ключевым моментам и попробуем рассмотреть наиболее перспективные моменты для улучшений и оптимизации, хотя, как говорится, заранее оптимизировать - плохая примета :)