Шаблон/алгоритм синхронизации клиент-сервер?

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

Ситуация довольно проста - сервер является центральным node, который несколько клиентов подключаются к тем же данным и манипулируют ими. Данные могут быть разделены на атомы, в случае конфликта, независимо от того, что находится на сервере, имеет приоритет (чтобы избежать попадания пользователя в конфликтное решение). Частичная синхронизация предпочтительна из-за потенциально больших объемов данных.

Есть ли какие-либо шаблоны/хорошие практики для такой ситуации, или если вы не знаете ни о чем, - каков будет ваш подход?

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

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

Любые мысли?

Ответ 1

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

У вас есть несколько вариантов использования.

  • Синхронизировать изменения. Для этого подходит ваш журнал изменений (или дельта-история). Клиенты отправляют свои дельта на сервер; сервер объединяет и распределяет дельты клиентам. Это типичный случай. Базы данных называют эту "транзакционную репликацию".

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

  • Клиент подозрительный. В этом случае вам нужно сравнить клиент с сервером, чтобы определить, является ли клиент актуальным и нуждается в каких-либо дельтах.

Вы должны следовать шаблону проектирования базы данных (и SVN), последовательно повторяя каждое изменение. Таким образом, клиент может выполнить тривиальный запрос ( "Какую ревизию следует иметь?" ) Перед попыткой синхронизации. И даже тогда запрос ( "Все дельта с 2149 года" ) очень приятен для клиента и сервера.

Ответ 2

Что вам действительно нужно, Operational Transform (OT). Это может даже удовлетворить конфликты во многих случаях.

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

Ответ 3

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

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

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

  • Синхронизация целого документа или базы данных используется в облачных приложениях, таких как Dropbox, Google Drive или Yandex.Disk. Когда пользователь редактирует и сохраняет файл, новая версия файла полностью загружается в облако, перезаписывая более раннюю копию. В случае конфликта обе версии файлов сохраняются так, что пользователь может выбрать, какая версия более актуальна.
  • Синхронизация пар ключ-значение может использоваться в приложениях с простой структурой данных, где переменные считаются атомарными, то есть не делятся на логические компоненты. Этот параметр похож на синхронизацию целых документов, так как и значение, и документ могут быть полностью перезаписаны. Однако, с точки зрения пользователя, документ представляет собой сложный объект, состоящий из множества частей, но пара ключ-значение представляет собой короткую строку или число. Поэтому в этом случае мы можем использовать более простую стратегию разрешения конфликтов, считая ценность более актуальной, если она была последней, чтобы измениться.
  • Синхронизация данных, структурированных как дерево или граф, используется в более сложных приложениях, где объем данных достаточно велик, чтобы полностью отправлять базу данных при каждом обновлении. В этом случае конфликты должны решаться на уровне отдельных объектов, полей или отношений. В первую очередь мы ориентируемся на этот вариант.

Итак, мы захватили наши знания в этой статье, которые, по моему мнению, могут быть очень полезны для всех, кто интересуется темой = > Синхронизация данных в приложениях на базе Core Data iOS (http://blog.denivip.ru/index.php/2014/04/data-syncing-in-core-data-based-ios-apps/?lang=en)

Ответ 4

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

Ответ 5

Для дельта (смены) синхронизации вы можете использовать шаблон pubsub для публикации изменений обратно всем подписчикам, такие услуги, как pusher могут делать это.

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

Ответ 6

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

Я начал с записи каждого изменения (вставки, обновления или удаления) с любого устройства в таблицу "истории". Так, если, например, кто-то изменит свой номер телефона в таблице "контакт", система отредактирует поле contact.phone, а также добавит запись истории с действием = обновление, поле = телефон, запись = [идентификатор контакта], value = [новый номер телефона]. Затем, когда устройство синхронизируется, оно загружает элементы истории с момента последней синхронизации и применяет их к своей локальной базе данных. Это похоже на шаблон "репликации транзакций", описанный выше.

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

В конце концов я узнал, что UUIDs могли бы избежать этого, но к тому времени моя база данных стала довольно большой, и я боялся, что полная реализация UUID создаст проблему производительности. Поэтому вместо использования полных UUID я начал использовать случайно сгенерированные 8-символьные буквенно-цифровые ключи в качестве идентификаторов, и я оставил свой существующий код на месте для обработки конфликтов. Где-то между моими нынешними 8-символьными клавишами и 36 символами UUID должно быть хорошее место, которое устраняло бы конфликты без ненужного раздувания, но, поскольку у меня уже есть код разрешения конфликтов, экспериментировать с этим не было приоритетом.,

Следующая проблема заключалась в том, что таблица истории была примерно в 10 раз больше, чем вся остальная база данных. Это делает хранение дорогостоящим, и любое обслуживание таблицы истории может быть болезненным. Сохранение всей этой таблицы позволяет пользователям откатывать любые предыдущие изменения, но это начинало казаться излишним. Поэтому я добавил подпрограмму в процесс синхронизации, где, если элемент истории, который было загружено устройством в последний раз, больше не существует в таблице истории, сервер не передает ему последние элементы истории, а вместо этого предоставляет файл, содержащий все данные для этот аккаунт. Затем я добавил cronjob, чтобы удалить элементы истории старше 90 дней. Это означает, что пользователи по-прежнему могут откатывать изменения менее чем за 90 дней, и если они синхронизируются не реже одного раза в 90 дней, обновления будут, как и прежде, инкрементными. Но если они будут ждать дольше 90 дней, приложение заменит всю базу данных.

Это изменение уменьшило размер таблицы истории почти на 90%, поэтому теперь ведение таблицы истории только увеличивает базу данных в два раза, а не в десять раз больше. Еще одним преимуществом этой системы является то, что синхронизация может по-прежнему работать без таблицы истории при необходимости - например, если мне нужно выполнить какое-то обслуживание, которое временно отключило ее. Или я мог бы предложить разные периоды отката для учетных записей в разных ценовых категориях. И если для загрузки требуется более 90 дней изменений, полный файл обычно более эффективен, чем инкрементный формат.

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