//幂等操作 private void RollbackOperations(TransactionDocumentP t, string source, string destination) { //1 将事务文档状态由pending更新为canceling. ReUpdateTransactionState(t, "pending", "canceling", new TimeSpan(0, 0, 100)); //2 账户余额回滚. FilterDefinitionBuilder <AccountP> filterBuilderS = Builders <AccountP> .Filter; FilterDefinition <AccountP> filterS1 = filterBuilderS.Eq(doc => doc._id, t.Source);//source FilterDefinition <AccountP> filterS2 = filterBuilderS.Where(doc => doc.PendingTransactions.Contains(t._id)); FilterDefinition <AccountP> filterS = filterBuilderS.And(new FilterDefinition <AccountP>[] { filterS1, filterS2 }); var updateS = Builders <AccountP> .Update.Inc(m => m.Balance, t.Value).Pull(m => m.PendingTransactions, t._id); bool isSuccess = mongoDBService.UpdateOne(AccountsCollectionName, filterS, updateS); if (isSuccess) { FilterDefinitionBuilder <AccountP> filterBuilderD = Builders <AccountP> .Filter; FilterDefinition <AccountP> filterD1 = filterBuilderD.Eq(doc => doc._id, t.Destination);//source FilterDefinition <AccountP> filterD2 = filterBuilderD.Where(doc => doc.PendingTransactions.Contains(t._id)); FilterDefinition <AccountP> filterD = filterBuilderD.And(new FilterDefinition <AccountP>[] { filterD1, filterD2 }); var updateD = Builders <AccountP> .Update.Inc(m => m.Balance, -t.Value).Pull(m => m.PendingTransactions, t._id); isSuccess = mongoDBService.UpdateOne(AccountsCollectionName, filterD, updateD); } if (isSuccess) { //3 将事务文档状态由canceling更新为cancelled. UpdateTransactionState(t, "canceling", "cancelled"); } }
//这一步失败一般是数据库或网络问题,一般若网络不稳定,通过重新执行几次应该可以解决问题 //但是如果是后台系统问题,那么可能需要很长时间恢复 /// <summary> /// 转账 /// </summary> /// <param name="value">转账金额</param> /// <param name="source">源账户</param> /// <param name="destination">目标账户</param> public void Process(decimal value, string source, string destination) { //超时时间 TimeSpan tSpan = new TimeSpan(0, 0, 100); //0 为参与事务的两个实体创建唯一的事务文档 PrepareTransfer(value, source, destination); //1 找到状态为"initial"的事务文档 TransactionDocumentP t2 = RetrieveTransaction(); //2 将事务文档状态由“initial”更改为“pending”,超时跳出 bool initial_pending = ReUpdateTransactionState(t2, "initial", "pending", tSpan); if (!initial_pending) { return; } //3 执行转账 bool isSuccessAp = ApplyTransaction(t2, value, source, destination); if (!isSuccessAp) { //回滚 RollbackOperations(t2, source, destination); return; } //4 将事务文档状态由“pending”更改为“applied” bool pending_applied = ReUpdateTransactionState(t2, "pending", "applied", tSpan); if (!pending_applied) { return; } //5 更新两个账户的待处理事务链表,移除事务标识,超时跳出 bool update = UpdateAccount(t2, source, destination, tSpan); if (!update) { return; } //6 将事务文档状态由“applied”更改为“done” bool applied_done = ReUpdateTransactionState(t2, "applied", "done", tSpan); if (!applied_done) { return; } //7 将事务文档状态由“done”更改为“initial” bool done_initial = ReUpdateTransactionState(t2, "done", "initial", tSpan); if (!done_initial) { return; } }
//超时解决不了的问题,只能通过定时执行方法清除,恢复 private void RecoveryOperations(TransactionDocumentP t, string state, DateTime maxTxnTime, decimal value, string source, string destination) { DateTime now = DateTime.Now; DateTime cutOff = DateTime.Parse((now - maxTxnTime).ToString()); FilterDefinitionBuilder <TransactionDocumentP> filterBuilder = Builders <TransactionDocumentP> .Filter; FilterDefinition <TransactionDocumentP> filter1 = filterBuilder.Eq(doc => doc.State, state); FilterDefinition <TransactionDocumentP> filter2 = filterBuilder.Lt(doc => doc.LastModified, cutOff); FilterDefinition <TransactionDocumentP> filter = Builders <TransactionDocumentP> .Filter.And(new FilterDefinition <TransactionDocumentP>[] { filter1, filter2 }); }
//检测超时 private bool CheckTimeOut(TransactionDocumentP t, TimeSpan maxTxnTime) { DateTime cutOff = DateTime.Now - maxTxnTime; FilterDefinitionBuilder <TransactionDocumentP> filterBuilder = Builders <TransactionDocumentP> .Filter; FilterDefinition <TransactionDocumentP> filter = filterBuilder.Lt(doc => doc.LastModified, cutOff); var tranDoc = mongoDBService.Single(TransactionCollectionName, filter); return(tranDoc == null ? true : false); }
private bool UpdateTransactionState(TransactionDocumentP t, string oldState, string newState) { if (t == null) { return(false); } FilterDefinitionBuilder <TransactionDocumentP> filterBuilder = Builders <TransactionDocumentP> .Filter; FilterDefinition <TransactionDocumentP> filter1 = filterBuilder.Eq(doc => doc._id, t._id); FilterDefinition <TransactionDocumentP> filter2 = filterBuilder.Eq(doc => doc.State, oldState); FilterDefinition <TransactionDocumentP> filter = filterBuilder.And(new FilterDefinition <TransactionDocumentP>[] { filter1, filter2 }); var update = Builders <TransactionDocumentP> .Update.Set(m => m.State, newState).Set(m => m.LastModified, DateTime.Now); UpdateResult updateResult = mongoDBService.DocumentUpdate(TransactionCollectionName, filter, update); return(updateResult.ModifiedCount > 0 && updateResult.ModifiedCount == updateResult.MatchedCount); }
//5 更新两个账户的待处理事务链表,移除事务标识 private bool UpdateAccount(TransactionDocumentP t, string source, string destination, TimeSpan maxTxnTime) { FilterDefinitionBuilder <AccountP> filterBuilderS = Builders <AccountP> .Filter; FilterDefinition <AccountP> filterS = filterBuilderS.Eq(doc => doc._id, source); var updateS = Builders <AccountP> .Update.Pull(doc => doc.PendingTransactions, t._id); bool isSucc = mongoDBService.UpdateOne(AccountsCollectionName, filterS, updateS); while (true) { if (isSucc) { break; } bool timeOut = CheckTimeOut(t, maxTxnTime); if (timeOut) { break; } isSucc = mongoDBService.UpdateOne(AccountsCollectionName, filterS, updateS); } if (!isSucc) { return(isSucc); } FilterDefinitionBuilder <AccountP> filterBuilderD = Builders <AccountP> .Filter; FilterDefinition <AccountP> filterD = filterBuilderD.Eq(doc => doc._id, destination); var updateD = Builders <AccountP> .Update.Pull(doc => doc.PendingTransactions, t._id); isSucc = mongoDBService.UpdateOne(AccountsCollectionName, filterD, updateD); while (true) { if (isSucc) { break; } bool timeOut = CheckTimeOut(t, maxTxnTime); if (timeOut) { break; } isSucc = mongoDBService.UpdateOne(AccountsCollectionName, filterD, updateD); } return(isSucc); }
//重复执行更新状态操作,超时跳出 private bool ReUpdateTransactionState(TransactionDocumentP t, string oldState, string newState, TimeSpan maxTxnTime) { bool isSucc = UpdateTransactionState(t, oldState, newState); while (true) { if (isSucc) { break; } bool timeOut = CheckTimeOut(t, maxTxnTime); if (timeOut) { break; } isSucc = UpdateTransactionState(t, oldState, newState); } return(isSucc); }
//创建事务文档 private void PrepareTransfer(decimal value, string source, string destination) { //创建事务文档 TransactionDocumentP tDoc = new TransactionDocumentP { _id = string.Format("{0}For{1}", source, destination), State = "initial", LastModified = DateTime.Now, Value = value, Source = source, Destination = destination }; FilterDefinitionBuilder <TransactionDocumentP> filterBuilder = Builders <TransactionDocumentP> .Filter; FilterDefinition <TransactionDocumentP> filter1 = filterBuilder.Eq(doc => doc._id, tDoc._id); if (mongoDBService.ExistDocument(TransactionCollectionName, filter1)) { return; } //将事务文档插入事务集合 mongoDBService.Insert(TransactionCollectionName, tDoc); }
//3 执行转账 private bool ApplyTransaction(TransactionDocumentP t, decimal value, string source, string destination) { FilterDefinitionBuilder <AccountP> filterBuilderS = Builders <AccountP> .Filter; FilterDefinition <AccountP> filterS1 = filterBuilderS.Eq(doc => doc._id, source); var updateS = Builders <AccountP> .Update.Inc(m => m.Balance, -value).Push(m => m.PendingTransactions, t._id); UpdateResult updateResultS = mongoDBService.DocumentUpdate(AccountsCollectionName, filterS1, updateS); bool isSuss = updateResultS.ModifiedCount > 0 && updateResultS.ModifiedCount == updateResultS.MatchedCount; if (isSuss) { FilterDefinitionBuilder <AccountP> filterBuilderD = Builders <AccountP> .Filter; FilterDefinition <AccountP> filterD1 = filterBuilderD.Eq(doc => doc._id, destination); var updateD = Builders <AccountP> .Update.Inc(m => m.Balance, value).Push(m => m.PendingTransactions, t._id); UpdateResult updateResultD = mongoDBService.DocumentUpdate(AccountsCollectionName, filterD1, updateD); isSuss = updateResultD.ModifiedCount > 0 && updateResultD.ModifiedCount == updateResultD.MatchedCount; } return(isSuss); }