Spring cloud Spring Cloud Stream Kafka-如何实现幂等性以支持分布式事务管理(最终一致性)

Spring cloud Spring Cloud Stream Kafka-如何实现幂等性以支持分布式事务管理(最终一致性),spring-cloud,microservices,distributed-transactions,spring-cloud-stream,spring-kafka,Spring Cloud,Microservices,Distributed Transactions,Spring Cloud Stream,Spring Kafka,我有以下典型场景: 用于购买产品的订单服务。充当分布式事务的指挥官 带有产品及其库存列表的产品服务 支付服务 Orders DB Products DB | | --------------- ---------------- ---------------- | OrderService | | ProductService | | Pa

我有以下典型场景:

  • 用于购买产品的订单服务。充当分布式事务的指挥官
  • 带有产品及其库存列表的产品服务
  • 支付服务

        Orders DB               Products DB
           |                       |
    ---------------         ----------------          ----------------
    | OrderService  |       | ProductService |        | PaymentService |
     ---------------         ----------------          ----------------
           |                       |                         |
           |                --------------------             |
           --------------- | Kafka orders topic |-------------
                           ---------------------
    
正常流量为:

  • 用户订购产品
  • 订单服务在DB中创建订单,并在Kafka主题“订单”中发布消息以预订产品(产品\保留\请求)
  • Product service在其数据库中将产品库存减少一个单位,并在“orders”中发布一条消息,表示Product_RESERVED
  • Order service获取PRODUCT_RESERVED消息并订购付款发布消息payment_REQUESTED
  • 付款服务订购付款,并通过消息PAYED进行应答
  • 订单服务读取已付款的邮件并将订单标记为已完成,从而完成交易
  • 我无法处理错误案例,例如:假设:

  • 支付服务无法对产品收费,因此它会发布消息“支付失败”
  • 订单服务对发布消息UNDO\u PRODUCT\u RESERVATION作出反应
  • Product service增加数据库中的库存以取消预订,并发布产品\u UNRESERVATION\u COMPLETED
  • 订单服务完成交易,将订单的最终状态保存为已取消\付款\失败
  • 在这个场景中,想象一下,无论出于何种原因,order service发布了一条UNDO_PRODUCT_RESERVATION消息,但没有收到PRODUCT_UNRESERVATION_COMPLETED消息,因此它会重试发布另一条UNDO_PRODUCT_RESERVATION消息

    现在,假设同一订单的两条UNDO_PRODUCT_预订消息最终到达ProductService。如果我同时处理这两个问题,最终可能会为产品设置无效库存

    在这种情况下,如何实现幂等性

    更新:

    按照Artem的指示,我现在可以检测重复的消息(通过检查消息头)并忽略它们,但可能仍存在以下情况,我不应忽略重复的消息:

  • 订单服务发送撤销产品预订
  • 产品服务获取消息并开始处理它,但在更新库存之前崩溃
  • 订单服务未收到响应,因此它会重试发送撤消产品预订
  • 产品服务知道这是一条重复的消息,但在这种情况下,它应该再次重复处理
    你能帮我想出一个方法来支持这个场景吗?我如何区分何时丢弃或重新处理该消息?

    我们使用spring integration kafka在微服务中使用kafka生成和使用消息。在我们的例子中,我们将org.springframework.messaging.Message对象发送到topics,并在从字节数组反序列化后从topics获取相同的类型。在消息实体中,有消息id、发送时间等标头值,而不是消息有效负载,它是您希望从一个微服务传输到其他微服务的实际对象。我们使用唯一的消息id值来实现幂等性。在生产者方面,您必须实现一些逻辑来确保消息的消息id在多次生成时是相同的。这实际上与您的生产逻辑有关。在我们的例子中,我们使用本地事务发布事件,这在Chris Richardson的博客中有很好的描述。通过这种方法,我们可以在生产者端用相同的消息id重新创建消息对象。在消费者方面,我们将所有已使用的消息id值持久化到数据库中,并在处理接收到的消息之前检查这些id。如果我们看到一条id在持久存储中的消息,我们只需忽略它

    在您的情况下,要实现幂等性:

    • 您应该在消息中保留唯一标识符
    • 在生产者方面,当多次生产时,必须生成相同的标识符
    • 在消费者方面,您必须检查接收到的id,以检测它是否在之前被消费
    关于更新中描述的第二种情况,

    我想你应该改变一下主意。若要实现更适合微服务体系结构的发布-订阅机制,就不应该在生产者端等待响应。在此场景中,您等待其他消息以了解消费者是否使用了该消息,如果消费者未使用该消息,则再次发送该消息

    下面的实施情况如何; 在producer端,您可以在producer中的事务中向Kafka发送消息。您应该在这里提供一种机制,仅当生产者端的事务已提交时才向kafka发送消息。这是原子性问题,我在上面给出了一个链接,展示了如何解决这个问题

    在消费者方面,您按顺序逐个轮询来自kafka主题的消息,只有当当前消息可以被消费时,您才能获得下一条消息。如果它没有被消费,你就不应该收到下一条消息。因为下一条消息可能与当前消息相关,如果您使用下一条消息,可能会破坏数据的一致性。当消息未被消费时,它不是生产者的问题。在使用者端,您应该提供重试和重播机制来使用消息

    我认为你们不应该等待制片人的回应。Kafka是一个非常智能的工具,它具有偏移量提交功能,作为消费者,您不必在轮询来自主题的消息时使用消息。如果在处理消息时遇到问题,只需不提交偏移量即可获取下一条消息

    使用上面描述的实现,您不会遇到类似“我如何区分何时应该丢弃消息或重新发送消息”这样的问题