消息队列就是一个使用队列来进行通信的组件
消息队列常常指消息中间件
主要实现的功能:异步处理 服务解耦 流量控制
异步处理:
现在项目的调用链路变长了,为了减小响应时间,让服务器并发处理
服务解耦:
为了新增服务的时候方便,只需要把服务的相关消息塞进消息队列中
流量控制:
大量请求的时候做缓冲
弊端:
系统稳定性会下降,运维难度会增大
消息中间件的两种模型:队列模型,订阅发布模型
队列模型:
生产者把消息塞到一个队列里面,一个队列可以存储多个生产者的消息,一个队列也可以有多个消费者,但是消费者之间是竞争关系,每个消息只能被一个消费者消费(弊端)
发布订阅模型:
为了解决一条消息能被多个消费者消费的问题
兼容队列模型,相当于生产者发布消息,可以有多个消费者进行订阅
为了提高并发度,还会引入队列或者分区的概念——消息发到一个主题下的某个队列或者某个分区当中。
某个主题下面有五个队列,那么这个主题的并发度就是5
消费者也有组的概念:一条消息会发往多个订阅了这个主题的消费组
如何保证消息不会丢失?
有三个阶段:生产消息,存储消息,消费消息。从三个阶段分别入手来看如何确保消息不会丢失
生产者在把消息发送到broker的时候需要妥善处理好broker的响应,不论是同步还是异步的发送消息,做好try catch
比如写入失败需要重新发送
存储消息的时候需要在消息刷盘之后再给生产者响应,不能只写入缓存就给生产者响应,如果是集群部署的,那么还需要写到副本机(子节点当中)
消费消息的时候有一个常犯的错误就是:必须要在消费者真正执行完成业务逻辑之后,在发送给broker消费成功的消息
消息可靠性增强了,性能就下降了
如何处理重复消息
处于安全性的考虑,在broker没有响应的时候,消息重复发送是不可避免的
需要用到幂等性来确认是否重复处理,在不同业务上的运用是不一样的
如何处理消息的有序性
有序性的分类:全局有序,部分有序
一般情况下不需要全局有序,只需要保证单表消息有序就行
部分有序:我们只需要将topic内部划分成我们需要的队列数,然后每个队列对应一个单线程来处理消息
可以通过队列数量来提升并发效率
如何处理消息堆积
消息堆积往往是生产者消费者速度不匹配造成的。有可能是因为消费者消费能力弱就渐渐积压了
先定位消费慢的原因,如果逻辑都有花了还是慢,那么就考虑水扩容 增加队列数量和消费者数量
因为一个队列对应一个消费者,所以队列数量一定要增加(消费者内部是多线程还是单线程就看具体场景)
如何设计一个消息队列
消息中间件有几个重要角色:生产者消费者,broker,注册中心
简述消息流转过程
注册中心用于服务的发现:包括broker 生产者 消费者的发现
简述实现要点:通信——各模块的通信可以基于netty然后自定义协议来实现,注册中心可以用各种现成的,也可以像rocketmq自己实现简单的namesrv
考虑扩容和整体性能——分布式思想,kafka分区(一个topic分几个partition),保证数据可靠性,采用多副本存储(raft)选举算法
为了提高消息队列的可靠性(本地文件系统来存储数据
性能提高——采用顺序写的方式提高性能,根据消息队列的特性利用内存映射,零拷贝进一步提高性能,利用kafka批处理思想提高吞吐
消息队列的推拉模式
生产者和broker之间默认是推的方式,只有消费者和broker之间的交互需要考虑推拉模式
推模式指的是从broker推向consumer,然后消费者被动的接受消息
推模式对于消费者的优缺点:实时性高,消费者使用更加简单
缺点:推送速率难以适应消费速率,速度难以一致
————推模式适应消息量不大,消费能力强要求实时性高的消费者
拉模式指的是主动拉取消息
优点:可以根据自身的需求来拉取消息
拉模式下broker相对轻松,更适合消息的批量发送
缺点:消息延迟(因为请求不能太繁忙,防止被认为是攻击) 。 消息忙请求(broker有延迟,隔了几个小时才有,那么几个小时只能消费者的请求都是无效的)
一般采用的是拉模式
如何减轻拉模式的负面影响?
rocketmq和kafka都采用了长轮询的方式