Эволюционный путь к гибридной архитектуре

Node.js внешний интерфейс

Get APP является продуктом более трех лет. Первоначально он был разработан чисто нативным способом. В начале 2018 года мы начали исследование и практику технических решений для гибридной разработки. В настоящее время Get APP содержит два набора гибридных решений. , ReactNative и веб-просмотр. С точки зрения времени, эта статья посвящена обзору процесса гибридного решения Webview, превращающего APP с 0 в 1. Мы также надеемся, что наш опыт может вдохновить некоторые команды, которые хотят внедрить гибридное решение.

Author: jiangqiang

1. Предыстория и мотивация

Get — это продукт с тяжелыми сценариями работы, и большинство функций в приложении будут иметь функцию обмена. В начале 2018 года для разработки функции в основном требовалось три человека на трех концах. Некоторые предприятия используют встроенный Webview и браузероподобные решения. Таким образом, первоначальная цель состояла в том, чтобы иметь набор кроссплатформенных решений, набор кода, который можно было бы выполнять на трех концах и иметь лучший опыт.Это диаграмма архитектуры Hybrid в то время:

Hybrid 架构图 v1

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

На этапе исследования RN мы обнаружили, что, хотя RN поддерживает трехтерминальное и динамическое обновление, для реализации его возможностей динамического обновления требуется поддерживающая инфраструктура, поэтому нам нужна автономная система управления ресурсами, которая может динамически обновлять файл RN внутри клиента. , и когда мы обдумывали и проектировали эту автономную систему управления ресурсами, мы обнаружили, что та же идея может быть применена к Webview. Мы можем преобразовать интерфейсный код в автономный пакет и обновлять его через автономную систему управления ресурсами. процесс запуска Weview, требуется только доступ к API данных без загрузки HTML/JS/CSS и т. д., также можно рассматривать как замаскированное расширение возможностей в автономном режиме.

Поэтому мы разработали первоначальную дорожную карту:

  1. Сначала разработайте автономную систему управления ресурсами;
  2. После завершения получите доступ к автономному веб-пакету, поскольку стоимость разработки автономного веб-пакета низкая, он может быстро улучшить работу существующих проектов и быстро получить прибыль;
  3. Наконец, осуществляется разработка и доступ к РН;

2. Автономная система управления пакетами ресурсов - Seeder

Работа над технологическим проектом похожа на работу над продуктом: необходимо разобраться в требованиях, сценариях использования и т. д., а затем подумать о технической архитектуре и деталях реализации. Сначала мы придумали название для проекта Seeder. (Почему название на самом деле бессмысленно, в основном потому, что внутри нет другой системы под названием Seeder...)

2.1 Цели

Разобравшись, мы считаем, что Seeder необходимо достичь следующих целей:

  1. Ресурсы могут динамически обновляться;
  2. Может поддерживать не последнюю версию клиента для обновления;
  3. Поддержка инкрементного обновления;
  4. Поддержка многоканальной публикации;

2.2. Выбор технологии и архитектуры

После уточнения цели нам нужно заняться техническим отбором и архитектурой.Для технического отбора мы используем знакомую команде комбинацию Nodejs+Mongodb.Схема архитектуры выглядит следующим образом:

image-20200115161749742

Сервер состоит из двух частей: Seeder и CDN, часть CDN в основном используется для загрузки пакетов ресурсов. Seeder разделен на службу Updater и службу Manager:

  1. Служба обновления: в основном для получения и обработки запросов на обновление от клиентов;
  2. Служба диспетчера: в основном управляет пакетами ресурсов и связанными с ними конфигурациями, включая создание пакетов различий и т. д.;

При разумном разделении служба обновления может стабильно выполнять 6000 запросов в секунду на двух машинах 8C16G в нашем последующем стресс-тесте;

2.3 Ключевые моменты реализации — определение пакета

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

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

Что касается файловой структуры, файл в формате info.json добавляется в корневой каталог в исходной структуре каталогов ресурсов для описания информации о пакете.

image-20200114171319102

