场景:好比下单操做,下单成功以后,会发布建立订单和扣减库存消息,但扣减库存消息执行会先于建立订单消息,也就说前者执行成功以后,才能执行后者。html
不保证彻底按照顺序消费,在 MQ 层面支持消息的顺序处理开销太大,为了极少许的需求,增长总体上的复杂度得不偿失。apache
因此,仍是在应用层面处理比较好,或者业务逻辑进行处理。缓存
应用层解决方式:网络
形成消息重复的根本缘由是:网络不可达。只要经过网络交换数据,就没法避免这个问题。因此解决这个问题的办法就是绕过这个问题。那么问题就变成了:若是消费端收到两条同样的消息,应该怎样处理?异步
消费端处理消息的业务逻辑保持幂等性。分布式
保证每条消息都有惟一编号且保证消息处理成功与去重表的日志同时出现。性能
第 1 条很好理解,只要保持幂等性,无论来多少条重复消息,最后处理的结果都同样。第 2 条原理就是利用一张日志表来记录已经处理成功的消息的 ID,若是新到的消息 ID 已经在日志表中,那么就再也不处理这条消息。优化
第 1 条解决方案,很明显应该在消费端实现,不属于消息系统要实现的功能。第 2 条能够消息系统实现,也能够业务端实现。正常状况下出现重复消息的几率其实很小,若是由消息系统来实现的话,确定会对消息系统的吞吐量和高可用有影响,因此最好仍是由业务端本身处理消息重复的问题,这也是 RabbitMQ 不解决消息重复的问题的缘由。.net
RabbitMQ 不保证消息不重复,若是你的业务须要保证严格的不重复消息,须要你本身在业务端去重。设计
AMQP 定义了消费者确认机制(message ack),若是一个消费者应用崩溃掉(此时链接会断掉,broker 会得知),可是 broker 还没有得到 ack,那么消息会被从新放入队列。因此 AMQP 提供的是“至少一次交付”(at-least-once delivery),异常状况下,消息会被重复消费,此时业务要实现幂等性(重复消息处理)。
对于生产者,AMQP 定义了事务(tx transaction)来确保生产消息被 broker 接收并成功入队。TX 事务是阻塞调用,生产者需等待broker写磁盘后返回的确认,以后才能继续发送消息。事务提交失败时(如broker宕机场景),broker并不保证提交的消息所有入队。
TX 的阻塞调用使 broker 的性能很是差,RabbitMQ 使用 confirm 机制来优化生产消息的确认。Confirm 模式下,生产者能够持续发送消息,broker 将消息批量写磁盘后回复确认,生产者经过确认消息的ID来肯定哪些已发送消息被成功接收。Confirm 模式下生产者发送消息和接受确认是异步流程,生产者须要缓存未确认的消息以便出错时从新发送。
应用层解决方式:
特别说明:AMQP 协议中的事务仅仅是指生产者发送消息给 broker 这一系列流程处理的事务机制,并不包含消费端的处理流程。
原 RabbitMQ 集群:manager一、manager二、manager3 节点均为磁盘存储,manager1 为主节点,HAProxy 负载三个节点。
现 RabbitMQ 集群更新(更合理的配置):
Kafka 的设计有明确的介绍:http://kafka.apache.org/documentation.html#design。
Kafka 应对场景:消息持久化、吞吐量是第一要求、状态由客户端维护、必须是分布式的。Kafka 认为 broker 不该该阻塞生产者,高效的磁盘顺序读写可以和网络 IO 同样快,同时依赖现代 OS 文件系统特性,写入持久化文件时并不调用 flush,仅写入 OS pagecache,后续由 OS flush。
这些特性决定了 Kafka 没有作“确认机制”,而是直接将生产消息顺序写入文件、消息消费后不删除(避免文件更新),该实现充分利用了磁盘 IO,可以达到较高的吞吐量。代价是消费者要依赖 Zookeeper 记录队列消费位置、处理同步问题。没有消费确认机制,还致使了 Kafka 没法了解消费者速度,不能采用 push 模型以合理的速度向消费者推送数据,只能利用 pull 模型由消费者来拉消息(消费者承担额外的轮询开销)。
若是在 Kafka 中引入消费者确认机制,就须要 broker 维护消息消费状态,要作到高可靠就须要写文件持久化并与生产消息同步,这将急剧下降 Kafka 的性能,这种设计也极相似 RabbitMQ。若是不改变 Kafka 的实现,而是在 Kafka 和消费者之间作一层封装,仍是须要实现一套相似 RabbitMQ 的消费确认和持久化机制。
参考资料: