V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
7911364440
V2EX  ›  程序员

问个分布式事务的问题

  •  
  •   7911364440 · 310 天前 · 1718 次点击
    这是一个创建于 310 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    目前想到的方案:

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

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

    需要全局一致性的数据,全部放在关系型数据库里处理,此数据库用于全局专门处理这类数据。这种设计设计,性能、备份、HA 都很好做。
    xiaohundun
        22
    xiaohundun  
       309 天前
    是不是可以参考 seata 的 TCC 模式?
    candafromcn
        23
    candafromcn  
       309 天前
    RocketMQ 不能像 kafka 一样分组消费吗,每一个需要同步的数据源认为是一个消费者,保证每个消费者 at least once 的消费就行了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5390 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 33ms · UTC 06:51 · PVG 14:51 · LAX 23:51 · JFK 02:51
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.