Skip to content
On this page

消息队列基础

概念理解

目前,业界通常有两种方式来实现系统间的通信:

  1. 基于远程调用:即 RPC 调用,客户端不需要知道调用的具体实现细节,只需直接调用实际存在于远程计算机上的某个对象即可,但调用方式看起来和调用本地应用程序中的对象一样。
  2. 基于消息队列:指由应用中的某个系统负责发送消息,由关心这条消息的相应系统负责接收消息,并在收到消息后进行各自系统内的业务处理。消息在被发送后可以立即返回,由消息队列负责消息的传递,消息发布者只管发而不管谁来取,消息使用者则只管取而不管是谁发布的,发布者与使用者都无需知道对方的存在。

如果是一个业务被拆分成多个子业务部署在不同服务器,就是分布式应用;如果是同一业务部署在多个服务器,就是集群。

典型使用场景

  • 异步处理:消息本身可以使入队的系统直接返回,所以实现了程序的异步操作,因此只要适合于异步的场景都可以使用消息队列来实现,如邮件、短信的发送

  • 解耦:在各模块之间增加一个中间层来实现解耦,也方便以后的扩展

    • 使用消息队列后,入队的系统和出队的系统没有直接的关系,入队系统和出队系统其中一套系统崩溃的时候,都不会影响到另一个系统的正常运转
  • 流量削峰:将短时间高并发的请求持久化,然后逐步处理,从而削平高峰期的并发流量,改善系统性能

    • 这种场景最经典的就是秒杀抢购,这种情况会出现很大的流量剧增,大量的需求集中在短短的几秒内,对服务器的瞬间压力非常大,而我们配合缓存使用消息队列能非常有效的顶住瞬间访问量,防止服务器顶不住而崩溃
  • 日志收集:利用消息队列产品在接收和持久化消息方面的高性能,引入消息队列快速接收日志消息,避免因写入日志时的某些故障导致业务系统访问阻塞、请求延迟等问题

  • 事务最终一致性:通过增加事件表来解决数据库操作和消息队列操作不在同一事务的问题,实现事务的最终一致性

  • 数据冗余:比如经常做订单系统,后续需要严格的转换和记录,消息队列可以把这些数据持久化存储在队列中,然后由订单处理程序进行获取,后续处理完成之后再把这条记录删除,保证每条记录都能处理完成

  • 扩展性:比如订单入队之后或许会有财务系统进行处理,但是后期我想加配货系统,我只需要让配货系统订阅消息队列就可以了,这样就很容易扩展

  • 排序保证:这种情况指的是在有些场景下数据处理顺序是非常重要的,这种情况非常适合队列处理,因为队列本身就可以做成单线程的单进单出的系统,从而保证数据按照顺序进行处理

常见的队列介质

  • MySQL:可靠性高、易实现,速度慢
  • Redis:速度快,单条大消息包时效率低。redis 提供了 list,适合做消息队列,但是 redis 有一个问题,消息包过大的时候,效率就慢了,一般单条内容都不大
  • 消息系统:专业性强、可靠,但学习成本高,如 RabbitMQ

消息处理的触发机制

  • 死循环方式读取处理:让一个死循环的程序不断地读取一个队列,并且进行后期处理,这种方式失效性是比较强的,因为这种程序不断地扫描消息队列,因此消息队列里一旦有数据,就可以进行后续处理。但是这样会造成服务器压力,最关键的是也不会知道程序什么时候会挂掉,一旦出现故障,没办法及时恢复,这种情况比较适合做秒杀,因为秒杀的时间点比较集中,一旦有秒杀可以立即处理。

  • 定时任务:每隔几秒或者几分钟执行一次,这样做的最大好处就是把压力分开了,无论入队的系统在哪个时间点入队的峰值是多么不平均,但由于出队的系统是定时执行的,所以会把压力均摊,每个时间点的压力会差不太多,所以还是比较流行的,尤其是订单系统和物流配货系统这类的,如订单系统会把写入队列,用户就可以看到我的订单在等物流配货了,这样物流系统就会定时把订单进行汇总处理,这样压力就不会太大,唯一的缺点就是定时和间隔和数量要把握好,不要等上一个定时任务没有执行完呢,下一个定时任务又开始了,这样容易出现不可预测的问题。

    守护进程:类似于PHP-FPM和PHP-CGI进程,需要linux的shell基础。

