Pull to refresh
0

Сага о геолокации и как сделать гео-вебсервис на NGINX без движка базы данных и без программирования

Reading time 6 min
Views 18K
Сегодня мы поднимем довольно старую тему про геолокацию по IP-адресу и новую про быстрые веб-сервисы без «языков программирования» . Также мы опубликуем готовый образ контейнера, чтобы вы за 5 минут могли развернуть такой веб-сервис у себя.

Наша компания занимается созданием Интернет-магазинов запчастей на собственной SaaS-платформе (ABCP.RU), а также у нас есть несколько связанных проектов, например, сервис поиска запчастей 4MyCar.ru.
Как и многие другие веб-проекты, мы в своё время пришли к пониманию необходимости геолокации по IP-адресу. Например, сейчас она используется на 4MyCar.ru для определения региона (при первом входе на сайт регион автоматически устанавливается именно так).



Аналогично производится выбор ближайшего к клиенту филиала магазина на сайтах клиентов платформы ABCP.



Когда перед нами впервые возникла задача геолокации, мы только начинали изучать этот вопрос. Собственно говоря, на тот момент, кроме баз MaxMind, особых альтернатив не было. Попробовали, поигрались и забросили. В реальной работе несколько раз воспользовались MaxMind GeoLite для того, чтобы отфильтровать особо назойливых ботов, пытавшихся уложить сайты наших клиентов

(хватило фильтрации по стране в nginx, примитивная проверка в if, см. документацию ngx_http_geoip_module). Бесплатные базы не давали достаточной точности в RU, содержали названия городов в латинице и поэтому не очень нам подходили для других целей.

Через некоторое время один из наших сотрудников обнаружил отличный сайт ipgeobase.ru, позволяющий “скачать” базы геолокации для России и Украины, а также воспользоваться его XML веб-сервисом через простой http запрос. Например, переход на сайт 4mycar.ru по фразе “масляный фильтр купить в урюпинске” из соответствующего города вызывал в итоге примерно такой запрос к веб-сервису http://ipgeobase.ru:7020/geo?ip=217.149.183.4. В результатах были названия города и региона на русском языке, что было очень удобно. В очень короткие сроки работу с веб-сервисом задействовали в коде, определяющем ближайший филиал магазина. Однако, после запуска в продакшн выявилось несколько проблем:
1) обычно запрос к веб-сервису требовал некоторого небольшого времени (сотые доли секунды в нормальном состоянии из датацентра в Москве), но вот из офиса разработчиков в регионе задержки были уже выше (где-то полсекунды);
2) изредка (по нашим наблюдениям, в “часы пик”) это время было ощутимо больше, что вызывало уже неприятные задержки при ответе нашим клиентам;
3) так уж вышло, что для одного и того же клиента несколько раз требовалось провести геолокацию, отсюда возник вопрос о кешировании геоданных;
4) своими неоптимальными запросами мы создавали нагрузку на веб-сервис ipgeobase, что было нехорошо по отношению к владельцам сервиса;
5) для других стран (не RU и не UA) геолокация не работала.

Для решения этих проблем мы быстро “собрали совещание”

и получили два основных варианта решения: взять базы и написать свой веб-сервис (периодически скачиваем базы ipgeobase, импортируем в свою базу данных, отдаём через http с кешированием, например, в memcached) или сделать кеширование геоданных в memcached или redis (запрашиваем данные в ipgeobase и сохраняев в кеш). Навскидку оба варианта требовали достаточно много столь дефицитных человеко-часов разработчиков, и в итоге нашелся третий вариант: мы несколько снижаем точность (заменяем последний октет в ip адресе на 0 и исходим из того, что у провайдеров одна подсеть /24 находится в разных городах не слишком часто) и делаем на своём оборудовании кеширующий прокси на nginx с большим временем кеширования и маленькими таймаутами при запросах к ipgeobase. Этот вариант оказался очень эффективным, в разы снизил нагрузку на ipgeobase и время геолокации. Вариант с собственным веб-сервисом был отложен на неопределенное время.

Через некоторое время нам вновь понадобилась геолокация в nginx (да, опять эти боты, но теперь уже много из RU), поэтому фильтрации по стране по данным баз MaxMind оказалось недостаточно.

Потребовалось это срочно, поэтому воспользовались другим geo модулем (ngx_http_geo_module) и вывели из базы ipgeobase номер региона в переменную. Этого хватило, чтобы “заткнуть дыры”.
Вскоре нам попался скрипт ipgeobase2nginx.php, который создавал базы для nginx и, в итоге, получили человекочитаемую информацию о городе в переменной. Эти данные, а также данные MaxMind, можно уже было выводить в логи или передавать в заголовках на backend, что, в принципе, всех устраивало.

Всё это время мы периодически задумывались о дальнейшем развитии. Планы по созданию собственного веб-сервиса пылились в списках TODO и всплывали изредка в виде “а я тут вечером хочу изучить python/erlang/haskell/etc, что бы написать следующим после ‘Hello world’?”, но дальше хотелок не двигались.
Внезапно, сначала в виде шутки за чаем (just for fun), возникла идея на основе имеющихся в nginx наработок сделать веб-сервис, аналогичный ipgeobase, но без движка базы данных и использования скриптовых языков.
Быстрый анализ того, что мы имеем, дал такой результат:
1) в свободном доступе есть базы GeoLite в csv и ipgeobase в тексте;
2) модуль ngx_http_geo_module умеет выставлять значения переменных по IP-адресу, а также делает это ужасно быстро (даже использует для ускорения binary geo range base);
3) для RU и UA мы доверяем ipgeobase, но, по возможности, хотим видеть и данные MaxMind;
4) в nginx отлично реализованы ssi (ngx_http_ssi_module), причем не только для text/html, но и для других типов файлов;
5) nginx может взять ip адрес из заголовка запроса и считать, что это IP-адрес клиента (ngx_http_realip_module), а значит, передать его модулю geo.
Осталось добавить несколько “наколеночных” скриптов, которые из файлов csv и ipgeobase сделают требуемые куски конфигов для nginx.

