在本文中,我们将讨论跨微服务实现事务的方式。
我们还将查看分布式微服务场景中的一些事务替代方案。
分布式事务(distributed transaction)是一个非常复杂的过程,有许多可能失败的部件。 此外,如果这些部件在不同的机器上运行,甚至在不同的数据中心运行,则提交事务的过程可能会变得非常长且不可靠。
这可能严重影响用户体验和整体系统带宽。 因此,解决分布式事务问题的最佳方法之一是完全避免它们。
通常,一个微服务被升级成独立且有用的形式。 它应该能够解决一些原子业务任务(atomic business task)。
如果我们可以在这样的微服务中拆分我们的系统,那么我们很可能根本不需要在它们之间实现事务。
例如,让我们考虑一个在用户之间广播消息的系统。
用户(User)微服务将关注用户配置(User Profile)文件(创建新用户,编辑配置文件数据等)以及以下基础领域类:
@Entity
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@Basic
private String name;
@Basic
private String surname;
@Basic
private Instant lastMessageTime;
}消息(message)微服务将关注广播。 它封装了实体Message及其周围的所有内容:
@Entity
public class Message implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@Basic
private long userId;
@Basic
private String contents;
@Basic
private Instant messageTimestamp;
}每个微服务都有自己的数据库。 请注意,我们没有从实体Message引用实体User,因为无法从消息(message)微服务访问用户类。 我们仅通过id引用用户。
现在,User实体包含lastMessageTime字段,因为我们希望在其配置文件中显示有关上次用户活跃时间的信息。
但是,要向用户添加新消息并更新她的lastMessageTime,我们现在必须跨微服务实现事务。
我们可以改变我们的微服务架构并从User实体中删除字段lastMessageTime。
然后我们可以在用户配置文件中显示此时间,方法是向消息(message)微服务发出单独的请求,并为该用户的所有消息查找最大的messageTimestamp值(单独查询该用户最近一次发送消息的时间)。
也许,如果消息微服务处于高负载或者离线,我们将无法在其配置文件中显示用户的最后一条消息的时间。
但这可能比仅仅因为用户微服务没有及时响应而无法提交分布式事务来保存消息更为可接受。
当我们必须跨多个微服务实现业务流程时,当然有更复杂的场景,我们不希望允许这些微服务之间的不一致。
两阶段提交协议(或2PC)是一种跨不同软件组件(多个数据库,消息队列等)实现事务的机制。
分布式事务的重要参与者之一是事务协调器(transaction coordinator)。分布式事务包括两个步骤:
2PC的问题在于它与单个微服务的操作时间相比非常慢。
协调微服务之间的事务,即使它们位于同一网络上,也可以真正降低系统速度,因此这种方法通常不会用于高负载情况。
XA标准是用于跨支持资源进行2PC分布式事务的规范。 任何符合JTA标准的应用程序服务器(JBoss,GlassFish等)都开箱即用的支持它。
参与分布式事务的资源可以是两个不同微服务的两个数据库。
但是,要利用此机制,必须将资源部署到单个JTA平台。 对于微服务架构而言,这并不总是可行的。
另一个提议的标准是REST-AT,它已经由RedHat进行了一些开发,但仍然没有退出草案阶段。 然而,WildFly应用服务器开箱即用的支持它。
此标准允许将应用程序服务器用作具有特定REST API的事务协调器,以创建和加入分布式事务。
希望参与两阶段事务的RESTful Web服务还必须支持特定的REST API。
遗憾的是,要将分布式事务桥接到微服务的本地资源,我们仍然必须将这些资源部署到单个JTA平台,或者解决自己编写此桥的非平凡任务。
到目前为止,处理跨微服务一致性的最可行模型之一是最终一致性。
此模型不会跨微服务强制执行分布式ACID事务。 相反,它建议使用一些机制来确保系统在未来的某个时刻最终保持一致。
例如,假设我们需要解决以下任务:
第二项任务是确保(保障),例如,由于某种原因,该用户未被禁止访问我们的服务器。
但这可能需要时间,我们希望将其提取到单独的微服务中。让用户等待这么久才知道她已成功注册是不合理的。
解决问题的一种方法是采用消息驱动的方法,包括补偿。让我们考虑以下架构:
消息平台可以确保微服务发送的消息持久化。然后如果接收器当前不可用,它们将在稍后发送
在这个架构中,一个快乐的场景是:
在我们完成所有这些步骤之后,系统应处于一致状态。但是,在一段时间内,用户实体似乎处于不完整状态。
当用户微服务删除无效帐户时,最后一步是补偿阶段。
现在让我们考虑一些失败情况:
即使一些消息是多次发出的,这也不会影响微服务数据库中数据的一致性。
通过仔细考虑所有可能的故障情况,我们可以确保我们的系统满足最终一致性的条件。与此同时,我们不需要处理昂贵的分布式事务。
但我们必须意识到确保最终的一致性是一项复杂的任务。对于所有情况,它没有单一的解决方案。
在本文中,我们讨论了跨微服务实现事务的一些机制。
而且,我们还探索了做这种事务的一些替代方案。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/818/viewspace-3147474/ ,如需转载,请注明出处,否则将追究法律责任。