LinkedIn является крупнейшей в мире социальной сетью для профессионалов. Популярность этого проекта может быть далека, от более общетематических социальных сетей, таких как, скажем Facebook, но, тем не менее, нагрузка на серверную часть проекта создается пользователями серьезная. О том как этот проект с ней справляется и пойдет речь далее.
Предисловие
Сообщение о публикации двух презентаций c JavaOne 2008 о LinkedIn и их обобщении от Overn Hurvitz пронеслось по русскоязычным новостным ресурсам уже достаточно давно, но время черкнуть пару строк обо всем этом нашлось у меня только сейчас.
- LinkedIn - A Professional Social Network Built with Java™ Technologies and Agile Practices
- LinkedIn Communication Architecture
Статистика
- 22 миллиона пользователей;
- 4+ миллиона уникальных посетителей в день;
- 40 миллионов просмотров страниц в день;
- 2 миллиона поисковых запросов в день;
- ежедневно отправляются 250 тысяч приглашений;
- 1 миллион ответов в день;
- 2 миллиона электронных сообщений ежедневно.
Платформа
- Solaris (как x86, так и SPARC)
- Tomcat и Jetty
- Oracle и MySQL
- Никакого ORM
- ActiveMQ для JMS
- Lucene в качестве основы для поиска
- Spring в роли "клея"
Серверная архитектура
2003-2005
- одно монолитное веб-приложение;
- одна общая база данных;
- сетевой граф кэшируется в памяти в "Облаке";
- поиск пользователей реализован с помощью Lucene, он работал на той же машине, что и "Облако", так как поиск был отфильтрован в соответствии с сетью пользователя, таким образом было удобно совмещать эти две функции на одной машине;
- веб-приложение напрямую обновляет базу данных, а она, в свою очередь, обновляет "Облако".
2006
- Добавлена репликация для уменьшения нагрузки на основную базу данных. Реплики предоставляют данные в режиме "только для чтения", а репликация ведется в асинхронном режиме с помощью дополнительного компонента под названием Databus, с его появлением обновление данных стало выглядеть следующим образом:
- сначала какие-либо изменения происходят в веб-приложении;
- веб-приложение обновляет основную базу данных;
- она, в свою очередь, отправляет обновления на Databus;
- далее уже Databus обновляет: реплики, Облако и поисковый индекс.
- Поиск был вынесен на отдельный сервер.
2008
- веб-приложение само по себе практически ничего не делает: бизнес логика распределена по отдельным сервисам;
- веб-приложение все так же предоставляет пользователям графический интерфейс, но для его генерации она теперь вызывает сервисы;
- каждый сервис имеет свою специфическую базу данных (т.е. вертикальное сегментирование);
- такой подход позволяет другим приложениям (помимо основного) получать доступ к LinkedIn, такие приложения были созданы для работодателей, рекламных служб, и так далее.
Облако
- "Облаком" в LinkedIn называют сервер, который кэширует весь граф социальной сети в памяти;
- его размеры: 22 миллиона вершин и 120 миллионов ребер;
- занимает 12GB оперативной памяти;
- одновременно держится в памяти в 40 экземплярах;
- построение Облака из данных, в дисковой системе, занимает 8 часов;
- обновления происходят в режиме реального времени с помощью Databus;
- во время остановки данные записываются на диск;
- кэш реализован с помощью C++, а доступ предоставляется по JNI;
- они выбрали именно C++ так как требовалось использовать минимум оперативной памяти, а также, задержки, связанные с Garbage Collection, были неприемлемыми.
- размещение всех данных в памяти является ограничением, но, как удалось выяснить в LinkedIn, разбиение графов на части - не самая тривиальная задача.
Облако кэширует целиком весь граф социальной сети LinkedIn, но на практике же пользователям требуется видеть его со своей точки зрения. Данная задача является вычислительно сложной, по-этому она выполняется лишь один раз при создании новой сессии, а затем система поддерживает результат в кэше. Такой подход требует 2 MB оперативной памяти на каждого активного пользователя. В течении сессии такой кэш обновляется только если сам пользователь сделал какие-либо изменения в нем, если же изменение вызвано другими пользователями - владелец сессии не заметит изменений.
Помимо этого используется кэширование профилей пользователей средствами EHcache. Одновременно в памяти хранится до 2 миллионов профилей (из 22 миллионов). Изначально планировалось использовать алгоритм LFU, но оказалось, что иногда EHcache зависал секунд на 30 во время перерасчета LFU, таким образом было принято решение о использовании вместо него алгоритма LRU.
Архитектура коммуникации
Как известно, пользователи практически любой социальной сети генерируют огромное количество сообщений в единицу времени, причем каждый тип сообщений обычно требует индивидуального подхода, но в целом их можно разделить на две категории: постоянные и временные. В LinkedIn разработчики построили по отдельному сервису, для обработки каждой из этих категорий. Каждый из них определенно заслуживает отдельного внимания, так как общего в них мало.
Сервис постоянных сообщений
Этот коммуникационный сервис выполняет все операции, связанные с постоянными сообщениями: приватными сообщениями и электронной почтой. Перед ним ставится вполне тривиальный ряд задач: доставлять сообщения получателям и сохранять их на постоянной основе, но на самом деле этим все не ограничивается: должны также поддерживаться, скажем, доставка сообщений с задержкой, массовые рассылки, отмена отправки сообщения, возможность добавления в сообщения какого-либо интерактивного контента. Реализован он был примерно следующим образом:
- вся система работает асинхронно и активно использует JMS;
- клиенты отправляют сообщения так же через JMS;
- далее сообщения перенаправляются с помощью сервиса маршрутизации в соответствующий почтовый ящик или напрямую в обработку электронной почты;
- доставка сообщений происходит как с помощью Pull (клиенты запрашивают свои сообщения), так и с использованием Push (т.е. отправки сообщений);
- помимо этого используется Spring с их собственными закрытыми расширениями, использующими HTTP-RPC.
Приемы, способствующие масштабируемости
- Функциональное сегментирование: отправленные, полученные, архивные сообщения. (т.е. вертикальное сегментирование)
- Классовое сегментирование: пользовательские, гостевые, корпоративные почтовые ящики.
- Сегментирование по диапазонам: по идентификаторам пользователей или по лексикографическим диапазонам самих сообщений. (т.е. горизонтальное сегментирование)
- Асинхронное выполнение операций.
Сервис сетевых обновлений
Этот сервис обеспечивает работу любых временных уведомлений, например, вызванных изменением статуса пользователей в контакт-листах. Такие сообщения должны с течением времени удаляться из-за быстрой потери актуальности, а также должна поддерживаться группировка и приоритезация сообщений. Функционирование этого сервиса оказалось не настолько очевидно, по сравнению с предыдущим, так что до итогового варианта было перепробовано масса менее удачных решений, но обо всем по порядку.
Изначальная архитектура (до 2007 года)
- используется много серверов, которые могут содержать обновления;
- клиенты отправляют запросы на каждый сервис отдельно: вопросы, обновления профилей и т.д.
- на сбор всех данных требовалось относительно много времени.
В 2008 году вся эта система поэтапно эволюционировала собственно в сам сервис сетевых обновлений:
Первая итерация
- клиент отправляет единственный запрос сервису сетевых обновлений;
- этот сервис в свою очередь параллельно отправляет всем остальным сервисам соответствующие запросы.
- результаты агрегируются и все вместе возвращаются клиенту;
- весь процесс основывается на Pull.
Вторая итерация
- стал использоваться метод Push: каждый раз, когда происходит какое-либо событие, они помещаются в пользовательский "почтовый ящик", в момент запроса пользователя ему возвращается просто содержимое, уже ожидающее своего звездного часа в специально том самом "ящике";
- такой подход сильно ускоряет процесс чтения, так как на тот момент данные уже готовы;
- с другой стороны, какая-то часть данных может так никогда и не понадобиться, что приводит к бесполезным передвижениям данных и лишнему используемому дисковому пространству;
- небольшая часть обработки данных все же производится уже в момент запроса пользователя (например, объединение нескольких обновлений от определенного пользователя в одно);
- обновления хранятся в CLOB'ах: по одному CLOB'у на каждый тип обновления для каждого пользователя (то есть в сумму около 15 CLOB'ов на каждого пользователя);
- сначала использовался размер CLOB'ов равный 8 KB, что было явно больше требуемого и приводило к существенному количеству неиспользуемого дискового пространства.
- вместо CLOB'ов можно было бы использовать дополнительные таблици по одной на каждый тип обновлений, но в этом случае пришлось бы постоянно удалять из них устаревшие записи, что было бы чрезвычайно неэффективно.
- в дополнение к этому использовался JMX для мониторинга и изменения конфигурации в реальном времени, что оказалось очень удобным и полезным.
Третья итерация
- Цель: повысить производительность путем сокращения количества обновлений CLOB'ов, так как они требуют много вычислительных ресурсов.
- Был добавлен буфер: колонки в таблицах типа
varchar(4000)
, в которых данные помещались изначально. При полном заполнении ячейки данные перемещаются в CLOB; это позволило на порядок сократить количество их обновлений. - Уменьшен размер самих сообщений об обновлениях.
И напоследок пару советов от LinkedIn
- нельзя бесконечно долго ограничиваться одной базой данных: используйте много баз данных как с вертикальным, так и с горизонтальным сегментированием данных;
- забудьте о ссылочной целостности и кросс-серверных JOIN'ах;
- забудьте о 100% целостности данных;
- при большом масштабе издержки могут стать проблемой: оборудование, базы данных, лицензии, системы хранения данных, электроэнергия и так далее;
- как только вы станете достаточно крупны и популярны, спаммеры и прочие злые люди не заставят себя долго ждать;
- не забывайте про кэширование!!!
- используйте асинхронные потоки данных;
- аналитика и построение отчетов может стать непростой задачей, постарайтесь задуматься о них заранее в процессе планирования системы;
- имейте всегда ввиду, что Ваша система может упасть в любой момент;
- не стоит недооценивать траекторию своего роста.
P.S.
Когда уже закончил переводить в голову пришла мысль, что если читателям будет интересно взглянуть на оригинальные презентации (хотябы ради иллюстрационного материала, который там вполне нагляден), то было бы проще сделать это прямо здесь, так что вот, для Вашего же удобства:
Кстати если Вы еще не успели подписаться на RSS - сейчас самое время!