Я знаю, что здесь есть аналогичные вопросы, но они либо говорят мне вернуться к обычным системам РСУБД, если мне нужны транзакции, либо использовать атомарные операции или двухфазную фиксацию . Второе решение кажется лучшим выбором. Третье я не хочу следовать, потому что кажется, что многое может пойти не так, и я не могу проверить его во всех аспектах. Мне сложно рефакторинг моего проекта для выполнения атомарных операций. Я не знаю, исходит ли это с моей ограниченной точки зрения (пока я работал только с базами данных SQL), или это действительно невозможно.
Мы хотели бы провести пилотное тестирование MongoDB в нашей компании. Мы выбрали относительно простой проект - SMS-шлюз. Это позволяет нашему программному обеспечению отправлять SMS-сообщения в сотовую сеть, а шлюз выполняет грязную работу: фактически общается с провайдерами через различные протоколы связи. Шлюз также управляет выставлением счетов за сообщения. Каждый покупатель, который обращается за услугой, должен купить кредиты. Система автоматически уменьшает баланс пользователя при отправке сообщения и отказывает в доступе, если баланс недостаточен. Кроме того, поскольку мы являемся клиентами сторонних поставщиков SMS, у нас также может быть собственный баланс на их счетах. Мы также должны следить за ними.
Я начал думать о том, как я могу хранить необходимые данные с помощью MongoDB, если я уменьшу некоторую сложность (внешний биллинг, отправка SMS в очереди). Исходя из мира SQL, я бы создал отдельную таблицу для пользователей, еще одну для SMS-сообщений и одну для хранения транзакций, касающихся баланса пользователей. Скажем, я создаю отдельные коллекции для всех в MongoDB.
Представьте себе задачу отправки SMS со следующими шагами в этой упрощенной системе:
проверьте, достаточно ли у пользователя средств на балансе; отказать в доступе, если недостаточно кредита
отправить и сохранить сообщение в коллекции SMS с подробностями и стоимостью (в действующей системе сообщение будет иметь
status
атрибут, и задача заберет его для доставки и установит цену SMS в соответствии с его текущим состоянием)уменьшить баланс пользователя на стоимость отправленного сообщения
зарегистрировать транзакцию в коллекции транзакций
В чем проблема? MongoDB может выполнять атомарные обновления только в одном документе. В предыдущем потоке могло случиться, что закрадывалась какая-то ошибка, и сообщение сохранялось в базе данных, но баланс пользователя не обновлялся и / или транзакция не регистрировалась.
У меня возникли две идеи:
Создайте единую коллекцию для пользователей и сохраните баланс как поле, транзакции и сообщения, связанные с пользователем, как вложенные документы в документе пользователя. Поскольку мы можем обновлять документы атомарно, это фактически решает проблему транзакции. Недостатки: если пользователь отправляет много SMS-сообщений, размер документа может стать большим и может быть достигнут предел документа в 4 МБ. Может быть, я смогу создавать исторические документы в таких сценариях, но я не думаю, что это будет хорошей идеей. Также я не знаю, насколько быстрой будет система, если я буду помещать все больше и больше данных в один и тот же большой документ.
Создайте одну коллекцию для пользователей и одну для транзакций. Транзакции могут быть двух видов: покупка в кредит с положительным изменением баланса и сообщения, отправленные с отрицательным изменением баланса. У транзакции может быть вложенный документ; например, в отправленных сообщениях детали SMS могут быть встроены в транзакцию. Недостатки: я не храню текущий баланс пользователя, поэтому мне приходится вычислять его каждый раз, когда пользователь пытается отправить сообщение, чтобы узнать, может ли сообщение пройти или нет. Боюсь, что этот расчет может замедлиться по мере роста количества хранимых транзакций.
Я немного не понимаю, какой метод выбрать. Есть ли другие решения? Я не смог найти в Интернете никаких передовых методов решения подобных проблем. Я полагаю, что многие программисты, которые пытаются познакомиться с миром NoSQL, вначале сталкиваются с аналогичными проблемами.
источник
Ответы:
Начиная с 4.0, MongoDB будет иметь транзакции ACID с несколькими документами. План состоит в том, чтобы сначала включить те, которые находятся в развертывании набора реплик, а затем сегментированные кластеры. Транзакции в MongoDB будут выглядеть так же, как транзакции, с которыми разработчики знакомы по реляционным базам данных - они будут многопользовательскими, с аналогичной семантикой и синтаксисом (например,
start_transaction
иcommit_transaction
). Важно отметить, что изменения в MongoDB, которые позволяют выполнять транзакции, не влияют на производительность рабочих нагрузок, для которых они не требуются.Подробнее см. Здесь .
Наличие распределенных транзакций не означает, что вы должны моделировать свои данные, как в табличных реляционных базах данных. Воспользуйтесь мощью модели документа и следуйте передовым и рекомендуемым практикам моделирования данных.
источник
Жизнь без сделок
Транзакции поддерживают свойства ACID, но, хотя в них нет транзакций
MongoDB
, у нас есть атомарные операции. Что ж, атомарные операции означают, что когда вы работаете над одним документом, эта работа будет завершена до того, как кто-либо другой увидит этот документ. Они увидят все внесенные нами изменения или ни одного из них. А с помощью атомарных операций вы часто можете выполнить то же самое, что мы сделали бы, используя транзакции в реляционной базе данных. Причина в том, что в реляционной базе данных нам нужно вносить изменения в несколько таблиц. Обычно таблицы, которые нужно соединить, и поэтому мы хотим сделать это сразу. И для этого, поскольку существует несколько таблиц, нам нужно будет начать транзакцию и выполнить все эти обновления, а затем завершить транзакцию. Но сMongoDB
, мы собираемся встроить данные, так как мы собираемся предварительно объединить их в документы, а это эти богатые документы с иерархией. Часто мы можем добиться того же. Например, в примере с блогом, если мы хотим убедиться, что мы обновили сообщение в блоге атомарно, мы можем это сделать, потому что мы можем обновить все сообщение в блоге сразу. Там, где, как если бы это была связка реляционных таблиц, нам, вероятно, пришлось бы открыть транзакцию, чтобы мы могли обновить коллекцию сообщений и коллекцию комментариев.Итак, какие наши подходы мы можем использовать,
MongoDB
чтобы преодолеть нехватку транзакций?Update
,findAndModify
,$addToSet
( В пределах обновления) и$push
( в пределах обновления) операции выполняют атомарна в одном документе.источник
Проверьте это Tokutek. Они разрабатывают плагин для Mongo, который обещает не только транзакции, но и повышение производительности.
источник
Подводя итог: если целостность транзакций является обязательной , не используйте MongoDB, а используйте только компоненты в системе, поддерживающие транзакции. Чрезвычайно сложно создать что-то поверх компонента, чтобы обеспечить функциональность, аналогичную ACID, для компонентов, не совместимых с ACID. В зависимости от индивидуальных вариантов использования может иметь смысл каким-то образом разделить действия на транзакционные и нетранзакционные действия ...
источник
Это не проблема. Упомянутая вами ошибка является либо логической (ошибка), либо ошибкой ввода-вывода (сеть, сбой диска). Такая ошибка может привести к тому, что хранилища без транзакций и транзакционные хранилища окажутся в несогласованном состоянии. Например, если он уже отправил SMS, но при сохранении сообщения возникла ошибка - он не может откатить отправку SMS, а это значит, что он не будет зарегистрирован, баланс пользователя не будет уменьшен и т. Д.
Настоящая проблема здесь в том, что пользователь может воспользоваться состоянием гонки и отправить больше сообщений, чем позволяет его баланс. Это также относится к СУБД, если вы не отправляете SMS внутри транзакции с блокировкой поля баланса (что было бы большим узким местом). В качестве возможного решения для MongoDB можно было бы
findAndModify
сначала уменьшить баланс и проверить его, если он отрицательный, запретить отправку и возместить сумму (атомарное приращение). Если положительный, продолжайте отправку и в случае неудачи верните сумму. Также можно вести сбор истории баланса, чтобы помочь исправить / проверить поле баланса.источник
Проект прост, но вы должны поддерживать транзакции для оплаты, что все усложняет. Так, например, сложная система портала с сотнями коллекций (форум, чат, реклама и т. Д.) В некотором отношении проще, потому что, если вы потеряете форум или запись в чате, никого не волнует. Если вы, с другой стороны, потеряете платежную транзакцию, это серьезная проблема.
Итак, если вам действительно нужен пилотный проект для MongoDB, выберите простой в этом отношении.
источник
Транзакции отсутствуют в MongoDB по уважительным причинам. Это одна из тех вещей, которые делают MongoDB быстрее.
В вашем случае, если транзакция является обязательной, mongo не подходит.
Может быть RDMBS + MongoDB, но это добавит сложности и затруднит управление и поддержку приложения.
источник
Это, вероятно, лучший блог, который я нашел о реализации транзакционной функции для mongodb.!
Флаг синхронизации: лучше всего подходит для простого копирования данных из главного документа
Очередь заданий: очень общего назначения, решает 95% случаев. В любом случае большинству систем требуется хотя бы одна очередь заданий!
Двухэтапная фиксация: этот метод гарантирует, что у каждой сущности всегда есть вся информация, необходимая для перехода в согласованное состояние
Сверка журналов: самый надежный метод, идеально подходящий для финансовых систем
Управление версиями: обеспечивает изоляцию и поддерживает сложные структуры
Прочтите это для получения дополнительной информации: https://dzone.com/articles/how-implement-robust-and
источник
Это поздно, но думаю, это поможет в будущем. Я использую Redis для создания очереди для решения этой проблемы.
Требование: на
изображении ниже показано, что 2 действия необходимо выполнять одновременно, но этап 2 и этап 3 действия 1 должны завершиться до начала этапа 2 действия 2 или наоборот (этап может быть запросом REST api, запросом базы данных или выполнением кода javascript ... ).
Как очередь помогает вам?
Очередь убедитесь, что каждый блочный код между многими функциями
lock()
иrelease()
во многих функциях не будет выполняться одновременно, сделайте их изолированными.Как создать очередь
Я сосредоточусь только на том, как избежать части состояния гонки при построении очереди на внутреннем сайте. Если вы не знаете основы очереди, иди сюда .
В приведенном ниже коде показана только концепция, которую необходимо правильно реализовать.
Но вам нужно
isRunning()
setStateToRelease()
setStateToRunning()
изолировать его, иначе вы снова столкнетесь с условиями гонки. Для этого я выбираю Redis для ACID и масштабируемость.В документе Redis говорится о транзакции:
P / s:
Я использую Redis, потому что мой сервис уже использует его, вы можете использовать любой другой способ поддержки изоляции для этого.
В
action_domain
моем коде выше показано, когда вам нужно только действие 1, вызванное пользователем A, заблокировать действие 2 пользователя A, не блокировать другого пользователя. По идее ставится уникальный ключ для блокировки каждого пользователя.источник
Транзакции теперь доступны в MongoDB 4.0. Образец здесь
// Runs the txnFunc and retries if TransientTransactionError encountered function runTransactionWithRetry(txnFunc, session) { while (true) { try { txnFunc(session); // performs transaction break; } catch (error) { // If transient error, retry the whole transaction if ( error.hasOwnProperty("errorLabels") && error.errorLabels.includes("TransientTransactionError") ) { print("TransientTransactionError, retrying transaction ..."); continue; } else { throw error; } } } } // Retries commit if UnknownTransactionCommitResult encountered function commitWithRetry(session) { while (true) { try { session.commitTransaction(); // Uses write concern set at transaction start. print("Transaction committed."); break; } catch (error) { // Can retry commit if (error.hasOwnProperty("errorLabels") && error.errorLabels.includes("UnknownTransactionCommitResult") ) { print("UnknownTransactionCommitResult, retrying commit operation ..."); continue; } else { print("Error during commit ..."); throw error; } } } } // Updates two collections in a transactions function updateEmployeeInfo(session) { employeesCollection = session.getDatabase("hr").employees; eventsCollection = session.getDatabase("reporting").events; session.startTransaction( { readConcern: { level: "snapshot" }, writeConcern: { w: "majority" } } ); try{ employeesCollection.updateOne( { employee: 3 }, { $set: { status: "Inactive" } } ); eventsCollection.insertOne( { employee: 3, status: { new: "Inactive", old: "Active" } } ); } catch (error) { print("Caught exception during transaction, aborting."); session.abortTransaction(); throw error; } commitWithRetry(session); } // Start a session. session = db.getMongo().startSession( { mode: "primary" } ); try{ runTransactionWithRetry(updateEmployeeInfo, session); } catch (error) { // Do something with error } finally { session.endSession(); }
источник