Вот что у нас получилось:
https://yadi.sk/d/QsNN87nMesXo8 — конфиги и скрипты.

Для того чтобы показать веб-сервис в работе, мы временно развернули его на VDS, доступном по ссылке http://muxgeo-demo.4mycar.ru:6280/muxgeo/.

Чтобы быстро запустить такой сервис у себя, вы можете загрузить готовый образ LXC — https://yadi.sk/d/1WrvV2RyesYFM (логин: пароль — ubuntu:ubuntu).

Приведем краткое описание работы скриптов, в LXC мы располагаем их в /opt/scripts.

В подкаталоге /opt/scripts/in нужно положить файлы, полученные из MaxMind и ipgeobase, и немного их обработать (примерно так):
iconv -f latin1 GeoLiteCity-Location.csv | iconv -t ascii//translit > GeoLiteCity-Location-translit.csv

Для работы требуется дополнительный файл от MaxMind с названиями регионов:
dev.maxmind.com/static/csv/codes/maxmind/region.csv

Теперь сами скрипты:
GeoLite2nginx.pl — генерирует файлы out/nginx_geoip_*
ipgeobase2nginx.pl — генерирует файлы out/nginx_ipgeobase_*

Нам потребуется наложить диапазоны IP-адресов в geoip и ipgeobase. Для этого первые два скрипта при выполнении создали файлы с целочисленным представлением IP-адресов ( out/nginx_geoip_num.txt и out/nginx_ipgeobase_num.txt ). Мы вручную сделали файл in/nginx_localip_num.txt, в который положили список зарезервированных диапазонов (локальные сети и т.п.). Дополнительно из результирующих списков исключить диапазон multicast адресов.

Как мы это делаем:
Скрипт make-dup-ranges.pl проходит по списку и для каждого четного ip (начало нового диапазона) добавляет в список предыдущий (конец предыдущего диапазона), а для каждого нечетного — следующий. Дальше список сортируем, убираем дубликаты.

Скрипт make-ranges.pl создает такой конфиг с диапазонами для nginx.

Теперь у нас есть конфиги для nginx, надо их подключить.

Схема у нас будет состоять из frontend и backend (frontend передает запросы на backend с преобразованием заголовков и кешированием). Сделаем всё это на ubuntu 14.04 в контейнере LXC, nginx возьмем с официального сайта.

Содержимое out положим сюда:
/etc/nginx/muxgeo/data/

Сделаем “обвязки”, которые установят необходимые переменные:
/etc/nginx/muxgeo/muxgeo.conf
/etc/nginx/muxgeo/muxgeo-geoip.conf
/etc/nginx/muxgeo/muxgeo-ipgeobase.conf

А также примитивную логику для backend:
/etc/nginx/muxgeo/muxgeo_site.conf

Конфиги для frontend и backend находятся здесь:
/etc/nginx/conf.d/muxgeo-frontend.conf (слушает порт 6280)
/etc/nginx/conf.d/muxgeo-backend.conf (порт 6299)

Также нам понадобится файл, допустим, index.html, в котором мы выведем в нужном нам формате данные с помощью SSI в nginx. Расположим его в каталоге
/opt/muxgeo/muxgeo-backend/muxgeo

Таким образом, запрос к
http://muxgeo-demo.4mycar.ru:6280/muxgeo/?ip=217.149.183.4
транслируется на backend с подменой ip адреса на 217.149.183.4, а backend вставит информацию в нужные места html текста.

Но html страничка — это немного не то, что нам хотелось, нужен xml, как у ipgeobase. Просто заполняем шаблон выводом соответствующих полей, смотрите пример в файле muxgeo.xml

По ссылке
http://muxgeo-demo.4mycar.ru:6280/muxgeo/muxgeo.xml?ip=217.149.183.4
получим “такой же, но лучше”, чем у ipgeobase вывод xml, да еще и в utf-8

Надо JSON — без проблем. По аналогии шаблон, и готово:
http://muxgeo-demo.4mycar.ru:6280/muxgeo/muxgeo.json?ip=217.149.183.4

Хочется экзотики — давайте выведем в виде ini-файла:
http://muxgeo-demo.4mycar.ru:6280/muxgeo/muxgeo.ini?ip=217.149.183.4

Для того, чтобы протестировать работу, можно, например, создать гео-базу по адресам всех стран в формате, похожем на результат упоминавшегося выше (ipgeobase2nginx.php). Сделаем текстовый файл с шаблоном (muxgeo_fullstr.txt) и простой скрипт, который прочитает данные для всех имеющихся диапазонов.

Небольшое замечание. В примерах frontend и backend работают на одном nginx. В случае большой нагрузки имеет смысл разнести их на разные nginx, так как воркер для backend с геоданными потребляет больше памяти, чем минимальный nginx с proxy_cache.

Каково дальнейшее развитие этого проекта? Можно, например, добавить другие источники данных, совсем немого усложнив конфигурацию, а также подключить свои гео-базы, в которые поместить “уточнения, полученные из достоверных источников :) ”.
Tags:
Hubs:
+7
Comments 51
Comments Comments 51

Articles

Information

Website
www.abcp.ru
Registered
Founded
Employees
31–50 employees
Location
Россия