最近在给客户做分布式事务的解决方案的时候发现Atomikos提供了一种有趣的跨服务的分布式事务解决方案,实现了微服务之间数据的强一致性。
我本人并不赞同和喜欢这种分布式事务的实现方式,我更喜欢Saga方式的分布式事务解决方案。强一致的事务需要为资源加锁,网络或者事务参与方的故障会导致大量的资源不能释放,在极端情况下可能导致应用雪崩。我的观点是在应用架构设计中要尽量避免这种强一致的分布式事务实现方式,根据不同的业务场景,设计灵活的事务补偿方案,利用Rocket MQ等中间件协调事务的各个参与方,附加自动扫描与监控机制,必要时人工干预,从而达到事务的最终一致性。
感兴趣的朋友可以访问Atomikos的网站和gitub,了解这种跨服务事务的具体实现。
点击下载示例代码, 解压后使用JDK 8编译执行examples-jta-rest-jaxrs目录下的Java代码,如果使用JDK 8以上版本需要在pom中添加JAXB依赖。
实现原理
客户端配置
import com.atomikos.remoting.jaxrs.TransactionAwareRestClientFilter;
javax.ws.rs.client.Client paymentClient = ClientBuilder.newClient();
paymentClient.register(TransactionAwareRestClientFilter.class);
客户端拦截器会自动在客户端JVM中检测到任何活动的Atomikos事务,并通过HTTP Header将其传播到服务端,以便服务端可以加入该事务。它还从HTTP Response header中提取URL,利用这个URL向服务端发出两段提交指令。
服务器端配置
import com.atomikos.remoting.jaxrs.TransactionAwareRestContainerFilter;
import com.atomikos.remoting.twopc.AtomikosRestPort;
JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean(); //we used CXF for our demo
sf.setProvider(new TransactionAwareRestContainerFilter());
sf.setResourceProvider(new SingletonResourceProvider(new AtomikosRestPort()));
服务端的拦截器可以检测客户端的事务传播请求,并加入该事务。它还将回调URL添加到HTTP Response Header中, AtomikosRestPort侦听该URL并等待提交指令(请求)。
实验
为了更好的观察事务的实现原理,我们在客户端和服务端添加了少许Java代码以便打印HTTP Request和Response.
在客户端添加代码,打印HTTP Response
在服务端添加代码,打印HTTP Request
运行EmbeddedTransactionManagerClient,查看日志
首先查看服务端日志,你会发现在客户端发送的HTTP Request中有一个Atomikos-Propagation header,客户端利用Atomikos-Propagation Header向服务端发送了一个事务传播请求,并附带了一个事务ID,172.16.73.169.tm159341106682100001到服务端。
Atomikos-Propagation: version=2019,domain=172.16.73.169.tm,timeout=9450,serial=true,recoveryCoordinatorURI=172.16.73.169.tm159341106682100001,parent=172.16.73.169.tm159341106682100001,property.com.atomikos.icatch.jta.transaction=true
继续读服务端的日志,Atomikos在服务端创建了一个子事务inventory159341106754700004 ,并对这个子事务做了提交,查看Atomikos的帮助文档可以知道,Atomikos此时并没有真正提交事务,而是把子事务的提交延迟到和父事务一起提交。
createSubTransaction(): created new SUBTRANSACTION inventory159341106754700004 for existing transaction inventory159341106739100002 with coordinatorId inventory159341106754700005
commit() done (by application) of transaction inventory159341106754700004
回过头来检查客户端日志,会发现HTTP Response中有一个Atomikos-Extent header,其中包含一个回调uri。客户端利用这个URI回调服务端,向服务端发出预提交与提交请求。
Atomikos-Extent:[version=2019,parent=172.16.73.169.tm159341106682100001,uri=http://localhost:9001/atomikos/172.16.73.169.tm159341106682100001/inventory159341106739100003,responseCount=1,direct=true]
继续检查客户端日志,客户端发出了两个Rest回调完成了两段提交
Calling prepare on http://localhost:9001/atomikos/172.16.73.169.tm159341106682100001/inventory159341106739100003
Calling commit on http://localhost:9001/atomikos/172.16.73.169.tm159341106682100001/inventory159341106739100003
总结
Atomikos这个样例代码为我们提供一种跨服务分布式事务的参考思路。Atomikos声称他们也会提供Spring Rest interceptor,但是Atomikos的这种分布式事务方案使客户端与服务端紧耦合,影响微服务的可用性,此外从Atomikos提供的示例代码中也不能检验它能否支持集群部署,即使Atomikos支持集群部署,笔者也并不赞同这种强一致的事务实现方式。