Для тех, кто пропустил первую часть статьи: вам здесь .
Ну а всем остальным как обычно привет, Хабрахабр.
Продолжаем тему PWA и изучение базового алгоритма синхронизации (не стоит ли бросать начатое?).
В прошлой части мы закончили тем, что наше условное приложение может запрашивать статьи с сервера, получать только актуальные материалы, отслеживать изменения и удаления статей и все это грамотно обрабатывать.
Все работало через вычисление дельты: разницы между тем, что есть в приложении, и тем, что хранится на сервере.
В этой части мы изучим различные конкретные схемы реализации описанных выше теорий, обсудим их сильные и слабые стороны.
Ну, прежде чем начать, опишем требования к необходимым алгоритмам.
У нас есть два объекта с одинаковым ID, и нам нужно уметь различать их: определять, какой из них новее.
То есть определить, какой из объектов является актуальным.
Если нас не два, а три, пять или десять, по сути ничего не меняется, нам просто нужно отсортировать их по степени релевантности и иметь возможность выбрать самый свежий, свежий, актуальный объект. На самом деле нам нужна система контроля версий.
Увеличить счетчик
Первая и самая очевидная идея — сделать банальный целочисленный счетчик.«Версия 1», «Версия 2»,… «Версия 100500» — ну вы поняли.
Главный плюс этой штуки в том, что здесь все понятно не только компьютеру, но и человеку.
То есть если покопаться в базе, то сразу поймешь, какая статья актуальна, а какая нет. Так в чем проблема? Представьте, что нашу статью могут редактировать несколько человек.
Один находится на стороне сервера, другой — на стороне клиента.
Первый выпуск статьи вышел под индексом «Версия 1».
Далее сервер создает редактируемый документ с индексом «Версия 2», а клиент. и на клиенте тоже создается «Версия 2», потому что за «1» следует «2», а клиент этого не сделал.
знать, что было на сервере: допустим, в данный момент не было доступного соединения.
Теперь у нас есть два документа с индексом «Версия 2», но мы не знаем, одинаковые они или нет. Но для клиента (да и для сервера тоже) обе версии кажутся легитимными — счетчик версий один и тот же, ничего синхронизировать не нужно.
Значит, наши версии нужно описать более подробным языком?
Временные метки
Чтобы различать версии документа с одинаковым идентификатором, мы можем записывать не только счетчик, но и время сохранения статьи.Будь то 11 октября 2016, 12:30:57 или просто 111016123057, нам важно, чтобы число было уникальным и выражало конкретное состояние статьи.
Мы не будем вдаваться в подробности ПРАВИЛЬНОГО описания временных меток; одни виды удобнее обрабатывать на ПК, другие удобнее пользователю.
Хотите сделать по-своему — делайте, просто рекомендуем использовать стандартный ИСО 8601 .
Что мы имеем в итоге? У нас одна и та же статья с разными временными метками, поэтому легко понять, какая версия новее.
Поскольку наши часы идут только вперед, каждая последующая версия будет иметь метку времени точно большую, чем предыдущая.
Или нет? Прежде чем продолжить чтение статьи, взгляните на этот замечательный сбор граблей , на который наступают программисты, когда думают, что знают, как работает время.
У него также есть есть вторая часть .
Тривиальный пример.
Вы встретили в баре очаровательную девушку, запишите ее номер в свой телефон.
Вы выпили достаточно, чтобы перепутать два числа, и хорошо, что вы решили перепроверить.
Вы редактируете номер, сохраняете правильную версию и прощаетесь.
Наши контакты имеют метки времени, синхронизация с облаком прошла успешно, поэтому даже если вы потеряете телефон по дороге домой, он останется.
Или нет? Если вы уже открывали «грабли» по ссылкам выше, вы поймете, как много может случиться с вашим блокнотом при сохранении второго номера.
Даже такие гиганты, как Google, с почти бесконечными ресурсами, иногда.
проблемы связанные со временем.
Во время обновления вашего блокнота может случиться что угодно.
Вы синхронизировали первую версию с опечаткой, а потом бац! — телефон синхронизировался с сервером и перевел часы на минуту назад. Или вы попали ровно в полночь, включился режим летнего времени.
У iPhone до сих пор возникают проблемы с будильниками, установленными на 1 января.
В общем, мы не можем настолько доверять простым меткам времени, насколько хотелось бы - в нашем случае вы потеряете номер телефона девушки, потому что утром даже не вспомните, что это была за опечатка и где.
В общем, это нам это не нужно.
Но время также может быть не синхронизировано на клиенте и сервере.
Подведем итог нашим размышлениям: каким бы ни был конкретный сценарий, проблема может возникнуть как в небольшом проекте, так и в большой системе.
Вы не можете доверять простым меткам времени, чтобы гарантировать согласованность версий двух объектов, даже если обе метки времени были созданы на одном и том же устройстве: такое поведение может привести к потере данных.
Это означает, что нам нужно улучшить временные метки.
Но перед этим нам необходимо рассмотреть еще один источник проблем: конфликты.
Разрешение конфликтов
Чтобы проиллюстрировать конфликты, нам нужно немного расширить наш пример.Представьте, что у нас есть не просто клуб читателей интересных статей, а клуб читателей и писателей.
Как Хабрахабр.
У нас уже что-то есть, и это работает: у нас есть устаревший код. Сервер может запрашивать авторов, а их приложения могут отправлять новые и/или измененные статьи, а пользователи могут аналогичным образом запрашивать у пользователя новые материалы и экономить трафик, поскольку сервер будет отправлять им только то, что им нужно.
Также у нас есть условные редакторы-модераторы и администраторы, которые могут поправить что угодно в любой статье.
А теперь случай: редактор прочитал статью, нашел опечатку, исправил ее и отправил изменение на сервер.
Примерно в это же время автор добавил обновление к статье, где сообщил, что обновил репозиторий примерами кода, поблагодарил внимательных читателей за комментарии и указанные ошибки, а также отправил на сервер.
Что произойдет, если оба этих события произойдут с разницей в несколько секунд? Даже если мы на 100% знаем, какая из правок пришла позже, а какая раньше (перед отправкой синхронизировали часы с сервером, проверили все мыслимые и немыслимые проблемы), у нас будет либо статья с опечаткой, либо статья с ошибками в тексте.
код.
На самом деле это конфликт. В данном случае возникает конфликт между версиями статьи.
Что делать?
Не знаю, как вы, а я не люблю конфликты.А в реальной жизни я стараюсь их избегать.
Правда, если в жизни так часто можно делать, то в IT постоянно приходится сталкиваться с конфликтами.
А конфликты – это очень и очень плохо.
А еще у нас есть такое понятие, как распределенные системы.
У этой фразы много разных толкований, но в нашем случае все сводится к следующему: у вас есть два или более компьютеров, соединенных какой-то сетью, и вы пытаетесь сделать так, чтобы некий набор данных выглядел одинаково на всех компьютерах.
А теперь самое приятное — любой элемент сети может выйти из строя в любой момент. Сценарии отказов много , а предусмотреть ВСЕ сложно.
И даже при базовом сценарии, где есть два устройства и один сервер, у нас уже есть распределенная система, которую нужно заставить работать.
Вот что мы сделаем.
Давайте начнем с малого, и хорошо построенное решение можно без проблем масштабировать.
Итак, если для редактирования документа на одном компьютере, как мы уже выяснили, конфликты — это очень плохо, то для распределенных компьютеров конфликты — естественное положение вещей.
Это не значит, что мы можем игнорировать их или делать вид, что их не происходит, это означает, что мы должны принять конфликты и сделать их частью механики.
Мы уже обсуждали, как возникают конфликты (и даже создавали их на примере), давайте посмотрим, как их разрешить.
Допустим, в нашем предыдущем примере во втором абзаце была опечатка, а в конце статьи было добавлено дополнение к материалу:
Например, если статья хранится в виде абзацев, то мы могли бы просто обновить две правки в разных абзацах, рассматривая эти изменения как два отдельных объекта.
Программисты называют это слиянием (или слиянием по-русски).
Это популярный и очень удобный механизм для таких вещей, как git. В этом случае компьютер без конфликтов собирает окончательную версию статьи самостоятельно.
Давайте усложним задачу.
Допустим, автор и редактор одновременно заметили опечатку и исправили ее разными способами.
Автор решил подобрать синоним и заменил слово, а редактор лишь исправил один-два символа в существующем.
И оба почти одновременно отправили изменения на сервер.
Теперь компьютер не сможет определить, какая версия правильная.
Что делать? Как вы уже заметили, мы разбиваем одну глобальную проблему на все более мелкие части и продолжаем все глубже разбираться в проблемах синхронизации.
Была статья – теперь есть абзацы.
Абзацы можно разделить на строки.
Строки – в слова.
Рано или поздно мы достигнем разумного предела разделения на сущности, а конфликты у нас все равно будут. Более того, у нас останутся именно те конфликты, которые компьютер не сможет разрешить самостоятельно: предыдущий пример — яркое тому подтверждение.
К таким проблемам нам просто придется привлечь человека: ИИ еще не изобрели, поэтому другого пути на данный момент нет. В конце концов, цель эволюции заключалась в том, чтобы научить людей выживать и разрешать конфликты, так что, я думаю, теперь вы не будете так бояться конфликтов в.
компьютерном смысле.
Ах да, пришло время вернуться к временным меткам и улучшить их.
Векторные часы
Как только вы начнете искать решение проблем с метками времени, вы встретите упоминание векторные часы , например, в виде Временные метки Лампорта .Применяя векторные часы к временным меткам, мы получаем то, что называется логические часы .
Если мы будем использовать с нашими объектами не только временные метки, но и логические часы, то мы сможем точно рассчитать, какие из правок произошли раньше, а какие позже.
Великолепный! Осталось еще кое-что выяснить.
Помните, мы говорили о случае, когда редактор одновременно исправлял опечатку и добавлял материал автора? Мы исправили этот конфликт с системой выше: логические часы + метки времени.
Что, если их правки относятся к одному и тому же фрагменту текста или, что еще забавнее, результатом их правок является один и тот же фрагмент текста? Никакие уловки здесь не помогут, конфликт останется.
То есть, на первый взгляд, конфликта нет, сравните результаты, откиньте лишние и все, дела как обычно.
А что, если мы пойдем еще дальше и не будем создавать конфликты вместо того, чтобы решать их последствия? Сейчас эта проблема выглядит несколько надуманной, но, эй.
Это пример, чтобы потом, когда вы действительно столкнетесь с такой проблемой, и работать не с одним сервером синхронизации, а с целым кластером серверов, причем с тысячи и тысячи клиентов, у вас не возникло проблем ни с созданием работающего решения, ни с его масштабированием в будущем.
Позвольте мне пропустить утомительные вещи и перейти к делу, иначе наш урок и так слишком длинный.
По сути, существует такая вещь, как адресуемые версии контента.
Адресуемые версии контента
Для разрешения конфликтов с одинаковыми правками воспользуемся этим хаком.Берем содержимое редактирования/объекта, прогоняем его через хэш-функцию (через тот же MD5 или SHA-1) и используем хеш как версию.
Поскольку идентичные исходные данные создают идентичные хэши, изменения, дающие одинаковый результат, не создают конфликта.
Правда, это не решает проблему с порядком правок, но мы сейчас это исправим.
Для начала вместо перезаписи документа при каждом обновлении мы будем хранить упорядоченный список версий вместе с документом.
Когда мы обновляем часть документа, версия с адресацией по содержимому записывается вместе с изменениями в верхней части листа.
Теперь мы точно знаем, когда и какая версия правок была получена.
В целом вещь полезная, но стоит заранее определиться, сколько правок и версий документа мы будем хранить или передавать на сервер.
Помните, почему все это затеялось? Чтобы оптимизировать работу приложения, уменьшите объемы трафика, потребление памяти и так далее.
Так что в реальной разработке подумайте, сколько правок вам нужно «нести с собой»: вообще подойдет цифра от 10 до 1000, а поможет только личный опыт и знание задач, целевой аудитории и того, как используется ваше приложение.
Помогите выбрать правильный параметр.
В результате мы тратим немного больше места на хранение истории правок, но имеем гибкую систему, позволяющую избежать некоторых конфликтов.
Вам может показаться, что проще использовать те же Vector Clock и разрешать конфликты по мере их возникновения, чем строить такую сложную вещь и тратить место на хранение «ненужных» данных, но поверьте, все изменится, как только у вас появится кластер вместо одного сервера, независимые серверы обработки запросов, а также необходимость предоставления согласованных данных для всех пользователей.
Эффективное управление конфликтами
Наконец, нам предстоит решить последнюю проблему.
Когда один пользователь последовательно вносил правки в один и тот же материал, история версий выглядит примерно так (для простоты вместо хешей мы используем простые числа):
У нас есть пять версий.
Теперь искусственно создадим конфликт: в списке версий после 4 у нас будет конфликт между 5-й и 6-й версиями:
Мы знаем, что эти версии противоречат друг другу.
Что, если вместо разрешения этого конфликта мы создадим другой? Давайте просто напишем еще пару значений на листе выше:
Теперь у нас конфликт между версиями "6" и "7", причем сам этот конфликт конфликтует с версией "5".
Кто-то упрлс? Нет. Фактически наша история изменений превращается в дерево версий.
Он имеет ряд преимуществ:
- Он эффективно сохраняет конфликты;
- Мы можем разрешать конфликты рекурсивно: сколько бы у нас ни было конфликтующих правок и ветвей, мы всегда можем вернуться в состояние, в котором конфликтов не было.
Собираем все это вместе
Итак, подведем итоги.Вместе мы рассмотрели и прояснили многие (иногда, казалось бы, очевидные) аспекты синхронизации.
Теперь посмотрим, что получится, если взять все наработки и объединить их.
Ниже приведены ключевые моменты описанного выше алгоритма, без пояснений, зачем это все делается, поскольку эти вопросы мы уже обсуждали: 1. Имеется два устройства – А и Б; необходимо синхронизировать информацию на них (от А до Б).
2. Прочтите контрольную точку синхронизации, если она существует. 3. Начинаем считывать обновленную информацию с устройства А; читаем с чекпоинта, если он есть, или с самого начала, если его не было.
- Мы рассматриваем каждую пару вида Id/версия;
- Если это удаление, запомните удаление локально на устройстве B;
- Если такой пары на B не было, мы читаем ее из A и сохраняем локально на B;
- Сохраните контрольную точку обновления на A и B.
- Если да, сохраните его на устройстве B;
- Если нет, мы создаем конфликт.
Вот и все
Спасибо, что дошли до конца.Надеюсь, вы понимаете, насколько сложно правильно и с первого раза разработать работающую и эффективную синхронизацию, которая будет надежной и не превратится для вас в одну большую боль при масштабировании приложения.
Что ж, мы обсуждали синхронизацию не ради самой синхронизации, а в контексте ее использования в PWA, так что применяйте полученные знания на практике.
Я, кстати, вам изначально не говорил, но мы «разобрались» на визуальном уровне Протокол репликации CouchDB , который все это учитывает: позволяет создавать прозрачную синхронизацию на основе p2p с любым количеством пользователей и учитывает все описанные выше сценарии.
В свою очередь, этот протокол является частью базы данных CouchDB, поэтому отлично работает с серверами.
В браузере и на локальных машинах можно использовать PouchDB: аналогичный продукт, реализующий тот же протокол, но на JavaScript и node.js. Таким образом, вы можете использовать комбинацию CouchDB+PouchDB и охватить все необходимые варианты использования.
Разумеется, на мобильных телефонах эти технологии также представлены в виде Couchbase Mobile И Облачная синхронизация для iOS и Android соответственно.
Итак, как я сказал в самом начале первого поста:
Используйте готовый продукт. Но не забывайте досконально разбираться в технологии, чтобы рано или поздно вы могли создать свою собственную.
Вот и все.
Теги: #pwa #векторные часы #метка времени #контроль версий #синхронизация #разработка веб-сайтов #разработка iOS #разработка мобильных приложений #разработка Android
-
Фраунгофер, Йозеф
19 Oct, 24 -
Сеульский Мегаполис: Трудно Быть Богом
19 Oct, 24 -
Хакатон Hackingedu — Итоги
19 Oct, 24 -
Оумуамуа - Решение Парадокса Ферми
19 Oct, 24 -
Управление Роботами Через Интернет
19 Oct, 24 -
Устали Регистрироваться?
19 Oct, 24 -
Вам Нравятся Автопутешествия?
19 Oct, 24