Давайте посмотрим на структуру следующего package.json:

image-20200115105152693

  1. appId: идентификатор приложения, который идентифицирует пакет;

  2. версия: указывает версию этого пакета;

  3. depend.containerVersion: указывает версию контейнера, от которого зависит этот пакет, текущий контейнер имеет только клиент;

  4. файлы: массив, который записывает все файлы и пути и их MD5;

  5. мета: расширенное информационное поле, здесь используются два расширенных поля, и эти два поля будут подробно описаны позже

    1. тип: тип пакета
    2. маршруты: список маршрутов, которые пакет должен зарегистрировать

2.4. Ключевые моменты реализации — добавочные обновления

Инкрементное обновление означает, что нам нужно только загрузить пакет исправлений.После установки пакета исправлений обновление приложения может быть завершено.Программное обеспечение, такое как наш часто используемый VSCode и большинство мобильных игр, поддерживает добавочное обновление. Ключевым моментом для достижения добавочного обновления является добавочный алгоритм, В результате исследований мы, наконец, выбрали поддержку алгоритма двоичного сравнения.bsdiff.

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

  1. Основанный на архивированном сжатом tgz-пакете для diff и patch, преимущество этого решения в том, что стоимость внедрения низкая, но проблема в том, что клиент должен хранить копию нижнего пакета, а так как пакет загружается на клиенте стороны, это может быть выполнено после распаковки.Это решение не может постоянно обновляться (не может постепенно обновляться с v1.0->v1.1->v1.2, только v1.0->v1.1, v1.0-> v1.2);
  2. Основанный на однофайловом diff, то есть добавочный пакет фактически содержит несколько файлов исправлений, включая файлы, описывающие информацию об изменении пакета.Хотя это решение сложнее реализовать, оно не имеет различных проблем решения 1. Поэтому , мы приняли однофайловую схему сравнения файлов;

Давайте взглянем на схему сравнения одного файла, сначала взглянем на структуру инкрементного пакета:

image-20200115113015205

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

image-20200115113150991

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

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

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

  1. Когда приходит запрос на создание инкрементного пакета, преимущество состоит в том, что инкрементный пакет должен быть.Проблема в том, что генерация инкрементного пакета является операцией с интенсивным использованием ЦП и не может поддерживать высокий параллелизм;
  2. Добавочные пакеты генерируются заранее, но заранее может быть создано только указанное количество добавочных пакетов, но могут быть более старые версии без доступных добавочных пакетов;

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

2.5 Изменения архитектуры

Взгляните на наши скорректированные архитектурные изменения:

image-20200116173706077

3. Платформа приложения — Адам

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

  1. Каждое веб-представление может иметь только одну страницу, и сложные функции не могут быть реализованы (чтобы поддерживать согласованное взаимодействие страницы с клиентом, каждое веб-представление имеет только одну страницу, чтобы производительность панелей вперед, назад и навигации была согласованной). ;
  2. Невозможно управлять панелью навигации, некоторые функции, требующие настройки панели навигации, зависят от клиента;
  3. Без систематической структуры невозможно единообразно обрабатывать исключения, кэши и т. д.;

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

3.1 Цели и декомпозиция

Стек технологий, с которым вся наша команда больше всего знакома, — это Vue, поэтому Адам должен быть инкапсулирован на основе Vue.Перед проектированием Адама нам нужно подтвердить цель:

  1. Функционально: Пакет может использоваться как законченное Приложение, которое может полностью реализовать функциональный модуль, включая многостраничные функции и т.д.;
  2. Технически: реализовать стандартизированное решение, а фреймворк обрабатывает общую логику, такую ​​как кэширование и страницы исключений;

Разбейте цели дальше:

  1. Клиент должен передать весь интерфейс Webview для обработки;
  2. Требуется маршрутизатор, который, как и клиент, поддерживает маршрутизацию страниц управления стеком;
  3. Страница должна обеспечивать одинаковые динамические эффекты в прямом и обратном направлении на стороне клиента, а также должна поддерживать переход на предыдущую страницу;
  4. Необходимо абстрагировать кеш и страницы исключений и дождаться уровня фреймворка;

