关于「 消息队列」你应该知道的事

851 阅读9分钟

消息队列是什么

百度百科上关于“消息队列”的定义是这样的

“消息队列(Message Queue)”是在消息的传输过程中保存消息的容器。

消息队列(Message Queue,简称MQ),指保存消息的一个容器,本质是个队列。

不管是什么类型的消息队列,消息队列的本质都是:一发一存一消费

生产者先将消息投递一个叫做「队列」的容器中,然后再从这个容器中取出消息,最后再转发给消费者,仅此而已。

image.png

消息队列最原始的模型包含了两个关键词:消息和队列

1、消息:就是要传输的数据,可以是最简单的文本字符串,也可以是自定义的复杂格式(只要能按预定格式解析出来即可)。

2、队列:大家应该再熟悉不过了,是一种先进先出数据结构。它是存放消息的容器,消息从队尾入队,从队头出队,入队即发消息的过程,出队即收消息的过程。

为什么使用消息队列

大家比较熟知的消息队列引用场景有:

  • 系统解耦
  • 异步通信
  • 流量削峰

除此之外,还有延迟通知、最终一致性保证、顺序消息、流式处理等等。

使用消息队列还可以:

屏蔽异构平台的细节 发送方、接收方系统之间不需要了解双方,只需认识消息。

复用 一次发送多次消费。

可靠一次保证消息的传递。如果发送消息时接收者不可用,消息队列会保留消息,直到成功地传递它。

提供路由发送者无需与接收者建立连接,双方通过消息队列保证消息能够从发送者路由到接收者,甚至对于本来网络不易互通的两个服务,也可以提供消息路由。

举个实际的例子说明一下,用户参加一个优惠券活动,需要输入活动推广码,然后系统需要给用户发放优惠券和发送短信、邮件等通知用户参加活动成功。

解耦:使用消息队列后用户输入推广码后将事件发送到消息队列,之后直接记录下用户使用推广码的记录即可,发券和发送触达则有相应的系统去消息队列获取事件进行处理即可,减少了系统依赖,实现了系统解耦。

异步: 改造后相当于发券和发触达这些后续步骤全部变成了异步执行,能减少输入推广码登记的时间,提升了系统的吞吐量。

削峰:消息队列转储消息,可以作为“漏斗”进行限流保护。

使用消息队列需要注意的问题

  • 高可用 项目中使用消息队列,都是得集群/分布式的。

  • 数据丢失 消息队列中的数据需要存在别的地方,这样才尽可能减少数据的丢失

  • 数据一致性 使用分布式事务,把所有关联操作放在一个事务里

  • 重复消费 保证消息消费的幂等性

  • 顺序消费

如何保证消息的顺序性?

RocketMQ的topic内的队列机制,可以保证存储满足FIFO(First Input First Output 先进先出),剩下的只需要消费者顺序消费即可。RocketMQ仅保证顺序发送,顺序消费由消费者业务保证。

Kafka 保证消息顺序性

image.png

写入一个 partition中的数据一定是有顺序的。

生产者在写的时候,可以指定一个 key,比如订单id作为key,那么订单相关的数据,一定会被分发到一个 partition中,此时这个 partition中的数据一定是有顺序的。Kafka 中一个 partition 只能被一个消费者消费。消费者从partition中取出数据的时候 ,一定是有顺序的。

消息可靠性怎么保证?

消息丢失可能发生在生产者发送消息MQ本身丢失消息消费者丢失消息3个方面。

  • 生产者发送消息 JOB轮询超过一定时间(时间根据业务配置)还未发送成功的消息去重试,在监控平台配置或者JOB程序处理超过一定次数一直发送不成功的消息,告警,人工介入。

  • MQ丢失

RocketMQ分为同步刷盘异步刷盘两种方式,默认的是异步刷盘,就有可能导致消息还未刷到硬盘上就丢失了。可以通过设置为同步刷盘的方式来保证消息可靠性,这样即使MQ挂了,恢复的时候也可以从磁盘中去恢复消息。当我们选择同步刷盘之后,如果刷盘超时会给返回FLUSH_DISK_TIMEOUT

Kafka也可以通过配置做到:acks=all 只有参与复制的所有节点全部收到消息,才返回生产者成功。除非所有的节点都挂了,消息才会丢失。

  • 消费者丢失 RocketMQ默认是需要消费者回复ack确认,而kafka需要手动开启配置关闭自动offset。消费方不返回ack确认,重发的机制根据MQ类型的不同发送时间间隔、次数都不尽相同,如果重试超过次数之后会进入死信队列,需要手工来处理了。(Kafka没有这些)

image.png

保证 MQ 重复消费幂等性

image.png

