MongoDB: реализовать блокировку чтения/записи (мьютекс)

Вопрос:Мне нужно реализовать некоторый механизм блокировки с MongoDB, чтобы предотвратить несогласованные данные, но разрешить грязные чтения. Условия: Приобретение блокировки WRITE возможно только при отсутствии блокировки READ lock и no WRITE. Приобретение блокировки READ возможно только при отсутствии блокировки WRITE. В одном документе может быть много параллельных блокировок READ. Должен существовать какой-то механизм тайм-аута: если (по

Вопрос:

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

Содержание

  1. Условия:
  2. Почему READ и WRITE блокирует/почему не только использует блокировку WRITE:
  3. Таймаут:
  4. Моя последняя реализация:
  5. Запросы:
  6. РЕДАКТИРОВАТЬ 1: Упрощенный запрос READ и WRITE
  7. EDIT 2: Разная структура данных для облегчения освобождения READ lock
  8. Вопросы:

Условия:

  • Приобретение блокировки WRITE возможно только при отсутствии блокировки READ lock и no WRITE.

  • Приобретение блокировки READ возможно только при отсутствии блокировки WRITE.

  • В одном документе может быть много параллельных блокировок READ.

  • Должен существовать какой-то механизм тайм-аута: если (по какой-то причине) какой-то процесс не освобождает свою блокировку, приложение должно восстановить.

Грязные чтения возможны, просто игнорируя все блокировки в запросе.

(Голодание процессов WRITE не входит в эту тему)

Почему READ и WRITE блокирует/почему не только использует блокировку WRITE:

Предположим, что у нас есть 2 коллекции: contacts и categories. Это отношение n-m, где каждый контакт имеет массив идентификаторов категорий.

READ lock: При добавлении категории к контакту мы должны убедиться, что эта категория не удаляется в данный момент (для чего требуется блокировка WRITE, см. ниже), И поскольку в одном документе может быть много блокировок READ, для нескольких процессов можно добавить эту отдельную категорию к нескольким контактам.

WRITE lock: При удалении категории мы должны сначала удалить идентификатор категории из всех контактов. Пока эта операция выполняется, мы должны удостовериться, что нельзя добавлять эту категорию к любому контакту (для этой операции требуется блокировка READ). Впоследствии мы можем безопасно удалить документ категории.

Таким образом, всегда будет согласованное состояние.

Таймаут:

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

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

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

Моя последняя реализация:

{ _id: 1234, lock: { read: [ ISODate(«2015-06-26T12:00:00Z») ], write: null } }

Либо lock.read может содержать элементы или lock.write. Никогда не бывает возможности установить оба набора!

Запросы:

Запросы для этого в порядке, некоторые могут быть немного проще (особенно “блокировка чтения релиза” ). Но главная причина показать их вам в том, что я все еще не уверен, что я ничего не пропустил.

Введение:

  • ISODate(«now») – текущее время. Он игнорировал все блокировки, срок действия которых истек. И он также использовал для удаления всех истекших блокировок чтения.
  • ISODate(«lock expiration») используется, чтобы указать, когда эта блокировка истечет и может быть проигнорирована/удалена. (например, now + 5 seconds)
    • Это используется при приобретении новой блокировки.
    • И он также используется при освобождении блокировки чтения.

Приобретать READ lock:

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

update( { _id: 1234, $or: [ { ‘lock.write’: null }, { ‘lock.write’: { $lt: ISODate(«now») } } ] }, { $set: { ‘lock.write’: null }, $push: { ‘lock.read’: ISODate(«lock expiration») } } )

Приобретать WRITE lock:

Если нет допустимой блокировки чтения и, нет допустимой блокировки записи, установите блокировку записи.

update( { _id: 1234, $and: [ $or: [ { ‘lock.read’:{ $size: 0 } }, { ‘lock.read’:{ $not: { $gte: ISODate(«now») } } } ], $or: [ { ‘lock.write’: null }, { ‘lock.write’: { $lt: ISODate(«now») } } ] ] }, { $set: { ‘lock.read’: [], ‘lock.write’: ISODate(«lock expiration») } } )

Блокировка READ:

Удалите приобретенную блокировку чтения с помощью метки времени истечения срока ее действия.

update( { _id: 1234, ‘lock.read’: ISODate(«lock expiration») }, { $unset: { ‘lock.read.$’: null } } ) update( { _id: 1234, }, { $pull: { ‘lock.read’: { $lt: ISODate(«now») } } } ) update( { _id: 1234 }, { $pull: { ‘lock.read’: null } } )

(Возможно, массив lock.read содержит несколько идентичных временных меток, если несколько процессов приобрели блокировку READ. Хотя нам нужно удалить только одну метку времени, и это не сработает с $pull, но работает с использованием оператора позиционирования $.
Также я удаляю все истекшие блокировки с дополнительным обновлением. Я пробовал некоторые вещи, но не смог уменьшить его до 2 или даже 1 обновления.)

Блокировка WRITE:

Удалите журнал записи. Здесь не должно быть ничего, чтобы проверить.

update( { _id: 1234 }, { $set: { ‘lock.write’: null } } ) РЕДАКТИРОВАТЬ 1: Упрощенный запрос READ и WRITE

{ $not: { $gte: ISODate(«now») } } будет соответствовать только, если поле не содержит что-либо $gte: ISODate(«now»). Хотя он будет соответствовать null и несуществующим полям, а также пустой массив.

Приобретать READ lock:

update( { _id: 1234, ‘lock.write’: { $not: { $gte: ISODate(«now») } } }, { $set: { ‘lock.write’: null }, $push: { ‘lock.read’: ISODate(«lock expiration») } } )

Приобретать WRITE lock:

update( { _id: 1234, ‘lock.write’: { $not: { $gte: ISODate(«now») } }, ‘lock.read’: { $not: { $gte: ISODate(«now») } } }, { $set: { ‘lock.read’: [], ‘lock.write’: ISODate(«lock expiration») } } )

Но до сих пор нет идеи относительно запроса Release READ lock…

Я думал о каком-то кортеже, имеющем временную метку тайм-аута и счет блокировок. Но тогда проблема связана с запросом блокировки Acquire READ.

EDIT 2: Разная структура данных для облегчения освобождения READ lock{ _id: 1234, lock: { read: [ { timeout: ISODate(«2015-06-26T12:00:00Z»), process: ObjectId(«…») } ], write: null } }

Это работает, потому что ObjectId состоит из метки времени, идентификатора машины, идентификатора процесса и счетчика. Таким образом, невозможно создать несколько равных ObjectIds. Короче говоря:

При приобретении блокировки READ мы вставляем документ, состоящий из временной метки тайм-аута и уникальной ObjectId. И, выпуская его, мы используем эту комбинацию, чтобы удалить ее из массива. Итак, единственные интересные запросы:

Замок Aquire WRITE:

update( { _id: 1234, ‘lock.write’: { $not: { $gte: 4 } }, ‘lock.read.timeout’: { $not: { $gte: 4 } } }, { $set: { ‘lock.read’: [], ‘lock.write’: ISODate(«lock expiration») } } )

Блокировка READ:

update( { _id: 1234, }, { $pull: { ‘lock.read’: { $or: [ { ‘timeout’: ISODate(«lock expiration»), process: ObjectId(«…») }, { ‘timeout’: { $lt: ISODate(«now») } } ] } } } )

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

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

Следующим шагом было бы избавиться от поля process и использовать только ObjectId, который должен содержать часть timeout. (например, Mongodb: выполнить запрос диапазона дат из ObjectId в оболочке mongo)

Вопросы:

  • Является ли это допустимой и пуленепробиваемой версией с использованием MongoDB?

  • Если “да”: могу ли я как-то улучшить его? (по крайней мере, часть “Release READ lock” )

  • Если “нет”: что с этим не так? Что я пропустил?

Заранее благодарим за помощь!

Оцените статью
Добавить комментарий