案例

  1. 解耦案例:队列处理订单系统和配送系统 在网购的时候,提交订单之后,看到自己的订单货物在配送中,这样就参与进来一个系统是配送系统,如果我们在做架构的时候,把订单系统和配送系统设计到一起,就会出现问题。首先对于订单系统来说,订单系统处理压力较大,对于配送系统来说没必要对这些压力做及时反映,我们没必要在订单系统出现问题的情况下,同时配送系统出现问题,这时候就会同时影响两个系统的运转,我们可以解耦解决。这两个系统分开之后,我们可以通过一个队列表来实现两个系统的沟通。首先,订单系统会接收用户的订单,进行订单的处理,会把这些订单写到队列表中,这个队列表是沟通两个系统的关键,由配送系统中的定时执行的程序来读取队列表进行处理,配送系统处理之后,会把已经处理的记录进行标记,这就是流程。

  2. 流量削峰案例:Redis 的 List 类型实现秒杀

    为什么要使用 Redis 而不适用 Mysql 呢?

    因为 Redis 是基于内存,速度要快很多,而 Mysql 需要往硬盘里写,因为其他业务还要使用 Mysql,如果秒杀使用 Mysql 的话,会把 Mysql 的资源耗光,这样其他的业务在读取 Mysql 肯定出问题。另外 Redis 对数据有一个持久化作用,这样要比 Memcache 要有优势,并且数据类型要多,这次要用的就是 Redis 的 List,可以向头部或者尾部向 Redis 的链表增加元素,这样 Redis 在实现一个轻量级的队列非常有优势。

部分消息协议

AMQP 协议

该协议的内容包括数据帧处理、信道复用、内容编码、心跳检测、数据表示和错误处理。

一条消息(message)的流转过程通常是这样的:发布者(publisher)产生一条数据,发送到消息代理(broker),Broker 收到消息后,由 Broker 中的交换器(exchange,可以被理解为一个规则表:路由规则 routing key 和 消息队列 queue 的映射关系——绑定 binding)根据 Routing Key 查询投递的目标 Queue。消费者(Consumer)向 Broker 发送订阅消息时会指定自己监听哪个 Queue,当有数据到达 Queue 时,Broker 会推送数据到 Consumer。

MQTT 协议

该协议是基于客户端-服务器的消息发布/订阅传输协议,其特点是轻量、简单、开放和易实现。部分支持该协议的中间件服务器有:RabbitMQ、Apache ActiveMQ、Apache Apollo 等。

一条消息(message)的流转过程通常是这样的:先由消息发布者(publisher)发布消息到代理服务器(broker),在消息中会包含主题(topic),之后消息订阅者(subscriber)如果订阅了该主题的消息,将会收到代理服务器推送的消息。

MQTT 协议中的客户端和服务器之间一般是通过请求应答模式来通信的,即客户端发送一条命令消息给服务器,然后服务器发送一条应答命令消息给客户端,其中涉及的客户端和服务器的通信场景可分为:建立连接、发布消息、主题订阅、心跳检测和断开连接。

该协议还包含了消息通信过程中的状态存储、消息分发重试、主题过滤器、错误处理、安全认证等内容。

STOMP 协议

该协议是一个简单的文本消息传输协议,提供了一种可互操作的连接格式,允许客户端与任意消息服务器(broker)进行交互。部分支持该协议的中间件服务器有:Apache Apollo、Apache ActiveMQ、RabbitMQ等。

与其他消息协议相同,STOMP 同样包含客户端和服务器,这里的客户端既可以是消息生产者,也可以是消息消费者,而服务器就是消息数据的目的地,所有消息都会被发送到服务器。

STOMP 的客户端和服务器之间的通信是基于来实现的,每一帧都包括一个表示命令的字符串、一系列可选的帧头条目以及帧的数据内容。