Архитектура и платформа проекта Одноклассники
В этом посте расскажем о накопленном за 5 лет опыте по поддержанию высоконагруженного проекта. Надеемся, что коллегам-разработчикам будет интересно узнать, что и как мы делаем, какие проблемы и трудности у нас возникают и как мы справляемся с ними.
Базовая статистика
До 2.8 млн. пользователей в онлайне в часы пик
7,5 миллиардов запросов в день (150 000 запросов в секунду в часы пик)
2 400 серверов, систем хранения данных
Сетевой трафик в час пик: 32 Gb/s
Архитектура
Слоеная архитектура:
• presentation layer (презентационный слой или попросту WEB сервера, формирующие HTML)
• business services layer (сервера, обеспечивающие подбор и обработку данных)
• caching layer (кеширование часто используемых данных)
• persistence layer (сервера БД)
• common infrastructure systems (системы логирования статистики, конфигурации приложений, локализация ресурсов, мониторинг)
Презентационный слой:
• Используем свой фреймворк, позволяющий строить композицию страниц на языке JAVA, используя собственные GUI фабрики (оформление текста, списки, таблицы, портлеты).
• Композиция страниц состоит из независимых блоков (обычно портлетов), что позволяет обновлять информацию на экране частями, используя AJAX запросы. Такой подход к навигации позволяет избавиться от постоянных перезагрузок страницы, тем самым важные функции сайта (Сообщения, Обсуждения и Оповещения) всегда доступны пользователю. Без javascript страница полностью работоспособна, кроме функциональностей, написанных на GWT — при переходах по ссылкам она просто полностью перерисовывается.
• Функциональные компоненты как Сообщения, Обсуждения и Оповещения, а также все динамичные части (шорткат меню, фотометки, сортировка фотографий, ротирование подарочков) написаны, используя фреймворк Google Web Toolkit.
Подбор, обработка и кеширование данных:
Код написан на Java. Есть исключения – некоторые модули для кеширования данных написаны на C и C++.
Java потому, что это удобный для разработки язык, много наработок в различных сферах, библиотек, open source проектов на Java.
На уровне бизнес логики располагаются порядка 25 типов серверов/компонентов и кешей, которые общаются между собой через удаленные интерфейсы. Каждую секунду происходит порядка 3 000 000 удаленных запросов между этими модулями.
Для кеширования данных используется «самописный» модуль odnoklassniki-cache. Он предоставляет возможность хранения данных в памяти средствами Java Unsafe. Кешируем все данные, к которым происходит частое обращение. Например: информацию из профайлов пользователей, группы пользователей, информацию о самих группах, конечно же, граф связей пользователей, граф связей пользователей и групп, праздники пользователей, некоторую мета информацию о фотографиях и т.п.
Для примера, один из серверов, кеширующий граф связей пользователей, в час пик способен обработать около 16 600 запросов в секунду. CPU при этом занят до 7%, максимальный load average за 5 минут — 1.2. Количество вершин графа > 85 миллионов, связей 2 500 миллиона (два с половиной миллиарда). В памяти граф занимает 30 GB.
Распределение и балансировка нагрузки:
• взвешенный round robin внутри системы;
• вертикальное и горизонтальное партиционирование данных как в базах данных, так и на кеширующем уровне;
• сервера на уровне бизнес логики разбиты на группы. Каждая группа обрабатывает различные события. Есть механизм маршрутизации событий, т.е. любое событие (или группу событий) можно выделить и направить на обработку на определенную группу серверов.
Управление сервисами происходит через централизованную систему конфигурации. Система самописная. Через WEB интерфейс можно поменять расположение портлетов, конфигурацию кластеров, изменить логику некоторых сервисов и т.д. Измененная конфигурация сохраняется в базе данных. Каждый из серверов периодически проверяет, есть ли обновления для приложений, которые на нем запущены. Если есть – применяет их.
Данные, сервера БД, резервные копии:
Общий объем данных без резервирования – 160 TB. Используются два решения для хранения и сервирования данных – MS SQL и BerkeleyDB. Данные хранятся как минимум в двух копиях. В зависимости от типов данных, копий может быть от двух до четырех. Имеется ежесуточный бэкап всех данных. Каждые 15 минут делаются резервные копии накопившихся данных. В результате такой стратегии резервного копирования максимально возможная потеря данных – 15 минут.
Оборудование, датацентры, сеть:
Используются двухпроцессорные, 4-х ядерные сервера. Объем памяти от 4 до 48 GB, в зависимости от функционала. В зависимости от типов и использования данных, они хранятся либо в памяти серверов, либо на дисках серверов, либо на внешних системах хранения.
Все оборудование размещено в 3 датацентрах. Всего около 2 400 серверов и систем хранения данных. Датацентры объединены в оптическое кольцо. На данный момент на каждом из маршрутов емкость составляет 30 Gb/s. Каждый из маршрутов состоит из физически независимых друг от друга оптоволоконных пар. Эти пары агрегируются в общую “трубу” на корневых маршрутизаторах.
Сеть разделена на внутреннюю и внешнюю. Сети разделены физически. Разные интерфейсы серверов подключены в разные коммутаторы и работают в разных сетях. По внешней сети WEB сервера, общаются с миром. По внутренней сети все сервера общаются между собой.
Топология внутренней сети – звезда. Сервера подключены в L2 коммутаторы (access switches). Эти коммутаторы подключены как минимум двумя гигабитными линками к agregation стеку маршрутизаторов. Каждый линк идет к отдельному коммутатору в стеке. Для того, чтобы эта архитектура работала, используем протокол RSTP. При необходимости, подключения access коммутаторов к agregation стеку осуществляются более чем двумя линками. Тогда используется link aggregation портов.
Agregation коммутаторы подключены 10Gb линками в корневые маршрутизаторы, которые обеспечивают как связь между датацентрами, так и связь с внешним миром.
Используются коммутаторы и маршрутизаторы от компании Cisco. Для связи с внешним миром мы имеем прямые подключения с несколькими крупнейшими операторами связи
Сетевой трафик в часы пик – 32 Gb/s
Система статистики:
Существует библиотека, отвечающая за логирование событий. Библиотека используется во всех модулях. Она позволяет агрегировать статистику и сохранять ее во временную БД. Само сохранение происходит с помощью библиотеки log4j. Обычно храним количество вызовов, максимальное, минимальное и среднее время выполнения, количество ошибок, возникших при выполнении.
Из временных баз вся статистика сохраняется в DWH. Каждую минуту сервера DWH ходят во временные базы в production и забирают данные. Временные базы периодически очищаются от данных.
Пример кода, который сохраняет статистику об отосланных сообщениях:
public void sendMessage(String message) {
long startTime = LoggerUtil.getMeasureStartTime();
try {
/**
* business logic - send message
*/
LoggerUtil.operationSuccess(LogFactory.getLog({log's appender name}), startTime, "messageService", "sendMessage");
} catch (Exception e) {
LoggerUtil.operationFailure(LogFactory.getLog({log's appender name}), startTime, "messageService", "sendMessage");
}
}
Наша система DWH хранит всю статистику и предоставляет инструменты для ее просмотра и анализа. Система построена на базе решений от Microsoft. Сервера баз данных – MS SQL 2008, система генерации отчетов – Reporting services. Сейчас DWH – это 13 серверов, находящихся в отделенной от production среде. Некоторые из этих серверов обеспечивают операционную статистику (т.е. онлайн статистику). Некоторые отвечают за хранение и предоставление доступа к исторической статистике. Общий объем статистических данных — 13 TB.
Планируется внедрение мультиразмерного (multi-dimension) анализа статистики на основе OLAP.
Мониторинг
Мониторинг разделен на две составляющие:
1. Мониторинг сервисов и компонентов сайта
2. Мониторинг ресурсов (оборудование, сеть)
Первичен мониторинг сервисов. Система мониторинга своя, основанная на оперативных данных в DWH. Есть дежурные, чья обязанность мониторить показатели работы сайта и в случае каких-либо аномалий предпринимать действия для выяснения и устранения причин этих аномалий.
В случае с мониторингом ресурсов, следим как за “здоровьем” оборудования (температура, работоспособность компонентов: CPU, RAM, дисков и т.д.), так и за показателями ресурсов серверов (загрузка CPU, RAM, загруженность дисковой подсистемы и т.п.). Для мониторинга “здоровья” оборудования используем Zabbix, статистику по использованию ресурсов серверов и сети накапливаем в Cacti.
Оповещения о наиболее критичных аномалиях приходят по смс, остальные оповещения отсылаются по емейлу.
Технологии:
• Операционные системы: MS Windows, openSUSE
• Java, C, C+. Весь основной код написан на Java. На С и С+ написаны модули для кеширования данных.
• Используем GWT для придания динамики WEB интерфейсу. С использованием GWT написаны такие модули как Сообщения, Обсуждения и Оповещения
• WEB сервера – Apache Tomcat
• Сервера бизнес логики работают под JBoss 4
• Балансировщики нагрузки на WEB слое – LVS. Используем IPVS для балансировки на Layer-4
• Apache Lucene для индексирования и поиска текстовой информации
• Базы данных:
MS SQL 2005 Std edition. Используется во многом потому, что так исторически сложилось. Сервера с MS SQL объединены в failover кластера. При выходе из строя одной из рабочих нод, standby нода берет на себя ее функции
BerkeleyDB – для работы с BDB используется своя, внутренняя библиотека. Используем BDB, C реализацию, версии 4.5. Двухнодовые master-slave кластера. Между мастером и слейвом родная BDB репликация. Запись происходит только в master, чтение происходит с обеих нод. Данные храним в tmpfs, transaction логи хранятся на дисках. Каждые 15 минут делаем бэкап логов. Сервера одного кластера размещены на разных лучах питания дабы не потерять обе копии данных сразу.
В разработке новое решение для хранения данных. Нам необходим еще более быстрый и надежный доступ к данным.
• При общении серверов между собой используем свое решение, основанное на JBoss Remoting
• Общение с SQL базами данных происходит посредством JDBC драйверов
Люди:
Над проектом работают около 70 технических специалистов. Из них 40 разработчиков, 20 системных администраторов и инженеров, 8 тестеров.
Все разработчики разделены на небольшие команды (1-3 человек). Каждая из команд работает автономно и разрабатывает либо какой-то новый сервис, либо работает над улучшением существующих. В каждой из команд есть технический лидер или архитектор. Он ответственен за архитектуру сервиса, выбор технологий и подходов. На разных этапах разработки к команде могут примыкать дизайнеры, тестеры и системные администраторы.
Например, существует отдельная команда сервиса Группы. Или команда, разрабатывающая коммуникационные сервисы сайта (такие как системы сообщений, обсуждений, ленту активности). Есть команда платформы, которая тестирует, обкатывает и внедряет новые технологии, оптимизирует уже существующие решения. В данный момент одна из задач этой команды – разработка и внедрение высокоскоростного и надежного решения для хранения данных.
Основные принципы и подходы в разработке
Разработка ведется небольшими итерациями. Как пример жизненного цикла разработки можно привести 3-х недельный цикл:
0 неделя — определение архитектуры
1 неделя — разработка, тестирование на компьютерах разработчиков
2 неделя — тестирование на pre-production среде, релиз на production среду
Практически весь новый функционал делается «отключаемым». Типичный запуск новой «фичи» выглядит следующим образом:
1. функционал разрабатывается и попадает в production релиз
2. через централизованную систему конфигурации функционал включается для небольшой части пользователей. Анализируется статистика активности пользователей, нагрузка на инфраструктуру
3. если предыдущий этап прошел успешно, функционал включается постепенно на все большей аудитории. Если в процессе запуска нам не нравится собранная статистика, либо непозволительно вырастает нагрузка на инфраструктуру, то функционал отключается, анализируются причины, исправляются ошибки, происходит оптимизация и все повторяется с 1-го шага
Best practices, tricks & tips
Специфика работы с СУБД:
• Мы используем как вертикальное, так и горизонтальное партиционирование, т.е. разные группы таблиц располагаются на разных серверах (вертикальное партиционирование), а данные больших таблицы дополнительно распределяются между серверами (горизонтальное партиционирование). Встроенный в СУБД аппарат партиционирования не используется — вся логика располагается на уровне бизнес сервисов.
• Распределенные транзакции не используются — все транзакции только в пределах одного сервера. Для обеспечения целостности, связанные данные помещаются на 1 сервер или, если это невозможно, дополнительно программируется логика восстановления данных.
• В запросах к БД не используются JOIN даже среди локальных таблиц для минимизации нагрузки на CPU. Вместо этого используется денормализация данных или JOIN происходят на уровне бизнес сервисов. В этом случае JOIN происходит как с данными из БД, так и с данными из кеша.
• При проектировании структуры данных не используются внешние ключи, хранимые процедуры и триггеры. Опять же для снижения нагрузки на CPU серверов БД.
• SQL операторы DELETE также используются с осторожностью — это самая тяжелая операция из DML. Стараемся не удалять данные лишний раз или используем удаление через маркер — запись сначала отмечается как удаленная, а потом удаляется фоновым процессом из таблицы.
• Широко используются индексы. Как обычные, так и кластерные. Последние для оптимизации наиболее частых запросов в таблицу.
Кеширование:
• Используются кеш сервера нашей собственной разработки, реализованные на Java. Некоторые наборы данных, как например профили пользователей, социальный граф, и т.п. целиком хранятся в кеше.
• Данные партиционируются на кластер кеш серверов. Используется репликация партиций для обеспечения надежности.
• Иногда требования к быстродействию настолько велики, что используются локальные короткоживущие кеши данных полученных с кеш серверов, расположенные непосредственно в памяти серверов бизнес логики.
• Кеш сервера, кроме обычных операций ключ-значение, могут выполнять запросы по данным, хранящимся в памяти, минимизируют таким образом передачу по сети ненужных данных. Используется map-reduce для выполнения запросов и операций на кластере. В особо сложных случаях, например для реализации запросов по социальному графу, используется язык C. Это помогает повысить производительность.
• Для хранения больших объемов данных в памяти используется память вне кучи Java (off heap memory) для снятия ненужной нагрузки с Java GC.
• Кеши могут использовать локальный диск для хранения данных, что превращает их в высокопроизводительный сервер БД.
Оптимизация скорости загрузки и работы страницы
• Кешируем все внешние ресурсы (Expires и Cache-Control заголовки). CSS и JavaScript файлы минимизируем и сжимаем (gzip).
• Для уменьшения количества HTTP запросов с браузера, все JavaScript и CSS файлы объединяются в один. Маленькие графические изображения объединяются в спрайты.
• При загрузке страницы скачиваются только те ресурсы, которые на самом деле необходимы для начала работы.
• Никаких универсальных CSS селекторов. Стараемся не использовать типовые селекторы (по имени тэга).
• Если необходимы CSS expressions, то пишем «одноразовые». По возможности избегаем фильтров.
• Кешируем обращения к DOM дереву, а так же свойства элементов, приводящие к reflow. Обновляем DOM дерево в «оффлайне».
• В GWT используем UIBinder и HTMLPanel для создания интерфейсов.
Полезного чтения! Будем рады вопросам.