概览消息队列篇〇:为什么需要消息队列

极客时间《消息队列高手课》笔记 概览消息队列篇〇:为什么需要消息队列 概览消息队列篇一:不同的mq的消息模型 概览消息队列篇二:如何确保消息不丢失 概览消息队列篇三:重复消息的处理 1. 异步处理 对于一个秒杀系统,需要解决的核心问题是,如何利用有限的服务器资源,尽可能多地处理短时间内的海量请求。 处理一个秒杀请求包含了很多步骤:如风险控制、库存锁定、生成订单、短信通知、更新通知数据等。如果没有任何优化,正常的处理流程是:App 将请求发送给网关,依次调用上述 5 个流程,然后将结果返回给 APP。 但上述5个步骤中,只需要风险控制和库存锁定这两步就可以决定秒杀是否成功,对于后续的生成订单、短信通知和更新统计数据等步骤,并不一定要在秒杀请求中处理完成。 所以当服务端完成前面 2 个步骤,确定本次请求的秒杀结果后,就可以马上给用户返回响应,然后把请求的数据放入消息队列中,由消息队列异步地进行后续的操作。 处理一个秒杀请求,从 5 个步骤减少为 2 个步骤,这样不仅响应速度更快,并且在秒杀期间,我们可以把大量的服务器资源用来处理秒杀请求。秒杀结束后再把资源用于处理后面的步骤,充分利用有限的服务器资源处理更多的秒杀请求。 可以看到,在这个场景中,消息队列被用于实现服务的异步处理。 这样做的好处是: 可以更快地返回结果; 减少等待,自然实现了步骤之间的并发,提升系统总体的性能。 2. 流量控制 在用消息队列实现了部分工作的异步处理后,我们还需要考虑如何避免过多的请求压垮我们的秒杀系统。 一个设计健壮的程序有自我保护的能力,也就是说,它应该可以在海量的请求下,还能在自身能力范围内尽可能多地处理请求,拒绝处理不了的请求并且保证自身运行正常。 我们可以使用消息队列隔离网关和后端服务,以达到流量控制和保护后端服务的目的。 加入消息队列后,整个秒杀流程变为: 网关在收到请求后,将请求放入请求消息队列; 后端服务从请求消息队列中获取 APP 请求,完成后续秒杀处理过程,然后返回结果。 这种设计的优点是:能根据下游的处理能力自动调节流量,达到“削峰填谷”的作用。但这样做同样是有代价的: 增加了系统调用链环节,导致总体的响应时延变长。 上下游系统都要将同步调用改为异步消息,增加了系统的复杂度。 3. 服务解耦 对于一个电商系统来说,当一个新订单创建时: 支付系统需要发起支付流程; 风控系统需要审核订单的合法性; 客服系统需要给用户发短信告知用户; 经营分析系统需要更新统计数据; …… 这些订单下游的系统都需要实时获得订单数据。随着业务不断发展,这些订单下游系统不断的增加,不断变化,并且每个系统可能只需要订单数据的一个子集,负责订单服务的开发团队不得不花费很大的精力,应对不断增加变化的下游系统,不停地修改调试订单系统与这些下游系统的接口。任何一个下游系统接口变更,都需要订单模块重新进行一次上线,对于一个电商的核心服务来说,这几乎是不可接受的。 所有的电商都选择用消息队列来解决类似的系统耦合过于紧密的问题。引入消息队列后,订单服务在订单变化时发送一条消息到消息队列的一个主题 Order 中,所有下游系统都订阅主题 Order,这样每个下游系统都可以获得一份实时完整的订单数据。无论增加、减少下游系统或是下游系统需求如何变化,订单服务都无需做任何更改,实现了订单服务与下游服务的解耦。 其他 作为发布 / 订阅系统实现一个微服务级系统间的观察者模式; 连接流计算任务和数据; 用于将消息广播给大量接收者。 消息队列带来的一些问题 引入消息队列带来的延迟问题; 增加了系统的复杂度; 可能产生数据不一致的问题。

