• 请不要在回答技术问题时复制粘贴 AI 生成的内容
7911364440
V2EX  ›  程序员

问个分布式事务的问题

  •  
  •   7911364440 · May 24, 2023 · 3537 views
    This topic created in 1262 days ago, the information mentioned may be changed or developed.

    业务流程大概是这样,从 RocketMQ 消费消息,将收到的数据转发给多个数据源(可能是 MySQL 、Oracle 、Kafka 等)。
    问题是如何保证一条消息最终成功发送给所有客户端?假设写 MySQL 成功了,发送 Kafka 时失败了,要怎么重发?

    目前想到的方案:

    1. 将发送失败的消息保存到本地数据库,同时记录目标客户端,再通过定时任务重发。
    2. 跟 1 类似,将失败的消息发送到消息队列中的某个特定的 topic ,客户端消费这个 topic 实现重发。
    23 replies  •  2023-05-25 11:43:31 +08:00
    lhcnic
    lhcnic
    May 24, 2023
    1
    RocketMQ 不是自带消费重试的吗?
    sujin190
    sujin190
    May 24, 2023
    2
    或许可以更粗暴点,从 RocketMQ 收到消息通过新的交换机再次发送 RocketMQ 各个不同的数据源队列去,然后各数据源各自消费者,反正不成功消息不会从队列消息,自动就有重试
    wangpugod2003
    wangpugod2003
    May 24, 2023
    3
    你这个需求,是保证整个流程,做到消息的 exactly once 处理.
    首先需要确认发出去了,每个模块收到了回 ack, 没有就 retry 该模块; 这样保证 > 1; 注意 retry 多次后如果没收到,再到定时任务中设置一段时间后重发,如果再没收到可以放到 dead message queue 中人工处理;
    然后每个对端都要 idempotent ,确保收到的消息(maybe > 1)但是只处理 1 次。一般是需要一个 transaction ID 。
    xhinliang
    xhinliang
    May 24, 2023
    4
    第一种方法,用事务消息

    第二种,用 MySQL 记录下所有的客户端的发送状态
    1. 在准备发送前,写入 MySQL ,初始化所有客户端的写入状态为 INIT
    2. 给每一个客户端发送,发送成功则修改 MySQL 对应客户端的写入状态为 SUCC ,失败则修改为 FAILED
    3. 异步进程扫描所有状态为 INIT 或者 FAILED 的记录,重发
    xuanbg
    xuanbg
    May 24, 2023
    5
    消息的消费者如果消费不成功,就把接收到的消息发送到延时队列。延时队列绑定正常的队列,这样过期时间到了就好自动转移到正常的队列里面,就能再次消费。如此循环不息,总有成功的时候。
    7911364440
    7911364440
    OP
    May 24, 2023
    6
    @lhcnic 问题是需要保证所有客户端都成功接收。如果写 MySQL 成功了,发送 Kafka 时失败了,只需要给 Kafka 重发,不能给所有客户端都重发一遍,会有重复数据。
    7911364440
    7911364440
    OP
    May 24, 2023
    7
    @sujin190 逻辑上应该是可行的,实现起来也很简单,但是会导致一条消息会出现在多个 topic 中,客户端越多冗余的数据就越多,太占用空间了,这个业务每天上千万的数据量,还有图片之类的数据,每条消息都很大。
    wu00
    wu00
    May 24, 2023
    8
    如果你不能改变数据来源(RocketMQ)
    建议在[RocketMQ] 到 [写入多个数据源] 之间加一层 MQ 或者数据库,让整个业务变成多个独立的事务单元
    比如:接收到 RocketMQ 的消息,写入到 MQ ,有几个数据源写几条消息,再加一个消费端专门消费 MQ 中的消息分别写入各自的数据源。
    ConfusedBiscuit
    ConfusedBiscuit
    May 24, 2023
    9
    分布式事务,自己想方案容易绕晕,可以找一些成熟的方案,比如 TCC 模型就比较简单易懂。收到 RocketMQ 消息后,自己按照 TCC 模型封装每个下游( MySQL 、Oracle 之类的比较简单,Kafka 之类的可能需要考虑用一些新特性)
    dqzcwxb
    dqzcwxb
    May 24, 2023
    10
    你这个思路没有问题,但是注意的是要保证重发时的幂等
    Dream95
    Dream95
    May 24, 2023
    11
    @7911364440 RocketMQ 本来就会存在重复消费问题,需要在消费者端去重的
    Dream95
    Dream95
    May 24, 2023
    12
    @7911364440 可以用不同的消费组实现
    notwaste
    notwaste
    May 24, 2023
    13
    做好做好监控,然后人工肉偿
    silypie
    silypie
    May 24, 2023
    14
    一般都是自动重试,多次失败后人工处理吧
    silypie
    silypie
    May 24, 2023
    15
    保证最终一致就行
    coderxy
    coderxy
    May 24, 2023
    16
    要想实现分布式事务,最基本的一个点就是下游要自己保证幂等, 否则无从谈起。
    jorneyr
    jorneyr
    May 24, 2023
    17
    这个是数据一致性吧,不算分布式事务,直接多次轮询进行补偿把没完成的给完成。
    如果是分布式事务,A 成功,B 失败,需要把 A 的操作给回滚。
    LeeSeoung
    LeeSeoung
    May 24, 2023
    18
    有个极端情况要考虑哈,比如你发给某个消费端超时了,你认为是失败的,但是实际消费端是成功的,这个时候你做重试,要保证消费端的幂等。
    lvxiaomao
    lvxiaomao
    May 24, 2023
    19
    感觉是没有问题的, 只要消费短幂等就好
    xyjincan
    xyjincan
    May 25, 2023
    20
    kafka 好呀,不同类型客户消费,使用不同分组就行,共享一个消息队列
    documentzhangx66
    documentzhangx66
    May 25, 2023
    21
    最省事的办法,是反向操作。

    需要全局一致性的数据,全部放在关系型数据库里处理,此数据库用于全局专门处理这类数据。这种设计设计,性能、备份、HA 都很好做。
    xiaohundun
    xiaohundun
    May 25, 2023
    22
    是不是可以参考 seata 的 TCC 模式?
    candafromcn
    candafromcn
    May 25, 2023
    23
    RocketMQ 不能像 kafka 一样分组消费吗,每一个需要同步的数据源认为是一个消费者,保证每个消费者 at least once 的消费就行了
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   2261 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 54ms · UTC 12:01 · PVG 20:01 · LAX 04:01 · JFK 07:01
    ♥ Do have faith in what you're doing.