image-20200115160956220

3.2 Схема архитектуры

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

image-20200115181521536

Каждый веб-пакет — это приложение, и каждый экземпляр приложения соответствует экземпляру Global Store и vue-stack-router, соответствующим нескольким экземплярам страницы.

Каждая страница состоит из компонента страницы и хранилища страниц. Жизненный цикл хранилища страниц соответствует странице.

3.3 Ключевые точки реализации — маршрутизатор

Для первоначального решения Router мы выбрали наш часто используемый vue-router, но в процессе реализации мы столкнулись со следующими проблемами:

  1. Реализация стековой маршрутизации более сложна. Большинство страниц в клиенте имеют характеристики стека, и выживание экземпляра страницы зависит от того, находится ли он в стеке. В vue-router выживание экземпляров компонента зависит от того, используется ли компонент kee-alive;
  2. Трудно реализовать скользящий возврат между двумя страницами, как в родном;
  3. Страницы с несколькими экземплярами не могут быть реализованы. В Native, когда страница A переходит на страницу A, будет создан новый экземпляр страницы A. В vue-router переход со страницы A на страницу A приведет к повторному рендерингу существующей страницы A, то есть страница A всегда является одноэлементной;

Для решения этих проблем мы разработалиvue-stack-router(Это был открытый исходный код, конкретные детали реализации, если вам интересно, вы можете напрямую увидеть код github, там больше контента, он не будет здесь расширяться), по сравнению с vue-router, он имеет следующие новые функции :

  1. Управление стекированной маршрутизацией;
  2. передача данных между маршрутами;
  3. Поддержка более тонких, настраиваемых эффектов перехода маршрутизации;
  4. Поддержка предварительного рендеринга;

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

3.4. Ключевые моменты реализации — магазин

Когда речь заходит об инструментах управления состоянием, все согласны с тем, что в простых проектах нет необходимости использовать Магазин, а сложные проекты могут отражать ценность Магазина.На самом деле внедрение Магазина требует затрат. Проанализируем характеристики мобильных страниц:

  1. шоу-ориентированный
  2. Низкая связанность между страницами
  3. простой поток данных

Поэтому на мобильной странице польза от отслеживания изменений состояния может быть не очень велика. Если убрать отслеживание состояния, Магазин может стать очень оптимизированным. Давайте посмотрим на наш собственный оптимизированный Магазин. Принцип следующий:

class MyStore {
  public name: string = '';
  public updateName(name: string): void {
    this.name = name;
  }
}
const store = Vue.observable(new MyStore());

Отслеживания состояния нет, только максимально налаженное извлечение состояния в класс для управления.

После разговора о реализации Магазина давайте взглянем на организационную форму Магазина, Мы часто используем Vuex и Redux как единое дерево компонентов, и даже у MobX есть решение сообщества mobx-state-tree, один компонент дерево. Однако в сочетании с характеристиками мобильных сервисов будут некоторые проблемы с одним деревом компонентов, а реализация поддержки нескольких экземпляров страниц будет более сложной. Опять же, организация хорошего однокомпонентного дерева обычно отделена от страницы, разработана отдельно, а потому несет дополнительную умственную нагрузку.

Основываясь на вышеупомянутой взаимоблокировке, в конце концов мы не использовали одно дерево компонентов, а реализовали решение Магазина с несколькими состояниями: страница соответствует Магазину, а магазин и жизненный цикл страницы согласованы. Логика отделена от изложения, страницы не связаны, главное простота;

3.5 Кэш

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

image-20200116171904997

Возьмите его из данных маршрутизации, памяти и LocalStorage по очереди. Что такое данные маршрутизации? Обычно в клиенте многие переходы страниц представляют собой переход сводной информации на страницу подробной информации (например, переход элемента на страницу сведений списка). Фактически, предыдущая страница уже содержит часть информация о последующих страницах.В это время данные предыдущей страницы могут быть перенесены на следующую страницу, а последняя страница может отображать основную информацию и улучшать взаимодействие с пользователем.

