目前 .NET Core 不支持System.Transactions 这个命名空间,所以火遍全球的 .NET Core 不在本示例的讨论范围。WCF 天生支持分布式事务,所以也不讨论。本文主要讨论 WebApi / WebForm 如何支持 DTC.
为什么要用 DTC
我司现在的系统架构很老了,大部分还是通过 ashx 在内部各个系统间传输数据。要命的订单处理流程涉及到多个服务(网站),如果哪个环节出了个小漏子,那只能手动重来一次了。为了解决这个问题,我研究了下 DTC(虽然很老了,但是一直没机会接触过)。
DTC
分布式事务 Distributed Transaction Coordinator , Windows 下叫 MSDTC
要使用 DTC 当然要开启这个服务,为了简单期间,我们这样设置:
- 控制面板\所有控制面板项\管理工具
- DTC 设置
示例地址
https://github.com/gruan01/DTCTest
原理
- 发起请求的时候,在请求头中添加当前事务的传播标识。
- 服务端收到请求的时候,读取请求头中的传播标识。
- 如果存在,跟据接收到的传播标识还原事务。
- 在客户端决定是提交还是回滚 。
从客户端发起一个需要 DTC 的调用
if (Transaction.Current != null) {
var token = TransactionInterop.GetTransmitterPropagationToken(Transaction.Current);
xxx.Headers.Add("TransactionToken", Convert.ToBase64String(token));
}
根据你的使用的组件自行修改。
WebForm 如何支持 DTC
由于生命周期的限制,事务的还原与使用不能在 Global 的 Application_BeginRequest / Application_EndRequest 中,实测无效。
简便期间,我做了一个包装:
public class DTCWrapper : IDisposable {
private static readonly string TransactionID = "TransactionToken";
private TransactionScope Scope = null;
private Transaction Trans = null;
public DTCWrapper() {
var context = HttpContext.Current;
if (context.Request.Headers.AllKeys.Contains(TransactionID)) {
var values = context.Request.Headers.GetValues(TransactionID);
if (values != null && values.Any()) {
var token = Convert.FromBase64String(values.First());
this.Trans = TransactionInterop.GetTransactionFromTransmitterPropagationToken(token);
//var scope = new TransactionScope(trans, TransactionScopeAsyncFlowOption.Enabled);
this.Scope = new TransactionScope(this.Trans);
}
}
}
public void Commit() {
if (this.Scope != null)
this.Scope.Complete();
}
public void Rollback(Exception ex = null) {
if (ex != null)
this.Trans.Rollback(ex);
else
this.Trans.Rollback();
}
#region dispose
public void Dispose() {
this.Dispose(true);
GC.SuppressFinalize(this);
}
~DTCWrapper() {
this.Dispose(false);
}
private bool isDisposed = false;
private void Dispose(bool flag) {
if (flag && !isDisposed) {
isDisposed = true;
if (this.Trans != null) {
this.Trans.Dispose();
this.Trans = null;
}
if (this.Scope != null) {
this.Scope.Dispose();
this.Scope = null;
}
}
}
#endregion
}
使用:
private void Process(string ctx) {
using (var dtc = new DTCWrapper())
using (var db = new TestDbContext()) {
db.Logs.Add(new Data2.Models.Log() {
CreateOn = DateTime.Now,
Ctx = ctx
});
db.SaveChanges();
dtc.Commit();
}
}
WebApi / MVC 如何支持 DTC
WebApi 就没有 WebForm 那样复杂的生命周期了,而且 WebApi / MVC 的 ActionFilter 特性可以很方便的对 Action 的执行前/执行后做手脚,所以 WebApi / MVC 对 DTC 的支持简直太那个了。。。
public class DTCAttribute : ActionFilterAttribute {
private static readonly string TransactionID = "TransactionToken";
public override void OnActionExecuting(HttpActionContext context) {
base.OnActionExecuting(context);
if (context.Request.Headers.Contains(TransactionID)) {
var values = context.Request.Headers.GetValues(TransactionID);
if (values != null && values.Any()) {
var token = Convert.FromBase64String(values.First());
var trans = TransactionInterop.GetTransactionFromTransmitterPropagationToken(token);
var transactionScope = new TransactionScope(trans);
context.Request.Properties.Add(TransactionID, transactionScope);
}
}
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) {
base.OnActionExecuted(actionExecutedContext);
if (actionExecutedContext.Request.Properties.ContainsKey(TransactionID)) {
var tScope = (TransactionScope)actionExecutedContext.Request.Properties[TransactionID];
if (tScope != null) {
if (actionExecutedContext.Exception != null) {
Transaction.Current.Rollback(actionExecutedContext.Exception);
} else {
tScope.Complete();
}
tScope.Dispose();
actionExecutedContext.Request.Properties.Remove(TransactionID);
}
}
}
}
使用:
[DTC]
public class TestController : ApiController {
。。。
如何看测试效果
请移步 https://github.com/gruan01/DTCTest/
参考
https://code.msdn.microsoft.com/Distributed-Transactions-c7e0a8c2
网友评论