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

224

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

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

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

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

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

Есть предположения?

tm_lv
источник
27
согласился, что очень мало разговоров о шаблонах для такого рода вещей ... хотя этот сценарий довольно распространен
Джек Уклея

Ответы:

88

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

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

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

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

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

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

С. Лотт
источник
Можете ли вы, сэр, объяснить, что такое дельта? Я думаю, что это комбинация хэш / метка времени ... Я хотел бы услышать от вас, сэр.
Анис
Дельта относится к изменению между двумя ревизиями. Например, если имя пользователя изменилось, то дельта может быть чем-то вроде {revision: 123, name: "John Doe"}
dipole_moment
31

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

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

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

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

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

Денис Буличенко
источник
3
^^^^^^ это, безусловно, лучший ответ, ребята!
hgoebl
Я согласен, Денис много чего привнес в тему + ссылки на статьи просто потрясающие. Также рассказывает об OT, упомянутом DanielPaull. Ответ С.Лотта хорош, но это гораздо глубже.
Кристиан
28

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

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

Даниэль Пол
источник
7
Даниэль, указатель на соответствующие ресурсы будет принята с благодарностью.
Парад
4
Я просто перечитал статью в Википедии. Это прошло долгий путь и имеет много соответствующих ссылок в нижней части этой страницы. Я бы указал вам на работу Чэнчжэна Сунь - на его работу ссылаются из википедии. en.wikipedia.org/wiki/Operational_transformation . Надеюсь, это поможет!
Даниэль Полл
13

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

erikkallen
источник
2
Порядковые номера ваш друг здесь. Подумайте о постоянных очередях сообщений.
Даниэль Полл
7

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

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

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

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

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

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

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

arlomedia
источник
1

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

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

Fuyi
источник