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

Наша компанія займається створенням Інтернет-магазинів запчастин на власній SaaS-платформі (ABCP.UK), а також у нас є декілька пов'язаних проектів, наприклад, сервіс пошуку запчастин 4MyCar.uk.
Як і багато інших веб-проекти, ми в свій час прийшли до розуміння необхідності геолокації по IP-адресою. Наприклад, зараз вона використовується на 4MyCar.uk для визначення регіону (при першому вході на сайт регіон автоматично встановлюється саме так).



Аналогічно проводиться вибір найближчого до клієнта філії магазину на сайтах клієнтів платформи ABCP.



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

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

Через деякий час один з наших співробітників виявив відмінний сайт ipgeobase.uk, що дозволяє «завантажити» бази геолокації для Росії і України, а також скористатися його XML веб-сервісом через простий http-запит. Наприклад, перехід на сайт 4mycar.ru по фразі «масляний фільтр купити в урюпінську» з відповідного міста викликав в результаті приблизно такий запит до веб-сервісу http://ipgeobase.ua: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) для UA RU і ми довіряємо 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.ua:6280/muxgeo/.

Щоб швидко запустити такий сервіс у себе, ви можете завантажити готовий образ LXC — https://yadi.sk/d/1WrvV2RyesYFM.

Наведемо короткий опис роботи скриптів, в 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.ua:6280/muxgeo/?ip=217.149.183.4
транслюється на backend з підміною ip адреси на 217.149.183.4, а backend вставить інформацію в потрібні місця html тексту.

Але html-сторінка — це трохи не те, що нам хотілося, потрібен xml, як у ipgeobase. Просто заповнюємо шаблон висновком відповідних полів, дивіться приклад у файлі muxgeo.xml

По посиланню
http://muxgeo-demo.4mycar.ua:6280/muxgeo/muxgeo.xml?ip=217.149.183.4
отримаємо «такий же, але краще», ніж у ipgeobase висновок xml, та ще й в utf-8

Треба JSON — без проблем. За аналогією шаблон, і готово:
http://muxgeo-demo.4mycar.ua:6280/muxgeo/muxgeo.json?ip=217.149.183.4

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

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

Невелике зауваження. У прикладах frontend і backend працюють на одному nginx. У разі великого навантаження має сенс рознести їх на різні nginx, так як воркер для backend з геоданными споживає більше пам'яті, ніж мінімальний nginx з proxy_cache.

Як подальший розвиток цього проекту? Можна, наприклад, додати інші джерела даних, зовсім німого ускладнивши конфігурацію, а також підключити свої гео-бази, в які помістити «уточнення, отримані з достовірних джерел :) ».

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.