July 24, 2022 · 1 min · 李昌

概览消息队列篇三:重复消息的处理

极客时间《消息队列高手课》笔记 概览消息队列篇〇:为什么需要消息队列 概览消息队列篇一:不同的mq的消息模型 概览消息队列篇二:如何确保消息不丢失 概览消息队列篇三:重复消息的处理 消息队列本身可以保证消息不重复吗 在 MQTT 协议中,给出了三种传递消息时能够提供的服务质量标准,这三种服务质量从低到高依次是: At most once: 至多一次。消息在传递时,最多会被送达一次。换一个说法就是,没什么消息可靠性保证,允许丢消息。一般都是一些对消息可靠性要求不太高的监控场景使用,比如每分钟上报一次机房温度数据,可以接受数据少量丢失。 At least once: 至少一次。消息在传递时,至少会被送达一次。也就是说,不允许丢消息,但是允许有少量重复消息出现。 Exactly once:恰好一次。消息在传递时,只会被送达一次,不允许丢失也不允许重复,这个是最高的等级。 这个服务质量标准不仅适用于 MQTT,对所有的消息队列都是适用的。我们现在常用的绝大部分消息队列提供的服务质量都是 At least once,包括 RocketMQ、RabbitMQ 和 Kafka 都是这样。 也就是说,消息队列很难保证消息不重复 用幂等性解决重复消息问题 一般解决重复消息的办法是,在消费端,让我们消费消息的操作具备幂等性。 一个幂等操作的特点是,其任意多次执行所产生的影响均与一次执行的影响相同。 一个幂等的方法,使用同样的参数,对它进行多次调用和一次调用,对系统产生的影响是一样的。所以,对于幂等的方法,不用担心重复执行会对系统造成任何改变。 如果我们系统消费消息的业务逻辑具备幂等性,那就不用担心消息重复的问题了,因为同一条消息,消费一次和消费多次对系统的影响是完全一样的。也就可以认为,消费多次等于消费一次。从对系统的影响结果来说:At least once + 幂等消费 = Exactly once。 常用设计幂等操作的方法 利用数据库的唯一约束实现幂等 对于一个转账操作,我们可以设置对于每一个转账单只能进行一次转账操作,这样除第一次操作外其他重复操作都会失败。 基于这个思路,不光是可以使用关系型数据库,只要是支持类似“INSERT IF NOT EXIST”语义的存储类系统都可以用于实现幂等,比如,还可以用 Redis 的 SETNX 命令来替代数据库中的唯一约束,来实现幂等消费。 为更新的数据设置前置条件 如果满足条件就更新数据,否则拒绝更新数据,在更新数据的时候,同时变更前置条件中需要判断的数据。这样,重复执行这个操作时,由于第一次更新数据的时候已经变更了前置条件中需要判断的数据,不满足前置条件,则不会重复执行更新数据操作。 记录并检查操作 记录并检查操作,也称为“Token 机制或者 GUID(全局唯一 ID)机制”,实现的思路特别简单:在执行数据更新操作之前,先检查一下是否执行过这个更新操作。 具体的实现方法是,在发送消息时,给每条消息指定一个全局唯一的 ID,消费时,先根据这个 ID 检查这条消息是否有被消费过,如果没有消费过,才更新数据,然后将消费状态置为已消费。

July 24, 2022 · 1 min · 李昌

概览消息队列篇二:如何确保消息不丢失