所谓消息幂等就是当出现消费者对某条消息重复消费的情况时,重复消费的结果与消费一次的结果是相同的,并且多次消费并未对业务系统产生任何负面影响。

思路:

  • 拿数据要写库,首先检查下主键,如果有数据,则不插入,进行一次update
  • 如果是写 redis,就没问题,反正每次都是 set ,天然幂等性
  • 生产者发送消息的时候带上一个全局唯一的id,消费者拿到消息后,先根据这个id去 redis里查一下,之前有没消费过,没有消费过就处理,并且写入这个 id 到 redis,如果消费过了,则不处理。
  • 基于数据库的唯一键
常用的业务幂等性保证方法
  • 利用数据库的唯一约束实现幂等:比如将订单表中的订单编号设置为唯一索引,创建订单时,根据订单编号就可以保证幂等

  • 去重表:本质也是根据数据库的唯一性约束来实现。思路是:首先在去重表上建唯一索引,其次操作时把业务表和去重表放在同个本地事务中,如果出现重现重复消费,数据库会抛唯一约束异常,操作就会回滚

  • 利用redis的原子性:每次操作都直接set到redis里面,然后将redis数据定时同步到数据库中

  • 多版本(乐观锁)控制:此方案多用于更新的场景下。其实现的大体思路是:给业务数据增加一个版本号属性,每次更新数据前,比较当前数据的版本号是否和消息中的版本一致,如果不一致则拒绝更新数据,更新数据的同时将版本号+1

  • 状态机机制:此方案多用于更新且业务场景存在多种状态流转的场景

  • token机制:生产者发送每条数据的时候,增加一个全局唯一的id,这个id通常是业务的唯一标识,比如订单编号。在消费端消费时,则验证该id是否被消费过,如果还没消费过,则进行业务处理。处理结束后,在把该id存入redis,同时设置状态为已消费。如果已经消费过了,则不进行处理。

消息队列如何选型

目前在市面上比较主流的消息队列中间件主要有,Kafka、ActiveMQ、RabbitMQ、RocketMQ 等这几种。

image.png

RocketMQ是阿里开源的,定位是非日志的可靠消息传输。例如:订单,交易,充值,流计算,消息推送,日志流式处理,binglog分发等。

Kafka是世界范围级别的消息队列标杆,定位是系统间的数据流管道,实时数据处理。例如:常规的消息系统、网站活性跟踪,监控数据,日志收集、处理等。

RocketMQ

RocketMQ实现原理

RocketMQ由NameServer注册中心集群、Producer生产者集群、Consumer消费者集群和若干Broker(RocketMQ进程)组成,它的架构原理是这样的:

  1. Broker在启动的时候去向所有的NameServer注册,并保持长连接,每30s发送一次心跳
  2. Producer在发送消息的时候从NameServer获取Broker服务器地址,根据负载均衡算法选择一台服务器来发送消息
  3. Conusmer消费消息的时候同样从NameServer获取Broker地址,然后主动拉取消息来消费

image.png

Borker的Master和Slave之间是怎么同步数据的呢?

RocketMQ 底层用了 DLedger,用 Raft 同步日志,从原理上保证了不会脑裂。 通过broker主从机制实现了高可用

  • 在Master broker收到消息后,会被标记为uncommitted状态,然后会把消息发送给所有的slave
  • slave在收到消息之后返回ack响应给master
  • master在收到超过半数的ack之后,把消息标记为committed
  • 发送committed消息给所有slave,slave也修改状态为committed
RocketMQ为什么速度快?

因为使用了顺序存储、Page Cache和异步刷盘。

  • 我们在写入commitlog的时候是顺序写入的,这样比随机写入的性能就会提高很多
  • 写入commitlog的时候并不是直接写入磁盘,而是先写入操作系统的PageCache
  • 最后由操作系统异步将缓存中的数据刷到磁盘
RocketMQ延迟队列怎么实现?

RocketMQ延迟队列的核心思路是:

所有的延迟消息由producer发出之后,都会存放到同一个topic(SCHEDULE_TOPIC_XXXX)下,不同的延迟级别会对应不同的队列序号。

当延迟时间到之后,由定时线程读取转换为普通的消息存的真实指定的topic下,此时对于consumer端此消息才可见,从而被consumer消费。

注意:RocketMQ不支持任意时间的延时,只支持以下几个固定的延时等级

private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h >2h";

参考资料

baike.baidu.com/item/%E6%B6…

www.zhihu.com/question/54…

juejin.cn/post/684490…

juejin.cn/post/684490…

www.zhihu.com/question/54…

www.zhihu.com/question/54…

zhuanlan.zhihu.com/p/138624006

www.zhihu.com/question/65…

zhuanlan.zhihu.com/p/142441996