Так откуда берутся кешированные данные, это не требует от разработчиков их ручной записи. Мы знаем, что View=fn(State), в схеме Store мы поместили состояние страницы в хранилище, и нам нужно только кэшировать Store. Что касается времени кэширования и восстановления, то при уничтожении страницы мы сериализуем Магазин, ждем открытия страницы и восстанавливаем Магазин.

4. Стандартные контейнеры

При разработке Адама тоже постоянные отзывы одноклассников.Сейчас более хлопотно получить доступ к новому веб-гибридному бизнесу.Требуется клиент для настройки webview,а новый бизнес зависит от релиза.Можем ли мы не полагаться на клиента в все?

Ответ положительный.

4.1 Протоколы маршрутизации

Мы добавили тип пакета и информацию о глобальной маршрутизации пакета в Package, чтобы клиент мог динамически прописывать маршрут при обновлении информации о пакете, то есть все маршруты в Package привязаны к стандартизированному webview. После запуска веб-просмотра соответствующий пакет загружается в соответствии с пропущенным маршрутом, и реализованы функции динамической загрузки и регистрации.

4.2 Окончательная архитектура

После завершения Адама и стандартизации контейнеров давайте взглянем на окончательную архитектуру:

image-20200116173601861

Пока что мы можем обновлять и повторять каждый пакет как независимое приложение.

5. Резюме и размышления

5.1 Результаты

Что касается функций, у нас есть доступ к таким модулям, как лекции, электронные книги, оценки, учебные лагеря, получение университетов, системы деятельности и справочные центры, а также доступ к более чем 90 страницам (30+ для ReactNative и 60+ для Интернета). ;

image-20200116174052095

С точки зрения эффективности, мы поддержали 49 функциональных пробных тестов и обновили 1900 раз в течение полутора лет. В тестовой среде он динамически обновлялся 13 000 раз;

По производительности мы нашли две неиспользуемые и использующие Seeder функции из системы мониторинга производительности для сравнения (данное сравнение не является строгим, т.к. нет данных о том, что одна и та же функция принимала последовательно две схемы, мы нашли две функции с похожими функциями, код сравнивает два проекта с одинаковыми количествами).

Общее решение для веб-просмотра

image-20200115115722718

Схема Адам+Сидер:

image-20200115115740709

В принципе, видно, что стабильность и эффективность были лучше улучшены.

5.2 Мышление

В процессе Гибридной посадки мы наступили на множество ям и получили много товаров.Расскажем коротко о двух моментах.

Во-первых, как оценить качество технического решения? У нас слишком много критериев: с точки зрения бизнеса, может ли это удовлетворить потребности и недорогие потенциальные последующие потребности; с точки зрения эксплуатации и обслуживания, приведет ли это к новым затратам на развертывание и эксплуатацию. С технической точки зрения мы можем даже взять шаблон проектирования и поговорить о нем. Однако мы редко обращаем внимание на пользовательский опыт технических решений, здесь под пользователями понимаются студенты-разработчики, которые используют ваш фреймворк и библиотеку. С точки зрения студентов, изучающих бизнес-развитие, вы обнаружите, что предоставленное решение действительно решает проблему, но в процессе использования этого решения может быть 30% работы, которая не является частью решения, но необходима для часть решения.Например, вход решения - A, разработчику нужно потратить много усилий, чтобы получить A, чтобы использовать эту схему. Поэтому, как разработчик фреймворка и библиотеки, вы должны учитывать сценарии использования всего решения, может ли техническая часть покрыть весь сценарий, как решить, если не может, нужно ли предоставлять средства автоматизации и т.д. на.

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

5.3. Последующее планирование

План последующих действий в основном состоит из двух аспектов:

  1. Поддержка нескольких сред и нескольких концов Адама охватывает сценарии, в которых достигается «конец» бизнеса;
  2. Seeder более гибкие сценарии обновления, такие как поддержка отложенной загрузки и т. д.;