消费者接收消息的两个模式
PUSH:队列有消息都会进行投递实时性很强导致客户端压力过大
PULL:消费者主动去MQ拉消息,压力可控但是实时性不强 拉取频率需要自己控制,拉取频繁造成服务端客户端压力,拉取间隔时间过长容易造成消费不及时。
任何MQ底层其实都是pull模式 PUSH模式底层也是PULL
匀速场景下PUSH模式 大部分场景下poll模式
消费者通过长轮询进行消费
RocketMQ消息发送的两种模式
发送消息的同步模式
mq服务器接收到消息后会返回一个确认,这种方式非常安全,但性能会大大受影响。等到所有的从机都复制了消息之后才会返回。所以对重要的消息可以选择这种方式。
消息异步模式
发送端不能忍受长时间响应时间会,消息队列会异步发送一个相应消息
利用回调来得到成功和失败的消息。
单向发送消息
这种方式不关心发送结果的场景这种方式吞吐量很大,但是存在消息丢失的风险。(日志场景可能使用)
延迟消费模式
消费者会立刻消费队列里的消息,希望投递消息一会儿再被消费者消费
发送消息时Message 类 中可以进行setDelayTimeLevel 通过控制level等级可以进行制定延迟时间。
批量消息生产者
用LIST 集合装下list消息集合进行批量 发送
顺序消息
确保的是局部有序:
实现方式 一个生产者的消息都放在一个队列里且消费者采用单线程方式进行消费。
TAG过滤和重复消费问题
Rocketmq提供消息过滤功能通过tag或者key进行区分,比如带有taga标签的被A消费,带有tagb标签的被B消费,还有在事务监听的类里面,只要是事务消息都要走同一个监听我们也需要通过过滤才区别对待。
订阅的tag一致才能在一个组 订阅的tag不一致也会不在一个组
TOP和tag使用场景
没有直接关联的消息,如淘宝交易消息 京东物流消息使用不同TOPIC进行区分,同样天猫交易消息,的子集 电器订单 女装订单等不同大消息的自消息可以用tag进行区分
消息优先级是否一致,同样是物流消息优先级有明显差距的使用不同TOPIC进行区分。
消息量级是否相当 量级别过大的消息要拆分成不同topic 使用同一个topic可能会卡死
Rocketmq中的消息key
在rocketmq中的消息,默认会有一个messagesid做消息的唯一标识,我们也可以给消息携带一个key,用作唯一标识或者业务标识,包括在控制面板查询的时候也可以使用messageID 或者key来进行查询(messageid Rocketmq生成 key 程序员自己设置)
携带key发送有利于后续消息去重
Nameservice
Nameservice使用集群化部署 因为要保证高可用因为Rocketmq其他部件都依赖Nameservice进行服务所以要对nameservice进行集群化部署来保证高可用。
每个Broker向所有nameservice注册信息
**系统(消费者和生产者)**每隔一段时间主动从nameservice拉取信息
如果某个broker120s没有发送心跳信息则认为broker挂了
broker会每隔30s向nameservice注册,nameservice会每隔10s检查一次broker
各种中间件那nameservice对比
kafka broker+zookeeper
rockermq namerservice
rabbitmq 集群节点自己做路由中心
Brocker
RocketMQ的Master-Slave模式采取的是Slave Broker不停的发送请求到Master Broker去拉取消息。
消费者信息拉取有可能从主节点拉取也有可能从从节点拉取,如果Master负载过高从Slave节点拉取。
如果Slave Broker挂了,那么此时无论消息写入还是消息拉取,还是可以继续从Master Broke去走。(Slave节点挂了影响不大)
如果Master节点挂了RocketMQ不能实现直接自动将Slave Broker切换为Master Broker吗
4.5版本之后可以使用基于Dledger实现RocketMQ高可用自动切换(基于Raft算法)
Brocker与NameServer进行通信
Broker会跟每个NameServer都建立一个TCP长连接,然后定时通过TCP长连接发送心跳请求过去
nameServer每十秒检查一次,broker每30s向nameserver汇报一次
topic在broker上分布式存储,每台broker可以存一部分数据。
生产者系统如何将消息发送给Broker
发送消息之前,得先有一个Topic,然后在发送消息的时候你得指定你要发送到那个broker
知道你要发送的Topic,那么可以跟Nameserver建立一个TCP长连接,然后定时从他那里拉取到最新的路由信息,包括集群里有哪些Broker,集群里有哪些Topic,每个Topic都存储在哪些Broker上。
生产者通过路由信息找到自己要投的topic在哪几台Broker上,就可以根据负载均衡算法选择出一台Broker出来。
Broker收到消息后会把消息存在磁盘当中,并且生产者会和Broker建立一个TCP长链接,然后通过长链接向Broker发送消息。
是生产者一定是投递消息到Master Broker的,然后Master Broker会同步数据给他的Slave Brokers,实现一份数据多份副本,保证Master故障的时候数据不丢失,而且可以自动把Slave切换为Master提供服务。
总结: 生产者一定给master消息消费者可能从master也可能从slave消费消息
生产者工作原理
创建Topic的时候为何要指定MessageQueue的数量
MessageQueue一个Topic对应了多少个队列就是多少个MessageQueue
Topic存储在多个Broker上面,而MessageQueue又分布在多个Topic上面
如果某次访问一个Broker发现网络延迟有500ms,然后还无法访问,那么就会自动回避访问这个Broker一段时间,比如接下来3000ms内,就不会访问这个Broker了。
Broker如何持久化存储消息
当Broker接收到消息的时候,Broker会把消息直接写入到CommitLog(日志文件),采用顺序写入保证写入速度。commitlog写入数据上限为1GB。
因为Topic内会有多个消息队列(MessageQueue),我们需要知道不同消息队列的落盘情况。
每个消息队列对应一个ConsumeQueue,每次Broker写入Commitlog的时候,要将写入对应的消息队列的时候,在消息队列的落盘文件里,把Commitlog里的地址引用写入到ConsumeQueue中。
Broker基于OS操作系统的PageCache和顺序写两个机制,来提升CommitLog文件的性能。
数据先写入OS内存,再由OS的后台线程将内存文件刷入到磁盘的Commitlog中
同步刷盘和异步刷盘
同步刷盘:数据不写入内存中,而是直接刷入磁盘中,之后再返回ACK
异步刷盘:数据写入内存中,直接返回ACK,之后再由OS线程异步将内存中的内容刷入磁盘中
异步:高吞吐量+数据可能丢失 同步:数据不会丢失,性能大幅度下降
消费者组
两种消费模式
集群式消费: 消费者组里只有一个会进行消费
广播模式:会把消息给所有消费者组里的消费者
Consume消费过程
消费者从MessageQueue 获得消息的ConsumeQueue
去找到对应的ConsumeQueue读取里面对应位置的消息在CommitLog中的物理offset偏移量
然后到CommitLog中根据offset读取消息数据,返回给消费者机器。
Broker会记录消费者的消费位置下次会从Broker存储的消费位置来进行继续消费
ConsumeQueue使用了Oscache进行优化,最近从生产者得到的消息会放入Oschache
如果消费者负载很高能够根上ConsumeQueue增长的速度会从OScache中直接获取
如果消费者消费速度很慢则从磁盘中获取即可
Consume处理完业务逻辑才能返回给MessageQueue 一个ACK来表示处理完消息
广播模式下Rocketmq不会维护消费进度而是消费者自己进行维护。时间到了没有返回ACK广播模式直接丢弃
集群模式Rocketmq会维护消费者进度,时间到了没有收到ACK会进行再次发送消息。保证消息不丢失。
消费者 生产者 Broker回顾
一个Topic分布式存储在不同Broker中
不同Broker中的Topic里有多个MessageQueue,每个MessageQueue 对应多个ConsumeQueue。
MessageQueue 会均匀分布在消费者集群中的机器当中(采用集群模式)
消费者集群采用pull或push方式来获得消息(其实底层实现都是pull方式)
push方式的实现是(长轮询+请求挂起)
RocketMQ消息领丢失方案
在RocketMQ中有事务消息的功能,凭借这个事务级的消息机制,就可以让我们确保订单系统推送给出去的消息一定会成功写入MQ里,绝对不会半路就搞丢了。
订单系统发送订单的时候,要发送half消息。
如果Rocketmq把消息成果接受要把half返回给订单系统
随后订单系统要进行自己的业务逻辑
如果订单系统成功完成业务逻辑会给消息系统一个commit
如果失败会给消息系统一个rollback
删除消息回撤
但是步骤4和步骤2都有受网络影响可能失败
所以需要步骤5查询订单系统详情判断消息状态
1.失败 删除消息
2.成果 确定消息
消费消息的消费组 也不能开启异步!
未开启异步只有消费完了才会回复让消息队列标志往前移动
但是开启异步会出现从OScache拿了就让消息队列标志向前移动的情况
零消费方案牺牲了大量时间,会使吞吐量大大下降
消费者消费消息重复问题
1.系统之间的请求问题,例如生产者所在的系统会受到重复消息导致产生重复,例如用户系统让下单系统
下单结果下单流程出现问题迟迟没有响应,所以用户系统再次让下单系统下单,这时候下单系统作为Procducer
就会下单两次
2.RocketMQ本身的问题,面已经介绍过,Producer与Broker中间可能由于网络波动问题导致重发因此
RoketMQ也会导致消息重发问题
重发问题解决:
生产者系统方面查询MQ是否已经下过单了
利用数据库幂等性解决重发
利用Redis锁解决重发
消费者消费失败情况
当消费者消费失败的时候,会返回一个RECONSUME_LATER状态将消息存储到重试队列
如果重试16次依然失败,那么消息会进入死信队列
消费者消息乱序问题
因为有多个Broker 一个Tpoic可能存储在多个Broker上并且Topic有多个MessageQueue所以会有多个ConsumeQueue,消息会分布式的存储在多个ConsumeQueue当中因此不可避免会有消息乱序问题。
解决消息乱序的方法
第一步:要有序的部分首先要都放在同一个消息队列中
第二步:要按照操作顺序放入消息队列
第三步:当消费者消费失败的时候不能使用重试队列和死信队列而是等待
实战 生产者端有个MessageQeueSelect{}方法可以进行重写根据需要选择队列。
同时消费者端有个MessageListOrder可以进行操作。
RocketMQ
消息过滤使用Tag可以进行消息过滤具体使用代码消费者端
消息发送方携带Tag
Message msg = new Message(
"TopicOrderDbDate",
"TABLeA"//携带的TAG
);
消息消费方根据Tag进行处理
consumer.subscribe(
"TopicOrderDbDate",
//MessageSelector.bySql()
"TABLEA||TABLEN"
//tag=TableA和tag=TableB的数据才会消费
)
通过有选择的消费减轻了压力
基于延迟队列的消费机制
有些消息需要在n秒后撤回,或者进行处理
延迟消息,会在30分钟才会被消费。消费者端会扫描数据查询这个订单在此期间有没有被支付如果支付了无所谓,如果没支付则删除订单。
生产者端 消息可以通过SetDelayTime来设置延迟级别不同级别对应不同延时时间
消费者端 接受消息后查询数据库详情来确认订单是否支付过
怎么根据KEY判断消息是否丢失过?
KEY 是消息的业务标识,由生产者在发送消息时设置,可以用来唯一标识某个业务逻辑。RocketMQ 会创建专门的索引文件,用来存储 KEY 和消息的映射,方便后续查询。
根据 KEY 判断消息是否丢失过的方法有以下几种:
使用命令行工具 RocketMQ 提供了一个命令行工具,叫做 mqadmin,可以用来执行各种管理和维护操作。其中,有一个子命令叫做 queryMsgByKey,可以根据 KEY 查询消息的状态,包括消息 ID、队列 ID、偏移量、存储时间、消费状态等。如果查询不到消息,或者消息的消费状态不是成功,就说明消息可能丢失过。
使用 Java API RocketMQ 的 Java 客户端也提供了一个方法,叫做 queryMessage,可以根据 KEY 查询消息的状态,返回一个 QueryResult 对象,包含了消息的列表和总数。如果查询不到消息,或者消息的消费状态不是成功,就说明消息可能丢失过。
消息追踪
开启 traceTopicenable = true
会开启追踪消息,内部会开启开启一个Topic进行消息追踪
RocketMq限流策略
令牌桶算法是一种限流算法,它的原理是以固定的速率向一个容量有限的桶中添加令牌,
每次请求都需要从桶中消耗一定数量的令牌,如果桶中没有足够的令牌,
就拒绝或等待请求。令牌桶算法可以应对突发的流量,允许一定程度的流量超出。
基于 MQ 实现令牌桶算法的思路是:
在生产者端,维护一个令牌桶,每隔一定时间向桶中添加令牌,直到达到桶的容量。
在消费者端,维护一个队列,用来接收生产者发送的消息。
当生产者有新的消息时,先检查令牌桶中是否有足够的令牌,
如果有,就从桶中扣除相应的令牌,并将消息发送 到 MQ 中;如果没有,就等待或丢弃消息。
当消费者有空闲时,就从 MQ 中获取消息,并进行处理。
这样,就可以利用 MQ 的缓冲和异步的特性,来实现令牌桶算法的限流效果。
也就是说每秒钟就发放多少个令牌,然后只能允许多少个请求通过。
kafka和RocketMq对比
RocketMQ 和 Kafka 都是优秀的分布式消息中间件,它们各有优点和缺点,具体的对比如下:
性能方面,Kafka 的单机吞吐量高于 RocketMQ,主要是因为 Kafka 使用了批量发送和异步刷盘的机制,但是这也会牺牲一定的可靠性和实时性。
可靠性方面,RocketMQ 支持同步刷盘和同步复制的方式,可以保证消息不会因为操作系统或网络故障而丢失,而 Kafka 使用异步刷盘和异步复制的方式,会存在数据丢失的风险。
实时性方面,RocketMQ 使用长轮询的方式,可以保证消息的及时投递,而 Kafka 使用短轮询的方式,会存在一定的延迟。
队列数方面,RocketMQ 支持单机最高 5 万个队列,而 Kafka 单机超过 64 个分区,性能会明显下降。
消息顺序方面,RocketMQ 支持严格的消息顺序,即使一台 Broker 宕机,也不会乱序,而 Kafka 只能保证分区内的顺序,一旦发生切换,就会出现乱序。
消息重试方面,RocketMQ 支持消费失败的消息定时重试,每次重试间隔时间顺延,而 Kafka 不支持消费失败的重试。
定时消息方面,RocketMQ 支持定时消息,可以指定消息在某个时间点被消费,而 Kafka 不支持定时消息。
分布式事务消息方面,RocketMQ 支持分布式事务消息,可以保证消息的最终一致性,而 Kafka 不支持分布式事务消息。
消息查询方面,RocketMQ 支持根据消息 ID 或内容查询消息,而 Kafka 不支持消息查询12。
消息回溯方面,RocketMQ 支持按照时间回溯消息,可以指定从某个时间点开始重新消费消息,而 Kafka 只能按照偏移量回溯消息。
消息过滤方面,RocketMQ 支持在 Broker 端进行消息过滤,可以根据消息属性或内容进行过滤,而 Kafka 只能在 Consumer 端进行消息过滤。
社区活跃度方面,Kafka 的社区活跃度高于 RocketMQ,有更多的开源贡献者和用户34。
开发语言方面,Kafka 使用 Scala 语言开发,而 RocketMQ 使用 Java 语言开发,对于 Java 开发者来说,RocketMQ 可能更容易理解和调试。