事务问题
从单体应用迁移到微服务架构时,不得不面临的问题之一就是事务。在单体应用时代,所有业务共享同一个数据库,一次请求操作可放置在同一个数据库事务中;在微服务架构下,这件事变得非常困难。然而事务问题不可避免,非常关键。
解决事务问题时,最先想到的解决方法通常是分布式事务。分布式事务在传统系统中应用的比较广泛,主要基于两阶段提交的方式实现。然而分布式事务在微服务架构中可行性并不高,主要基于这些考虑:
分布式事务需要事务管理器,对于不同语言平台来说,几乎没有有一致的实现来进行事务管理;
并非所有的持久化基施都提供完整ACID的事务,比如现在广泛使用的NoSQL;
分布式事务存在性能问题。
根据CAP理论,分布式系统不可兼得一致性、可用性、分区容错性(可靠性)三者,对于微服务架构来讲,我们通常会保证可用性、容错性,牺牲一部分一致性,追求最终一致性。所以对于微服务架构来说,使用分布式事务来解决事务问题无论是从成本还是收益上来看,都不划算。
对微服务系统来说解决事务问题,CQRS+Event Sourcing是更好的选择。
CQRS是命令和查询职责分离的缩写。CQRS的核心观点是,把操作分为修改状态的命令(Command),和返回数据的查询(Query),前者对应于“写”的操作,不能返回数据,后者对应于“读”的操作,不造成任何影响,由此领域模型被一分为二,分而治之。
Event Sourcing通常被翻译成事件溯源,简单的来说就是某一对象的当前状态,是由一系列的事件叠加后产生的,存储这些事件即可通过重放获得对象在任一时间节点上的状态。
通过CQRS+Event Sourcing,我们很容易获得最终一致性,例如对于一个跨系统的交易过程而言:
用户在交易微服务提交下单命令,产生领域事件 PlaceOrderEvent ,订单状态 PENDING ;
支付微服务收到领域事件进行扣款,扣款成功产生领域事件 PaidEvent ;
交易微服务收到领域事件 PaidEvent ,将订单标记为 CREATED ;
若支付微服务发现额度不足扣款失败,产生领域事件 InsufficientEvent ,交易微服务消费将订单标记为 CANCELED 。
我们只要保证领域事件能被持久化,那么即使出现网络延迟或部分系统失效,我们也能保证最终一致性。
实践上,我们利用Spring从4.2版本开始支持的自定义应用事件机制将本地事务和事件投递结合起来进行:
领域内业务过程会产生领域事件,通过Spring的应用事件机制进行应用内投递;
监听相应的领域事件,在事务提交前投递至消息队列;
以上全都没有异常发生,则本地事务提交,如果出现异常,本地事务回滚。