极客时间《消息队列高手课》笔记 概览消息队列篇〇:为什么需要消息队列 概览消息队列篇一:不同的mq的消息模型 概览消息队列篇二:如何确保消息不丢失 概览消息队列篇三:重复消息的处理 检测消息丢失的方法 可以使用类似分布式链路追踪系统来追踪每一条信息。 还可以利用消息队列的有序性来验证是否有消息丢失。 在 Producer 端,我们给每个发出的消息附加一个连续递增的序号,然后在 Consumer 端来检查这个序号的连续性 如果没有消息丢失,Consumer 收到消息的序号必然是连续递增的,或者说收到的消息,其中的序号必然是上一条消息的序号 +1。如果检测到序号不连续,那就是丢消息了。 大多数消息队列的客户端都支持拦截器机制,你可以利用这个拦截器机制,在 Producer 发送消息之前的拦截器中将序号注入到消息中,在 Consumer 收到消息的拦截器中检测序号的连续性,这样实现的好处是消息检测的代码不会侵入到你的业务代码中,待你的系统稳定后,也方便将这部分检测的逻辑关闭或者删除。 确保消息可靠传递 一条消息从生产到消费完成这个过程,可以划分三个阶段, 生产阶段: 在这个阶段,从消息在 Producer 创建出来,经过网络传输发送到 Broker(mq服务端)端。 存储阶段: 在这个阶段,消息在 Broker 端存储,如果是集群,消息会在这个阶段被复制到其他的副本上。 消费阶段: 在这个阶段,Consumer 从 Broker 上拉取消息,经过网络传输发送到 Consumer 上。 下面就如何确保消息可靠传递分别对三个阶段进行分析 生产阶段 在生产阶段,消息队列通过最常用的请求确认机制,来保证消息的可靠传递:当你的代码调用发消息方法时,消息队列的客户端会把消息发送到 Broker,Broker 收到消息后,会给客户端返回一个确认响应,表明消息已经收到了。客户端收到响应后,完成了一次正常消息的发送。 只要 Producer 收到了 Broker 的确认响应,就可以保证消息在生产阶段不会丢失。有些消息队列在长时间没收到发送确认响应后,会自动重试,如果重试再失败,就会以返回值或者异常的方式告知用户。 存储阶段 在存储阶段正常情况下,只要 Broker 在正常运行,就不会出现丢失消息的问题,但是如果 Broker 出现了故障,比如进程死掉了或者服务器宕机了,还是可能会丢失消息的。 如果对消息的可靠性要求非常高,可以通过配置 Broker 参数来避免因为宕机丢消息。 对于单个节点的 Broker,需要配置 Broker 参数,在收到消息后,将消息写入磁盘后再给 Producer 返回确认响应,这样即使发生宕机,由于消息已经被写入磁盘,就不会丢失消息,恢复后还可以继续消费。例如,在 RocketMQ 中,需要将刷盘方式 flushDiskType 配置为 SYNC_FLUSH 同步刷盘。...

July 24, 2022 · 1 min · 李昌

概览消息队列篇一:不同的mq的消息模型

