- PVSM.RU - https://www.pvsm.ru -

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

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

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

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

Когда перед нами впервые возникла задача геолокации, мы только начинали изучать этот вопрос. Собственно говоря, на тот момент, кроме баз MaxMind [3], особых альтернатив не было. Попробовали, поигрались и забросили. В реальной работе несколько раз воспользовались MaxMind GeoLite для того, чтобы отфильтровать особо назойливых ботов, пытавшихся уложить сайты наших клиентов
Сага о геолокации и как сделать гео-вебсервис без движка базы данных и без программирования - 2
(хватило фильтрации по стране в nginx, примитивная проверка в if, см. документацию ngx_http_geoip_module [4]). Бесплатные базы не давали достаточной точности в RU, содержали названия городов в латинице и поэтому не очень нам подходили для других целей.

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

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

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

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

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

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

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

Приведем краткое описание работы скриптов, в 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 [15]

Теперь сами скрипты:
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 [16]
транслируется на 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 [17]
получим “такой же, но лучше”, чем у ipgeobase вывод xml, да еще и в utf-8

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

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

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

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

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

Автор: Dmitriy002

Источник [20]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/linux/83985

Ссылки в тексте:

[1] ABCP.RU: http://abcp.ru/

[2] 4MyCar.ru: http://4mycar.ru/

[3] MaxMind: http://www.maxmind.com/

[4] ngx_http_geoip_module: http://nginx.org/ru/docs/http/ngx_http_geoip_module.html

[5] ipgeobase.ru: http://ipgeobase.ru

[6] http://ipgeobase.ru:7020/geo?ip=217.149.183.4: http://ipgeobase.ru:7020/geo?ip=217.149.183.4

[7] ngx_http_geo_module: http://nginx.org/ru/docs/http/ngx_http_geo_module.html

[8] ipgeobase2nginx.php: https://github.com/liveder/ipgeobase2nginx/

[9] ngx_http_ssi_module: http://nginx.org/ru/docs/http/ngx_http_ssi_module.html

[10] ngx_http_realip_module: http://nginx.org/ru/docs/http/ngx_http_realip_module.html

[11] https://yadi.sk/d/QsNN87nMesXo8: https://yadi.sk/d/QsNN87nMesXo8

[12] VDS: https://www.reg.ru/?rlink=reflink-717

[13] http://muxgeo-demo.4mycar.ru:6280/muxgeo/: http://muxgeo-demo.4mycar.ru:6280/muxgeo/

[14] https://yadi.sk/d/1WrvV2RyesYFM: https://yadi.sk/d/1WrvV2RyesYFM

[15] dev.maxmind.com/static/csv/codes/maxmind/region.csv: http://dev.maxmind.com/static/csv/codes/maxmind/region.csv

[16] http://muxgeo-demo.4mycar.ru:6280/muxgeo/?ip=217.149.183.4: http://muxgeo-demo.4mycar.ru:6280/muxgeo/?ip=217.149.183.4

[17] http://muxgeo-demo.4mycar.ru:6280/muxgeo/muxgeo.xml?ip=217.149.183.4: http://muxgeo-demo.4mycar.ru:6280/muxgeo/muxgeo.xml?ip=217.149.183.4

[18] http://muxgeo-demo.4mycar.ru:6280/muxgeo/muxgeo.json?ip=217.149.183.4: http://muxgeo-demo.4mycar.ru:6280/muxgeo/muxgeo.json?ip=217.149.183.4

[19] http://muxgeo-demo.4mycar.ru:6280/muxgeo/muxgeo.ini?ip=217.149.183.4: http://muxgeo-demo.4mycar.ru:6280/muxgeo/muxgeo.ini?ip=217.149.183.4

[20] Источник: http://habrahabr.ru/post/251463/