消息队列的概述

什么是消息队列

  计算机科学中,消息队列(英语:Message queue)是一种进程间通信或同一进程的不同线程间的通信方式,软件的贮列用来处理一系列的输入,通常是来自用户。消息队列提供了异步的通信协议,每一个贮列中的纪录包含详细说明的资料,包含发生的时间,输入设备的种类,以及特定的输入参数,也就是说:消息的发送者和接收者不需要同时与消息队列交互。消息会保存在队列中,直到接收者取回它。
我们可以把消息队列看作是一个存放消息的容器,当我们需要使用消息的时候,直接从容器中取出消息供自己使用即可。
  消息队列是分布式系统中重要的组件之一。使用消息队列主要是为了通过异步处理提高系统性能和削峰、降低系统耦合性。我们知道队列 Queue 是一种先进先出的数据结构,所以消费消息时也是按照顺序来消费的。

消息队列的作用

三大作用:

  • 通过异步处理提高系统性能(减少响应所需时间)
  • 削峰/限流
  • 降低系统耦合性

通过异步处理提高系统性能

 将用户的请求数据存储到消息队列之后就立即返回结果。随后,系统再对消息进行消费。
因为用户请求数据写入消息队列之后就立即返回给用户了,但是请求数据在后续的业务校验、写数据库等操作中可能失败。因此使用消息队列进行异步处理之后,需要适当修改业务流程进行配合,比如用户在提交订单之后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功,以免交易纠纷。这就类似我们平时手机订火车票和电影票。

消峰/限流

在这里插入图片描述

 先将短时间高并发产生的事务消息存储在消息队列中,然后后端服务再慢慢根据自己的能力去消费这些消息,这样就避免直接把后端服务打垮掉。

降低系统耦合性

 使用消息队列还可以降低系统耦合性。我们知道如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性无疑更好一些。
在这里插入图片描述
 从上图可以看出消息发送者和消息接收者之间没有直接耦合,消息发送者将消息发送至分布式消息队列即结束对消息的处理,消息接收者从分布式消息队列中获取该消息后进行后续处理,并不需要知道该消息从何而来。对新增业务,只要对该类消息感兴趣,就可以订阅该消息,对原有的系统和业务没有任何的影响。从而实现网站业务的可扩展性设计。
 另外为了避免消息队列服务器宕机造成消息的丢失,会将成功发送到消息队列的消息存储在消息生产者服务器上,等消息真正被消费者服务器处理后才会删除消息。在消息队列服务器宕机后,生产者服务器会选择分布式消息队列集群中的其他服务器发布消息。

使用消息队列带来的一些问题

  1. 系统可用性降低: 系统可用性在某种程度上降低,为什么这样说呢?在加入MQ之前,你不用考虑消息丢失或者说MQ挂掉等等的情况,但是,引入MQ之后你就需要去考虑了!
  2. 系统复杂性提高: 加入MQ之后,你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题!
  3. 一致性问题: 我上面讲了消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度。但是,万一消息的真正消费者并没有正确消费消息怎么办?这样就会导致数据不一致的情况了!

解决方式

  1. 可用性:
     实际项目中发送MQ消息,如果不使用集群,其中MQ机器出现故障宕机,那么MQ消息就不能发送,系统就会崩溃,所以我们需要集群MQ。各种消息中间间的集群方式不同。下面以ActiveMQ的集群为例(Zookeeper+ActiveMQ)如图:
    在这里插入图片描述
     服务器向Zookeeper注册时,Zookeeper会分配序列号,我们认为序列号小的那个就是主服务器,序列号大的那个就是备用服务器。
     当我们的客户端(Web server)需要访问服务时,需要连接Zookeeper,获得指定目录下的临时节点列表,也就是已经注册的服务器信息,获得序列号小的那台主服务器的地址,进行后续的访问操作,以达到总是访问主服务器的目的。当主服务器发生故障,Zookeeper从指定目录下删除对应的临时节点,同时通知关心这一变化的所有客户端,高效的传播这一信息,当下一个请求来的时候,还是连接Zookeeper,但是此时其实是访问备用MQ服务器。

  2. 对于复杂性问题
    如何保证消息不被重复消费
     要回答这个问题, 必须要知道为什么会出现消息被重复消费,大多都是因为网络不通导致,确认信息没有传送到消息队列,导致消息队列不知道自己已经消费过该消息了,再次将消息分发给其他的消费者。解决该问题有下面三种思路:
    如果消息是做数据库的插入操作,给这个消息做一个唯一的主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据。
    如果这个消息是做redis的set操作,那么不用解决,因为无论set几次结果都是一样的。set操作本来就是幂等操作。
    如果上面两种情况都不行,那么准备一个第三方来做消费记录,以redis为例,给消息分配一个全局id,只要消费过该消息,将<id,message>以k-v的形式写入redis。那么消费者开始消费前,先去redis中查看有没有消费记录即可。
    如何保证消息的可靠传输
     保证消息的可靠传输就是防止生产者弄丢数据,消息队列弄丢数据,消费者弄丢数据而已。
     消息队列一般都会持久化到磁盘,生产者数据丢失的话MQ事务会进行回滚,可以尝试重新发送。消费者丢失的话一般都是采用了自动确认消息模式导致消费信息被删,只要改为手动的就好,也就是说消费者消费完之后,调用一个MQ的确认方法就可以。
    如何保证从消息队列里拿到的数据按顺序执行
    通过算法,将需要保持先后顺序的消息放到同一个消息队列中,然后只用一个消费者去消费该队列。
    rabbitMQ:拆分多个queue,每个queue一个consumer,就是多一些queue而已。或者就是一个queue对应一个consumer,然后这个consumer内部用内存队列做排队,然后分发给底层不同的worker来处理
    kafka:一个topic,一个partition,一个consumer,内部单线程消费,写n个内存queue,然后N个线程分别消费一个内存queue即可。
    数据是通过push还是pull方式给到消费段,各自有什么利弊
    push模型实时性能好,但是因为状态维护等问题,难以应用到消息中间件的实践中,因为
    在Broker端需要维护consumer的状态,不好适用Broker去支持大量的consumer的场景。
    consumer的消费速度是不一致的,Broker进行推送难以处理不同consumer的状况
    Broker无法处理consumer无法读取消息的情况,因为不知道consumer的宕机是短暂的还是永久的
    另外推送消息(量可能会很大)也会加重consumer的负载或者压垮consumer
    pull模式实现起来相对简单一点,但是实时性取决于轮训的频率,在对实时性要求高的场景不适合使用。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值