美文网首页
29. 消息幂等:如何保证消息不被重复消费

29. 消息幂等:如何保证消息不被重复消费

作者: 木叶苍蓝 | 来源:发表于2023-07-13 14:27 被阅读0次

    应用的幂等是在分布式系统设计时必须要考虑的一个方面。在消息队列应用中,如何处理因为重复投递等原因导致的幂等问题。

    对业务幂等的理解

    • 幂等是业务的一个特性
      幂等问题体现在对于不满足幂等性的业务,在消息重复消费,或者远程调用失败重试时出现的数据不一致,业务数据错乱等现象。
    • 幂等函数
      对一个函数或者方法,使用相同的参数执行多次,数据结果是一致的。

    HTTP 协议中定义了交互的不同方法
    比如 GET和POST以及 PUT , DELETE 等
    其中 GET, DELETE 等方法都是幂等的,而 POST 方法不是。

    注意:业务上的幂等指的是操作不影响资源本身,并不是每次读取的结构都保证一致

    比如通过 GET 接口查询一条订单记录,在多次查询的时间段内订单状态可能会有新的更新而发生变化,查询到的数据可能不同,但是读接口本身仍然是一个幂等的操作。

    在业务开发中对数据的操作主要是 CRUD
    即在做数据处理时的 Create,Read,Update,Delete 这几种操作
    Create 操作不是幂等的
    Update 操作可能幂等也可能不幂等

    UPDATE order SET status=1 WHERE id = 100;   # 幂等操作
    
    UPDATE order SET price  = price + 1 WHERE id = 100;  # 不是幂等操作
    

    各类中间件对幂等性的处理

    使用 binlog 分发进行数据同步,如果数据库更新消息被多次消费可能导致数据的不一致。

    远程服务调用的幂等问题

    远程服务调用出现失败,一般都是通过配置重试,保证请求调用成功率,提高整体服务的可用性
    Apache Dubbo 支持多种集群容错的方式,可以针对业务特性,配置不同的失败重试机制
    包括 Failover 失败自动切换,Failsafe 失败安全,Failfast 快速失败等

    • 在 Failover 下,失败会重试两次
    • 在 Failfast 下,失败则不会重试,直接抛出异常

    Dubbo 的容错机制考虑了多种业务场景的需求
    根据不同的业务场景,可以选择不同的容错机制,进而有不同的重试策略,保证业务正确性。

    消息消费中的重试问题

    消息队列的消息发送重试和微服务中的失败调用重试
    都是通过重试的方式,解决网络抖动,传输不稳定等导致的偶发调用失败
    需要从中间件和业务的不同层面,来保证服务调用的幂等性

    消息投递的几种语义

    • At most once
      最多会被送达一次,消息可能会丢,但不会重复传输,一般用于对消息可靠性没有太高要求的场景。比如一些允许数据丢失的日志报表,监控信息等
    • At least once
      至少会被送达一次,消息绝不会丢,但可能会出现重复传输,大部分消息队列都支持到这个级别,应用最广泛。
    • Exacly once
      每条消息肯定会被传输一次且仅传输一次,并且保证送达
      绝对的 Exactly once 级别很难实现,通过的 Exactly once 方案几乎不可能存在
      可以参数分布式系统的 【FLP不可能定理】

    在消费端也可以定义类似的消费语义
    比如消费端保证最多被消费异常,至少被消费一次
    这两种语义可以认为是同意给级别的两种描述

    不同消息队列支持的投递方式

    RocketMQ 支持 At least once 的投递语义
    通过消费端消费的 ACK 机制来实现:
    在消息消费过程中,消费端在消息消费完成后,返回 ACK
    如果消息已经 pull 到本地,但还没消费,则不会返回 ACK 响应

    在业务上应用 RocketMQ 时,可以根据不同的业务场景实现其他级别的投递语义,比如最多送达一次等。

    业务上如何处理幂等

    消息消费的幂等本质上是已给系统设计的问题
    消息队列是为了实现系统目标而引入的手段之一
    并且分布式消息队列天然存在消费时序,消息失败重发等问题

    天然幂等不需要额外设计

    有部分业务是天然幂等的
    这部分业务,允许重复调用,即允许重试
    在配置消息队列时,可以通过合理的重试,来提高请求的成功率

    利用数据库进行去重

    要根据订单流转的消息在数据库中写一张订单 Log 表
    可以把订单 ID 和修改时间戳做一个唯一索引进行约束
    当消费端消费消息出现重复投递时,会多次去订单 Log 表中进行写入
    除第一条之外,后面的都会失败

    设置全局唯一消息 ID 或者任务 ID

    在消息投递时,给每个业务消息附加一个唯一的消息 ID
    就可以在消费端利用类似分布式锁的机制,实现唯一性的消费
    消息被消费后,在缓存中设置一个 Key 对应的唯一 ID,代表数据已经被消费
    当其他的消费端去消费时,可以根据这天记录,来判断是否已经处理过

    相关文章

      网友评论

          本文标题:29. 消息幂等:如何保证消息不被重复消费

          本文链接:https://www.haomeiwen.com/subject/iwfuudtx.html