目录
前言
1. 什么是消息队列
2. 消息队列的应用场景
2.1 异步处理
2.2 服务解耦合
2.3 流量控制
3. 消息队列的基本模型
3.1 队列模型
3.2 发布/订阅模型
4. 消息队列概念详解(本项目)
5. 如何保证消息的不丢失(扩展)
5.1 生产消息
5.2 存储消息
5.3 消费消息
6. 处理重复消息(扩展)
7. 处理消息堆积
结语
从今天开始,本系列开始进行总结自己已经模拟实现的一个消息队列的项目,涵盖了对消息队列的认识,消息队列的应用场景,消息队列的模拟实现,以及相关Demo验证消息队列功能.本项目的主要目标是帮助自己能够理解消息队列实现的思想,进一步去掌握消息队列这一技术栈,为自己以后在工作中使用功能更加复杂更加全面对的消息队列打下基础,那话不多了,我们直接开始!
我们先来看一下百度百科给出的详细解释:
简要看过消息队列的定义之后,我们思考为什么要使用消息队列呢?消息队列有什么应用场景?
随着时代的快速发展,我们的业务不断扩张,从之前的单体架构到现在的微服务架构, 成百上千的服务之间相互调用和依赖。从一开始一个服务器上有 100 个在线用户(在当时已经很多了),到现在坐拥10亿日活的微信。我们需要有一个设备来解耦服务之间的关系、控制资源合理合时的使用以及缓冲流量洪峰等等。那么随着这些功能的需要,消息队列就应运而生了.
消息队列主要的应用场景有三个:
1. 异步处理
2. 服务解耦
3. 流量控制
随着一个简单项目的不断扩展,我们会发现项目的请求链路越来越长,比如之前的博客系统,现在看起来只是简单的一个项目,假设我们这个系统上线了(这很不可能,呜呜呜,做的差太多了,但是可以假如,哈哈哈),我们有着成千上万的用户,这时候我们就特别需要进行扩展更多的功能,进而给用户更加好的体验,我们会添加用户的会员管理,用户积分系统,抽奖系统,短信系统等等,这会导致我们一路同步调用下来响应的时间变得很长,用户的体验就会变得相当的不好.这时候消息队列就可以排上用场了.我们假设有一个积分兑换礼品的功能:
正常来说生成了用户订单就可以进行返回请求了,积分服务和短信的通知没必要同步进行处理,我们可以将这两部分的进行一步处理,虽然可能会出现一点延迟,但是却减少了用户请求的时间,最后的结果也是一样的.这就是消息队列存在的意义.
上面我们给礼品兑换服务添加了短信通知功能,如果再添加什么类似于做什么活动可以进行积分的再次增加,那么我们这个订单的下游系统就会越来越复杂,为了实现这些接口,我们不断的在此基础上就西宁添加下游任务的接口,那么这个服务就会耦合性变得特别大.我们将新扩展的功能进行添加到消息队列,并不是每次订单服务还会触发这些功能,这些功能会被来自消息队列指定的主题而被调用.
关于这方面我们就不得不提起,当时我们学习堵塞队列的时候的削峰填谷这个名词了,我们设计队列的时候就是为了防止在某一时刻服务器涌入大量的请求,导致服务器发生崩溃,我们通过将请求进行发送到消息队列,进行对请求的缓冲,已达到更好的处理响应的目的.
生产者往某个队列里面发送消息,一个队列可以存储多个生产者的消息,一个队列也可以有多个消费者, 但是消费者之间是竞争关系,即每条消息只能被一个消费者消费。
为了解决一条消息能被多个消费者消费的问题,发布/订阅模型就来了。该模型是将消息发往一个Topic即主题中,所有订阅了这个 Topic的订阅者都能消费这条消息.一个典型的例子就是群聊,只有加入到当前群聊的人才会收到消息.RabbitMQ就是采用队列模型,通过Exchange模块来将消息发送至多个队列,解决一条消息需要被多个消费者消费问题。
(我们后面实现的消息队列就是模仿着RabbitMQ来实现的,大家可以看后面的系列.)
队列模型每条消息只能被一个消费者消费,而发布/订阅模型就是为让一条消息可以被多个消费者消费而生的,当然队列模型也可以通过设定交换机类型发送到多个队列来解决一条消息被多个消费者消费问题,但是会有数据的冗余。
发布/订阅模型兼容队列模型,即只有一个消费者的情况下和队列模型基本一致。
RabbitMQ采用队列模型,RocketMQ和Kafka采用发布/订阅模型。
一般我们称发送消息方为生产者 Producer
,接受消费消息方为消费者Consumer
,消息队列服务端为Broker
。消息从Producer
发往Broker
,Broker
将消息存储至本地,然后Consumer
从Broker
拉取消息,或者Broker
推送消息至Consumer
,最后消费。
我们后面是按照RabbitMQ进行构建的消息队列.在其中Broker中会有以下的概念:
其中Exchange和Queue可以理解为"多对多"关系,和数据库中的"多对多"一样:
1. 一个Exchange可以绑定多个Queue(可以往多个Queue只能中进行转发消息)
2. 一个Queue也可以被多个Exchange绑定(一个Queue中的消息可以来自于多个Exchange)
下图是进行的整合
消息不丢失是我们使用消息队列的一个大前提,我们还是看整个结构图
要保证消息的不丢失,一是要保证我们生产的消息确定是给到消息队列,二是我们的消息队列收到消息确定存储到本地,三是消费者接收到消息是否消费成功,Broker得到了消费者消费成功的通知.从而服务器得到相应的通知.
生产者发送消息至Broker
,需要处理Broker
的响应,不论是同步还是异步发送消息,同步和异步回调都需要做好异常的处理,进而妥善的处理响应,如果Broker
返回写入失败等错误消息,需要重试发送。当多次发送失败需要作报警,日志记录等。这样就能保证在生产消息阶段消息不会丢失。
存储消息阶段需要在消息刷盘之后再给生产者响应,假设消息写入缓存中就返回响应,那么机器突然断电这消息就没了,而生产者以为已经发送成功了。如果Broker
是集群部署,有多副本机制,即消息不仅仅要写入当前Broker
,还需要写入副本机中。那配置成至少写入两台机子后再给生产者响应。这样基本上就能保证存储的可靠了。
消费者拿到消息,一定是消费消息成功再给Broker进行返回消费成功.所以只要我们在消息业务逻辑处理完成之后再给Broker
响应,那么消费阶段消息就不会丢失。
我们有时候回应为网络的原因会多次向消息队列进行发送消息,但是这就回引起消息的重复,但是这种网络波动的原因引起的问题我们是很难进行控制的,但是我们可以在我们业务中进行处理重复的消息.(曲线救国)
使用幂等进行处理重复消息
幂等是数学上的概念,我们就理解为同样的参数多次调用同一个接口和调用一次产生的结果是一致的。因此需要改造业务处理逻辑,使得在重复消息的情况下也不会影响最终的结果。我们就可以针对这个问题进行引入版本号,比较版本号和数据库中消息的版本号,或者给我们插入的消息设置唯一约束.具体的业务看业务细节.
消息的堆积往往是因为生产者的生产速度与消费者的消费速度不匹配。有可能是因为消息消费失败反复重试造成的,也有可能就是消费者消费能力弱,渐渐地消息就积压了。
1. 因此我们需要先定位消费慢的原因,如果是bug
则处理 bug
,如果是因为本身消费能力较弱,我们可以优化下消费逻辑,比如之前是一条一条消息消费处理的,这次我们批量处理,比如数据库的插入,一条一条插和批量插效率是不一样的。
2. 假如逻辑我们已经都优化了,但还是慢,那就得考虑水平扩容了,增加队列数和消费者数量,注意队列数一定要增加,不然新增加的消费者是没东西消费的。
3. 在处理的逻辑中计入多线程,使用线程池实现多线程下的消费信息.
以上就是有关消息队列的一些初步的认识,为我们接下来的项目提供支持.
上文参照了一个很牛的博主,大家可以访问这个博主,真的很有意思!!!,好啦本文内容就到这里,请多多关注后续实战项目.
博主CSDN链接: