Служба Каталогов Мобильных Приложений

Руслан Ароматов, главный разработчик ICD

Служба каталогов мобильных приложений

Добрый день, хабровчане! Я работаю backend-разработчиком в Московском кредитном банке, и на этот раз мне бы хотелось рассказать о том, как мы организовали доставку runtime-контента в наше мобильное приложение «МКБ Онлайн».

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

Не помешает это и тем, кто занимается разработкой мобильных приложений.

В статье нет примеров кода, только некоторые обсуждения по теме.



Предварительные условия

Я думаю, что любой разработчик мобильных приложений сталкивался с проблемой обновления какой-то части контента своего приложения.

Например, изменить пункт пользовательского соглашения, иконку или координаты магазина внезапно переехавшего покупателя.

Кажется, что может быть проще? Пересобираем приложение и заливаем в магазин.

Клиенты обновлены, все довольны.

Но эта простая схема не работает по одной простой причине — не все клиенты обновляются.

И, судя по статистике, таких клиентов довольно много.

В случае с банковским приложением неспособность предоставить актуальную информацию может стоить как денег, так и недовольства клиентов.

Например, первого числа следующего месяца меняются тарифы по карте, включаются новые правила бонусной программы или добавляются новые виды получателей платежей.

И если клиент запустит приложение ровно в 0:01 ночи, он должен увидеть обновленный контент. «?Элементарно!» - ты говоришь.

— «Скачайте эти данные с сервера и будет вам счастье».

И вы будете правы.

Это то, что мы делаем.

Всё, мы уходим.

Однако не все так просто.

У нас есть приложения как для iOS, так и для Android. Каждая платформа имеет несколько разных версий, которые имеют разные функциональные возможности и API. В результате может случиться так, что нам нужно обновить файл приложения на Android с версией API выше 27, но не трогать iOS и более ранние версии.

Это становится еще интереснее, когда нам, например, нужно обновить значки получателей платежей или добавить новые элементы с новыми значками.

Мы рисуем каждый экземпляр значка в семи разных разрешениях для каждого конкретного типа экрана: для Android у нас их 4 (hdpi, xhdpi, xxhdpi, xxxhdpi) и 3 для iOS (1x, 2x, 3x).

Какой из них следует отправить в конкретное приложение? «Ну и отправьте параметры файла, которые нужны конкретному приложению».

Верно! Никто, кроме приложения, не знает, какой файл ему нужен.

Однако это еще не все.

Приложения имеют довольно много файлов, которые связаны между собой.

Например, списки получателей платежей (один файл json) связаны с данными получателей платежей (другой файл json).

А если мы получим первый файл и по каким-то причинам не сможем получить второй, то клиенты не смогут оплатить услугу.

И это не очень хорошо, прямо скажем.

Второй случай: мы обновляем весь набор иконок получателей платежей (а их больше сотни) при входе на страницу оплаты.

В зависимости от скорости интернета это может занять от 10 секунд до нескольких минут. Каким должно быть правильное поведение страницы? Например, вы можете просто отображать предыдущую версию иконок, загружать новые в фоновом режиме, затем кэшировать их и показывать новые только при следующем посещении страницы клиентом.

Как-то не очень хорошо, да? Другой вариант — динамическая замена уже загруженных значков новыми.

Не слишком красиво, правда? Что делать, если какая-то иконка вообще не загружается? Затем мы увидим красивый ряд новых иконок с кусочком старого дизайна посередине.



Служба каталогов мобильных приложений

«Тогда весь набор иконок загружайте в один архив при запуске приложения».

Неплохая идея.

Нет, правда.

Но есть нюанс.

Часто бывает, что дизайнер перерисовал всего пару иконок из ста, а заменить нужно только их.

Они весят 200 байт, а весь наш архив — 200 килобайт. В том, что клиенту придется заново выкачивать то, что у него уже есть? И мы еще не посчитали стоимость такой работы на сервере.

Допустим, к нам приходит 10 000 клиентов в час (это среднее значение, может быть и больше).

Запуск приложения инициирует фоновое обновление.

Справочная литература (да, теперь вы знаете, как мы это называем).

Если одному клиенту необходимо обновить 1 килобайт, то сервер за час передаст более 10 мегабайт. Пенни, да? Что делать, если набор обновлений весит 1 мегабайт? в этом случае нам придется отдать 10 гигабайт. В какой-то момент мы приходим к мысли, что нам нужно посчитать трафик.

Тогда вам нужно научиться понимать, какие файлы изменились, а какие нет, и скачивать только нужные.

Верно.

Как определить, какие файлы изменились, а какие нет? Для этого вычисляем хэш.

Таким образом, в приложении появляется некий файловый кэш, который содержит набор файлов каталога.

Эти файлы используются в качестве ресурсов по мере необходимости.

И на стороне сервера у нас в конечном итоге было.



Служба каталогов

В общем, это обычный веб-сервис, доставляющий файлы по http с учетом всех требований приложения.

Он состоит из определенного количества докер-контейнеров, внутри которых запускается Java-приложение с веб-сервером на борту.

Бэкенд — база данных Tarantool на движке винила (тут мучительного выбора не было — просто всё железо для этой базы уже было; об этом можно почитать в моей предыдущей статье).

Сервис интеллектуального кэширования на базе ZeroMQ и Tarantool ) с репликацией «главный-подчиненный».

Для управления файлами имеется веб-интерфейс сервиса, также полностью написанный вами.



Служба каталогов мобильных приложений

Детали технической реализации не особо важны в теме данной статьи.

Это может быть php+apache+mysql, C#+IIS+MSSQL или любая другая комбинация, в том числе вообще без базы данных.

На схеме ниже показано, как работает сервис, который мы называем Woodside. Мобильные клиенты проходят через балансировщик к экземплярам веб-сервиса, а те, в свою очередь, извлекают необходимые файлы из базы данных.



Служба каталогов мобильных приложений

Но в этой статье я расскажу только о структуре системы каталогов и о том, как мы их используем в приложениях.

Мы делим файлы, необходимые в приложениях, на 3 разных типа.

Файлы, которые должны быть всегда в приложении и не зависят от типа операционной системы.

Например, это pdf-файл с договором на банковское обслуживание.

Файлы ресурсов также необходимы в приложении, но зависят от операционной системы и параметров экрана (плотности пикселей) устройства.

Например, иконки получателей платежей.

Файлы, которым не обязательно постоянно находиться в файловом кэше, запрашиваются приложением по требованию.

Это могут быть какие-то документы, которые клиент никогда не сможет открыть, или тяжелые картинки партнерской программы, в которую клиент никогда не сможет войти.

Такие файлы, в зависимости от выбранной политики, можно удалять из кэша после выхода из приложения, чтобы не занимать место.



Служба каталогов мобильных приложений

В сборку приложения сразу добавляются первые 2 типа файлов в виде архивов - последний релиз по умолчанию включает новейший набор справочников.

Они также попадают в систему автоматического обновления, которая работает в фоновом режиме при запуске приложения и работает следующим образом.

1. Служба каталогов автоматически получает часть данных из разных мест: баз данных, сопутствующих сервисов, сетевых ресурсов – это некая важная общая банковская информация, которая обновляется другими подразделениями.

Другая часть — справочники, созданные внутри нашей команды через веб-интерфейс и содержащие файлы, предназначенные только для мобильных приложений.

2. По расписанию (или кнопке) сервис перебирает все файлы всех каталогов и на их основе формирует набор индексных файлов (внутри json) как для файлов первого типа (2 версии для iOS и Android), так и для файлов ресурсов второго типа (по 7 версий для каждого типа экрана).

Это выглядит примерно так:

   

{ "version": "43", "date": "04 Apr 2020 12:31:59", "os": "android", "screen": "any", "hashType": "md5", "ts": 1585992719, "files": [ { "id": "WBRbDUlWhhhj", "name": "action-in-rhythm-of-life.json", "dir": "actions", "ts": 1544607853, "hash": "68c589c4fa8a44ded4d897c3d8b24e5c" }, { "id": "o3K4mmPOOnxu", "name": "banks.json", "dir": "banks", "ts": 1583524710, "hash": "c136d7be420b31f65627f4200c646e0b" } ] }

Индексы содержат информацию обо всех файлах заданного типа, на основе которой строится механизм обновления каталогов в приложениях.

3. При запуске приложений первое, что они делают, это загружают индексные файлы в свою директорию.

/новый внутри его файлового кэша.

И в каталоге /текущий у них есть индексы для текущего набора файлов вместе с самими файлами.

4. На основе новых и старых индексных файлов (включая все текущие файлы, от которых рассчитывается хеш) создаются списки файлов, которые необходимо обновить или удалить, и в целом устанавливается необходимость обновления.

5. После этого в каталог /новый приложения скачивают необходимые файлы с сервера по прямой ссылке (за это отвечает идентификатор файла в индексе).

При этом также учитывается наличие и хеши файлов, уже находящихся в каталоге.

/новый , потому что это может быть пополнение запасов.

6. Как только весь набор файлов получен в каталоге /новый , они проверяются по индексному файлу (иногда случалось, что файлы загружались не полностью).

7. Если проверка прошла успешно, все дерево файлов перемещается и заменяется в директорию /текущий .

Свежий индексный файл становится текущим.

8. Если проверка не пройдена, файлы перемещаться не будут, а приложение продолжит использовать текущий набор каталогов.

При следующем запуске приложения механизм обновления попытается это исправить.

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

Пока прецедентов не было.

Но почему это так сложно? На самом деле это не очень сложно.

Но дело в том, что нам постоянно приходится экспериментировать и искать компромиссы между количеством постоянно обновляемых файлов и загрузкой во время выполнения, между экономией трафика и скоростью.

Большую роль в выборе типа файла играет то, когда именно он нужен в приложении.

Например, если иконка должна отображаться сразу на главной странице после входа в систему, то приложение может загрузить такой файл во время выполнения сразу, а не помещать его в длительный механизм обновления.

Теперь общий размер архива только с основными файлами составляет 12 мегабайт, не считая экранно-зависимых ресурсов.

А поскольку наше обновление по сути является атомарной операцией, нам нужно дождаться его завершения.

Это может занять до нескольких минут в случаях, когда соединение плохое и имеется много новых файлов.

Важный момент – экономия трафика.

Были случаи, когда мы полностью избавлялись от 100-мегабитного канала после тяжелых обновлений.

Пришлось расширить до 300. Пока хватит. В среднем метрики показывают, что клиенты обычно загружают от 25 до 50 гигабайт в час в течение дня (это потому, что у нас довольно большие файлы, которые обновляются ежедневно).

В плане экономии еще есть куда расти, но бизнес тоже не дремлет – все время добавляются разные новые красоты.

В заключение могу добавить, что сервисом пользуются и сами фронт-серверы, которые при запуске загружают данные, необходимые для обработки клиентских запросов.

Как вы доставляете обновления контента в приложения? Теги: #iOS #Android #разработка iOS #разработка Android #docker #Microservices #java #разработка мобильных устройств #web #http

Вместе с данным постом часто просматривают: