【问题标题】:How to implement transactions in Meteor Method calls如何在 Meteor 方法调用中实现事务
【发布时间】:2017-10-23 11:54:30
【问题描述】:

假设我有 2 个集合“PlanSubscriptions”和“ClientActivations”。我正在对这两个集合进行连续插入。

后一个依赖前一个,如果任何一个事务失败,那么整个操作必须回滚。

如何在 Meteor 1.4 中实现这一点?

【问题讨论】:

    标签: meteor


    【解决方案1】:

    在 Meteor 中没有办法做到这一点,因为 mongodb 不是 ACID 兼容的数据库。它具有单文档更新原子性,但不是多文档更新原子性,这就是您使用两个集合的情况。 来自 mongo 文档:

    单个写操作修改多个文档时,每个文档的修改都是原子的,但是整个操作不是原子的,其他操作可能交错。

    有一种方法可以隔离多文档更新的可见性,但这可能不是您所需要的。

    使用 $isolated 运算符,一旦写入操作修改了第一个文档,影响多个文档的写入操作可以防止其他进程交错。这确保了在写入操作完成或出错之前没有客户端看到更改。 独立的写入操作不提供“全有或全无”的原子性。也就是说,写入操作期间的错误不会回滚错误之前的所有更改

    但是,有几个库试图在应用程序级别解决问题。我建议看看fawn

    在您的情况下,您恰好有两个依赖集合,可以利用两阶段提交技术。在此处阅读更多信息:two-phase-commits

    【讨论】:

      【解决方案2】:

      由于 MongoDB 不支持原子性,您必须使用方法链来管理它。

      您可以编写一个方法,例如transaction,您将在其中调用PlanSubscriptions.insert(data, callback)。然后在回调函数中,如果第一次插入成功,您将调用ClientActivations.insert(data, callback1),如果第二次插入成功,则在callback1 中返回真值,否则返回假值。如果第一次插入返回错误,则无需执行任何操作,但如果第二次插入返回错误,则删除从第一个集合中插入的 id。

      我可以建议以下结构:

      'transaction'(){
          PlanSubscriptions.insert(data, (error, result)=>{
              if(result){
                  // result contains the _id
                  let id_plan = result;
                  ClientActivations.insert(data, (error, result)=>{
                      if(result){
                          // result contains the _id
                          return true;
                      }
                      else if(error){
                          PlanSubscriptions.remove(id_plan);
                          return false;
                      }
                  })
              }
              else if(error){
                  return false;
              }
          })
      }
      

      【讨论】:

      • 非常感谢!你回答了我的问题。我也喜欢你的解决方案,你如何看待我在上面发布的答案,我也得到了正确的执行并且整个事务的原子性得到了维护?如果您能说明我的回答,我将不胜感激。提前致谢。
      • 实际上我有点不愿意使用任何额外的包,因为它可以用最少的努力轻松解决。我已经看到你的答案,它可能也有效。
      • 赞成,因为您的实现不需要任何其他额外的包。
      【解决方案3】:

      嗯,我自己想通了。

      我添加了一个包babrahams:transactions

      在服务器端 Meteor Method 调用中,我调用了 tx 包全局公开的对象。整个服务器端Meteor.method({}) 如下所示。

      import { Meteor } from 'meteor/meteor';
      import {PlanSubscriptions} from '/imports/api/plansubscriptions/plansubscriptions.js';
      import {ClientActivations} from '/imports/api/clientactivation/clientactivations.js';
      
      Meteor.methods({
          'createClientSubscription' (subscriptionData, clientActivationData) {
                  var txid;
                  try {
                  txid = tx.start("Adding Subscription to our database");
      
                  PlanSubscriptions.insert(subscriptionData, {tx: true})
      
                  ClientActivations.insert(activation, {tx: true});
      
                  tx.commit();
      
                  return true;
              } catch(e){
                  tx.undo(txid);
              }
              return false;
          }
      });
      

      每次插入我都添加了{tx : true},这表明它是事务的一部分。

      服务器控制台输出:

      I20170523-18:43:23.544(5.5)? Started "Adding Subscription to our database" with
      transaction_id: vdJQvFgtyZuWcinyF
      I20170523-18:43:23.547(5.5)? Pushed insert command to stack: vdJQvFgtyZuWcinyF
      I20170523-18:43:23.549(5.5)? Pushed insert command to stack: vdJQvFgtyZuWcinyF
      I20170523-18:43:23.551(5.5)? Beginning commit with transaction_id: vdJQvFgtyZuWcinyF
      I20170523-18:43:23.655(5.5)? Executed insert
      I20170523-18:43:23.666(5.5)? Executed insert
      I20170523-18:43:23.698(5.5)? Commit reset transaction manager to clean state
      

      欲了解更多信息,您可以转到链接:https://github.com/JackAdams/meteor-transactions

      注意:我使用的是 Meteor 1.4.4.2

      【讨论】:

        【解决方案4】:

        仅将此链接分享给未来的读者:

        https://forums.meteor.com/t/solved-transactions-with-mongodb-meteor-methods/48677

        import { MongoInternals } from 'meteor/mongo';
        
        // utility async function to wrap async raw mongo operations with a transaction
        const runTransactionAsync = async asyncRawMongoOperations => {
        
            // setup a transaction
            const { client } = MongoInternals.defaultRemoteCollectionDriver().mongo;
            const session = await client.startSession();
            await session.startTransaction();
            try {
                // running the async operations
                let result = await asyncRawMongoOperations(session);
                await session.commitTransaction();
                // transaction committed - return value to the client
                return result;
            } catch (err) {
                await session.abortTransaction();
                console.error(err.message);
                // transaction aborted - report error to the client
                throw new Meteor.Error('Database Transaction Failed', err.message);
            } finally {
                session.endSession();
            }
        };
        
        import { runTransactionAsync } from '/imports/utils'; // or where you defined it
        
        Meteor.methods({
            async doSomething(arg) {
                // remember to check method input first
        
                // define the operations we want to run in transaction
                const asyncRawMongoOperations = async session => {
        
                    // it's critical to receive the session parameter here
                    // and pass it to every raw operation as shown below
                    const item = await collection1.rawCollection().findOne(arg, { session: session });
                    const response = await collection2.rawCollection().insertOne(item, { session: session });
        
                    // if Mongo or you throw an error here runTransactionAsync(..) will catch it
                    // and wrap it with a Meteor.Error(..) so it will arrive to the client safely
        
                    return 'whatever you want'; // will be the result in the client
                };
        
                let result = await runTransactionAsync(asyncRawMongoOperations);
                return result;
            }
        });
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-09-30
          • 1970-01-01
          • 2011-08-26
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多