ITPub博客

首页 > > > 微服务的分布式事务

微服务的分布式事务

作者:markriver 时间:2020-07-02 15:37:23 0 删除 编辑

1.简介

在本文中,我们将讨论跨微服务实现事务的方式。

我们还将查看分布式微服务场景中的一些事务替代方案。

2.避免跨微服务的事务

分布式事务(distributed transaction)是一个非常复杂的过程,有许多可能失败的部件。 此外,如果这些部件在不同的机器上运行,甚至在不同的数据中心运行,则提交事务的过程可能会变得非常长且不可靠。

这可能严重影响用户体验和整体系统带宽。 因此,解决分布式事务问题的最佳方法之一是完全避免它们。

2.1 需要事务的架构示例

通常,一个微服务被升级成独立且有用的形式。 它应该能够解决一些原子业务任务(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,我们现在必须跨微服务实现事务。

2.2 不使用事务的替代方法

我们可以改变我们的微服务架构并从User实体中删除字段lastMessageTime。

然后我们可以在用户配置文件中显示此时间,方法是向消息(message)微服务发出单独的请求,并为该用户的所有消息查找最大的messageTimestamp值(单独查询该用户最近一次发送消息的时间)。

也许,如果消息微服务处于高负载或者离线,我们将无法在其配置文件中显示用户的最后一条消息的时间。

但这可能比仅仅因为用户微服务没有及时响应而无法提交分布式事务来保存消息更为可接受。

当我们必须跨多个微服务实现业务流程时,当然有更复杂的场景,我们不希望允许这些微服务之间的不一致。

3. 两阶段提交协议(Two-Phase Commit Protocol)

两阶段提交协议(或2PC)是一种跨不同软件组件(多个数据库,消息队列等)实现事务的机制。

3.1. 2PC的架构

分布式事务的重要参与者之一是事务协调器(transaction coordinator)。分布式事务包括两个步骤:

  • 准备阶段 — 在此阶段,事务的所有参与者都准备提交并通知协调器他们已准备好完成交易
  • 提交或回滚阶段 — 在此阶段,事务协调器向所有参与者发出提交或回滚命令

2PC的问题在于它与单个微服务的操作时间相比非常慢。

协调微服务之间的事务,即使它们位于同一网络上,也可以真正降低系统速度,因此这种方法通常不会用于高负载情况。

3.2. XA 标准

XA标准是用于跨支持资源进行2PC分布式事务的规范。 任何符合JTA标准的应用程序服务器(JBoss,GlassFish等)都开箱即用的支持它。

参与分布式事务的资源可以是两个不同微服务的两个数据库。

但是,要利用此机制,必须将资源部署到单个JTA平台。 对于微服务架构而言,这并不总是可行的。

3.3. REST-AT 标准草案

另一个提议的标准是REST-AT,它已经由RedHat进行了一些开发,但仍然没有退出草案阶段。 然而,WildFly应用服务器开箱即用的支持它。

此标准允许将应用程序服务器用作具有特定REST API的事务协调器,以创建和加入分布式事务。

希望参与两阶段事务的RESTful Web服务还必须支持特定的REST API。

遗憾的是,要将分布式事务桥接到微服务的本地资源,我们仍然必须将这些资源部署到单个JTA平台,或者解决自己编写此桥的非平凡任务。

4.最终的一致性和补偿

到目前为止,处理跨微服务一致性的最可行模型之一是最终一致性。

此模型不会跨微服务强制执行分布式ACID事务。 相反,它建议使用一些机制来确保系统在未来的某个时刻最终保持一致。

4.1 最终一致性的案例

例如,假设我们需要解决以下任务:

  • 注册用户个人资料
  • 做一些自动后台检查得到用户实际可以访问的系统

第二项任务是确保(保障),例如,由于某种原因,该用户未被禁止访问我们的服务器。

但这可能需要时间,我们希望将其提取到单独的微服务中。让用户等待这么久才知道她已成功注册是不合理的。

解决问题的一种方法是采用消息驱动的方法,包括补偿。让我们考虑以下架构:

  • 负责注册用户配置文件的用户(user)微服务
  • 验证(validation)微服务的任务是进行后台检查
  • 支持持久化队列消息的消息平台

消息平台可以确保微服务发送的消息持久化。然后如果接收器当前不可用,它们将在稍后发送

4.2 快乐的情景

在这个架构中,一个快乐的场景是:

  • 用户(user)微服务注册一个用户,将有关他的信息保存在本地数据库中
  • 用户(user)微服务用一个标志标记该用户。它可能表示此用户尚未经过验证,无法访问完整的系统功能
  • 向用户发送注册确认,并发出警告,告知不能访问系统的所有功能
  • 用户(user)微服务向验证(validation)微服务发送消息以进行用户的后台检查
  • 验证(validation)微服务运行后台检查并使用检查结果向用户微服务发送消息
    • 如果结果是肯定的(positive),则用户(user)微服务解锁用户
    • 如果结果为负(negative),则用户(user)微服务将删除用户帐户

在我们完成所有这些步骤之后,系统应处于一致状态。但是,在一段时间内,用户实体似乎处于不完整状态。

当用户微服务删除无效帐户时,最后一步是补偿阶段。

4.3 失败场景

现在让我们考虑一些失败情况:

  • 如果验证(validation)微服务不可访问,那么具有持久队列功能的消息平台可确保验证(validation)微服务稍后会收到此消息
  • 假设消息平台失败,则用户微服务尝试稍后再次发送消息,例如,通过对尚未验证的所有用户的预定批处理
  • 如果验证(validation)微服务收到消息,验证用户但由于消息平台故障而无法发送回答,则验证微服务也会在稍后的某个时间重试发送消息
  • 如果其中一条消息丢失或发生其他一些故障,用户(user)微服务会通过预定的批处理找到所有未经验证的用户,并再次发送验证请求

即使一些消息是多次发出的,这也不会影响微服务数据库中数据的一致性。

通过仔细考虑所有可能的故障情况,我们可以确保我们的系统满足最终一致性的条件。与此同时,我们不需要处理昂贵的分布式事务。

但我们必须意识到确保最终的一致性是一项复杂的任务。对于所有情况,它没有单一的解决方案。


5. 结论

在本文中,我们讨论了跨微服务实现事务的一些机制。

而且,我们还探索了做这种事务的一些替代方案。

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/818/viewspace-3147474/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2020-07-03

  • 博文量
    502
  • 访问量
    18493