Мы, команда
Карт «Спутника», разрабатываем карты на основе данных «OpenStreetMap». В этой заметке мы расскажем об архитектуре нашего решения для рендеринга тайлов.
Бэкенд карт написан на языке
Go с использованием библиотеки
Mapnik, поэтому – Gopnik :-)
Исходники Gopnik доступны на
Github.
Архитектура сервиса openstreetmap.org содержит множество компонентов.
Пренебрегая деталями, можно выделить три основных части: базу данных PostgreSQL, API для загрузки и редактирования данных и система рендеринга карт.
Для того, чтобы создать свою карту на основе данных OSM, потребуется поддерживать свою копию базы данных и систему рендеринга.
Коснемся некоторых общих вопросов создания on-line карт. На текущий момент существует несколько технологий отображения: от варианта, когда сервер отдает клиенту готовую картинку, до варианта визуализации загруженных данных на стороне клиента. Наибольшей популярностью на данный момент пользуются тайловые карты. Тайл — небольшое квадратное изображение с участком карты. Клиент загружает тайлы с сервера и затем склеивает их в единое изображение.
Эта технология проста в реализации, минимально загружает клиентскую сторону. Конечно, не лишена она и недостатков, но как мне кажется, использование такой технологии будет оправдано на протяжении еще долгого времени.
В мире OpenStreetMap стек тайловых карт держится на трех китах:
базе данных (чаще всего PostgreSQL);
библиотеке рендеринга (обычно Mapnik);
клиентская javascript-библиотека (в большинстве случаев — Leaflet).
Чтобы соединить эти технологии воедино, потребуется еще ряд вещей: дополнить, по необходимости, данные, добавить иконки, описать стиль рисования, настроить генерацию и кэширование тайлов.
Разрабатывая свой стиль мы хотели добиться одновременно полноты данных и легкости восприятия. Платой за красоту стала техническая сложность. Стиль оказался примерно вдвое более сложным и тяжеловесным, нежели стиль openstreetmap.org
Как следствие, обновление тайлов занимает продолжительное время (около 5 дней), необходимо кэшировать значительное количество данных (примерно 2Тб). Все это сильно осложняет жизнь.
Чаще всего стек рендеринга строится на основе HTTP-сервера Apache c плагином mod_tile и бекендом, непосредственно занимающимся генерацией тайлов: Tirex или Renderd.
Эта схема проверена временем, именно так и работала первая версия maps.sputinik.ru. Однако нельзя сказать, что она нас полностью устраивала. Первое, с чем мы столкнулись — проблематичность использования облачных хранилищ для тайлового кэша. mod_tile разрабатывался с оглядкой на файловую систему, а сторадж с eventual consistency использовать и вовсе невозможно без серьезной доработки. Кроме того, схема балансировки renderd имеет определенные недочеты, использовать ее в среде нескольких дата-центров затруднительно. Да и штатные утилиты особым удобством не отличаются.
Мы провели эксперимент: набросали прототип системы, которая делает ровно то, что мы от нее хотели. Прототип прижился и получил дальнейшее развитие. По своей сути система, во многом, повторяет архитектуру mod_tile, с некоторыми расширениями и дополнениями. Она написана на языке Go с использованием библиотеки Mapnik, за что и получила свое название — Gopnik.
Gopnik легко масштабируется по произвольному количеству узлов, может использовать различные системы хранения данных, поддерживает расширение при помощи плагинов.
Gopnik состоит из двух компонент: dispatcher и render. Dispatcher принимает запросы от пользователя, проверяет наличие тайлов в кэше, в случае необходимости выбирает подходящий узел в кластере по хэш-функции координат и ставит ему задачу на генерацию тайлов. Render обеспечивает непосредственно рендеринг.
Отдельное внимание уделено проблеме взаимодействия с хранилищем. Загвоздка в том, что пользователи запрашивают тайлы по одному. Сервер же, для экономии ресурсов и для уменьшения проблем со стыковкой отдельных картинок генерирует сразу большую область карты (обычно 8x8 тайлов + дополнительный буфер), которая затем нарезается на куски. Такую область называют метатайлом. Gopnik группирует запросы от пользователя по метатайлам, при первом запросе к несохраненному в кэше метатайлу, начинается рендеринг. Все запросы, поступившие позднее, присоединяются к ожиданию. По завершении рендеринга возвращаются тайлы для всех ожидающих запросов и начинается фоновое сохранения в кэш. Кроме того, некоторое (настраиваемое) время результаты рендеринга хранятся в локальном кэше узла на случай, если клиент запросит другие тайлы из только что сгенерированного метатайла, так же локальный кэш может держать данные до полного сохранения их в сторадж, если последний поддерживает подтверждение сохранения.
Однако на лету можно генерировать лишь те части карты, где количество данных ограничено. В остальных случаях приходится подготавливать тайлы заранее. Для этого в Gopnik'е существует специальный набор утилит.
При помощи утилиты importer подготавливается план генерации. Prerender координирует процесс. Кластер узлов prerender slave непосредственно выполняет рендеринг.
Система является распределенной, устойчивой к сетевым сбоям и отказу slave-узлов. Единой точкой отказа является координатор, однако, благодаря логам рендеринга, процесс может быть продолжен с места сбоя. Такое решение позволяет избежать сложных полностью распределенных систем, при этом обеспечить должный уровень отказоустойчивости.
Исходники на Github
Документация
По мотивам
доклада Максима Дементьева на Highload++ 2014
комментарии (4)