极客时间《消息队列高手课》笔记 概览消息队列篇〇:为什么需要消息队列 概览消息队列篇一:不同的mq的消息模型 概览消息队列篇二:如何确保消息不丢失 概览消息队列篇三:重复消息的处理 1. 主题和队列 最基本的队列模型,是按照“队列”的数据结构来设计的,即先进先出。生产者发送消息(入队),消费者获取消息(出队)。 当有多个生产者往同一个队列中发送消息,则这个队列中可以消费到的消息,就是这些生产者生产的所有消息的合集。消息的顺序就是这些生产者发送消息的自然顺序。如果有多个消费者接收同一个队列的消息,这些消费者之间实际上是竞争的关系,每个消费者只能收到队列中的一部分消息,也就是说任何一条消息只能被其中的一个消费者收到。 这时候问题就出现了,如果一份消息需要被多个消费者消费,比如,对于一个订单,它需要被风控系统、支付系统等系统消费,显然上述的模型不能满足这个需求。这时候一个可行的解决方案是:为每个消费者创建一个单独的队列,让生产者发送多份。 但显然这样会浪费较多的资源,同一个消息复制了多份。更重要的是,生产者必须知道有多少个消费者。为每个消费者单独发送一份消息,这实际上违背了消息队列“解耦”这个设计初衷。 为了解决这个问题,演化出“发布-订阅”(pub-sub)模型。 在发布-订阅模型中,消息的发送方称为发布者(Publisher),消息的接收方称为订阅者(Subscriber),服务端存放消息的容器称为主题(Topic)。发布者将消息发送到主题中,订阅者在接收消息之前需要先“订阅主题”。“订阅”在这里既是一个动作,同时还可以认为是主题在消费时的一个逻辑副本,每份订阅中,订阅者都可以接收到主题的所有消息。 当发布-订阅模型中只有一个订阅者时,那它和队列模型就基本上是一样的了。也就是说,发布 - 订阅模型在功能层面上是可以兼容队列模型的。 2. 常见mq的消息模型 2.1 RabbitMQ的消息模型 RabbitMQ是少数仍坚持使用队列模型的产品之一。 那么RabbitMQ是如何解决多个消费者的问题呢? RabbitMQ有一个Exchange模块,在 RabbitMQ 中,Exchange 位于生产者和队列之间,生产者并不关心将消息发送给哪个队列,而是将消息发送给 Exchange,由 Exchange 上配置的策略来决定将消息投递到哪些队列中。 同一份消息如果需要被多个消费者来消费,需要配置 Exchange 将消息发送到多个队列,每个队列中都存放一份完整的消息数据,可以为一个消费者提供消费服务。 2.2 RocketMQ的消息模型 RocketMQ 使用的消息模型是标准的发布-订阅模型 但是,在 RocketMQ 也有队列(Queue)这个概念,并且队列在 RocketMQ 中是一个非常重要的概念,要了解队列在RocketMQ中的作用,我们首先要了解消息确认机制及其带来的问题 为了确保消息不会在传递过程中由于网络或服务器故障丢失,消息队列一般采用“请求-确认”机制来确认消息的成功消费。消费者在成功消费一条消息,完成自己的业务逻辑后,会发送确认給消息队列。消息队列只有收到确认后,才认为一条消息被成功消费。 这种机制很好地保证了消息传递过程中的可靠性,但是其带来另一个问题:在某一条消息被成功消费之前,下一条消息是不能被消费的,否则就会出现消息空洞,违背了有序性这个原则。 也就是说,每个主题在任意时刻,至多只能有一个消费者实例在进行消费,那就没法通过水平扩展消费者的数量来提升消费端总体的消费性能。 为了解决这个问题,RocketMQ在主题下增加了队列的概念,每个主题包含多个队列,通过多个队列来实现多实例并行生产和消费。 RocketMQ 只在队列上保证消息的有序性,主题层面是无法保证消息的严格顺序的。 RocketMQ 中,订阅者的概念是通过消费组(Consumer Group)来体现的。每个消费组都消费主题中一份完整的消息,不同消费组之间消费进度彼此不受影响,也就是说,一条消息被 Consumer Group1 消费过,也会再给 Consumer Group2 消费。 同时,一个消费组中包含多个消费者,同一个组内的消费者是竞争消费的关系,每个消费者负责消费组内的一部分消息。如果一条消息被消费者 Consumer1 消费了,那同组的其他消费者就不会再收到这条消息。 在 Topic 的消费过程中,由于消息需要被不同的组进行多次消费,所以消费完的消息并不会立即被删除,这就需要 RocketMQ 为每个消费组在每个队列上维护一个消费位置(Consumer Offset),这个位置之前的消息都被消费过,之后的消息都没有被消费过,每成功消费一条消息,消费位置就加一。 2.3 Kafka的消息模型 Kafka 的消息模型和 RocketMQ 是完全一样的,唯一的区别是,在 Kafka 中,队列这个概念的名称不一样,Kafka 中对应的名称是“分区(Partition)”,含义和功能是没有任何区别的。

June 30, 2022 · 1 min · 李昌