HappyTeemo

什么是消息队列

消息队列是一种异步的服务间通信方式,适用于无服务器和微服务架构。

消息在被处理和删除之前一直存储在队列上。每条消息仅可被一位用户处理一次。

消息队列可被用于分离重量级处理、缓冲或批处理工作以及缓解高峰期工作负载。

提供消息的我们称为生产者;接收消息的我们称为消费者。

为什么要用消息队列

目的:解耦、异步、削峰。

一、解耦

通常我们的代码是存在调用链的,如果是一个单体里面,直接函数调用,等待返回就好了。但是在分布式、微服务中,我们可能不知道上下游的改动或者可靠性。

比如A给BCD提供消息。

  • 如果要添加E或者去掉C,则需要改A的代码。
  • 如果B挂了,A怎么给B补发?
  • 总之,BCD要改,A必须要跟着改。

改成:

A给消息队列提供消息。BCD从消息队列拿。(发布订阅模型)

  • 添加、删除 BCD自己决定。
  • 消费者成功与否,生产者不必关心。

总结:通过一个 MQ,Pub/Sub 发布订阅消息这么一个模型,A 系统就跟其它系统彻底解耦了。

带来问题:

  • 丢数据。 B取了数据,消费的过程中挂了。则数据丢失。改:加入返回机制,消费完了通知A。
  • 重复消费。B消费了但是没通知A消费了,然后挂了,再获取的时候,会把挂之前取的消息再消费一遍。

二、异步

顺序执行:A-B-C

改成:先写入队列,直接返回,ABC并发执行。

问题:顺序性。逻辑是要顺序,但是并发可能导致乱序。

img

三、削峰

当上游的流量冲击过大, 想要不被打垮,要么抛弃这些流量,要么存储这些流量。很显然,存起来在大多数时候是更好地选择。

注意这里是大多数,有时候一些具有时效性的场景,抛弃可能反而更好。

将流量存起来,在空闲的时候慢慢消费,缓冲上下游的瞬时突发流量,使其更平滑,增加系统的可用性,这就是削峰填谷。

消息队列的两种形式

点对点

  • 很原始的模式,如果消费者要增加则必须要改生产者。优点是顺序消费。
  • 像打电话。

订阅发布:

  • 生产者不用关心消费者的数量。可以*扩展数量。
  • 像订报纸。

消息队列带来的问题

没有银弹,都是驱虎吞狼而已。

  • 可用性降低:消息队列挂了,整个系统全挂。
  • 复杂度增加:要解决 消息丢失、重复消费、顺序性等问题。
  • 一致性问题:BCD并发执行,如果B挂了,那么CD产生的数据就会有缺陷。

常见问题:

delivery guarantee:

  • At most once:消息可能会丢,但绝不会重复传输
  • At least one:消息绝不会丢,但可能会重复传输
  • Exactly once:每条消息肯定会被传输一次且仅传输一次,很多时候这是用户所想要的。

1. 重复消息

消费者消费了但是还没来得及记录已读标记就挂了,重启后则会重复读取。

最常见的解决方案是消费者保持幂等性。即多次消费结果不变。主要是因为消息丢失更不可控。

代码层一般是添加特殊标记,如果已经消费过了则过滤掉。

比较复杂的还有双删和事务。

  • 双删:先记录已读index在本地,消费完后通知消息队列(commit),删除已读index。重启时如果有已读index,则从该index开始读取,没有则看消息队列上的标记。(由于commit和删除index的操作很快,所以失败一个可能性较小)。

  • 事务:消费过程和commit做成事务,但是会影响性能。

2. 消息丢失

  • 生产者丢数据:生产者在收到消息队列的接收确认后再删除消息(ACK)。比较耗性能。
  • MQ丢数据:本地持久化、冗余备份、集群高可用。
  • 消费者丢数据:消费者先记录标记,但是没有消费就挂了,重启后则该消息丢失。所以一般是先消费再标记(commit),宁可多消息不愿少消息。

3. 顺序消费

  • 分布式事务模型。即全局锁之类的方式。
  • 将需要顺序执行的事情给一个服务做。通过哈希等算法保证同一个标志(比如用户ID)的消息总是给固定的服务处理,在单一服务内部是顺序的。
  • 让BCD之间横向通讯。比如B的逻辑需要CD来补充,则CD做完了通知B一下,B去补充自己的逻辑即可。

4. 消息积压的处理

  • 紧急扩容消费者,加快消费速度。
  • 过期失效。抛弃一部分消息,降级服务,优先保证系统整体的稳定性。
  • 消息紧急性,让紧急的事情优先处理。

消费模型 Push vs Pull

消费者拉取 Pull

即消费者自己找消息队列拿,自己一口一口吃。

  • 消费速度 消费者自己决定
  • Consumer 可以自己控制消费方式——即可批量消费也可逐条消费,同时还能选择不同的提交方式从而实现不同的传输语义。
  • 消费者需要轮训。(可能造成无效等待,可以采用长轮训)
    • 长轮询:没有消息就等30秒,还是没有再断开重连,减少了握手开销,但是依然会占用连接数。
  • 消息反压 :消费者速度慢,反压上游。

消息队列推送 Push

即生产者强行推给消费者,即养猪,不管吃不吃的完,先倒进去再说。

  • 减少了消费者的无效等待。
  • 消费者处理不过来的时候可能会丢失。
  • 可以考虑有消息的时候广播 ,消费者自己维护一个队列。

相关文章: