RabbitMQ入门

什么是消息中间件

消息中间件(MQ)的定义其实并没有标准定义。一般认为,消息中间件属于分布式系统中一个子系统,关注于数据的发送和接收,利用高效可靠的异步消息传递机制对分布
式系统中的其余各个子系统进行集成

简单来说就是,消息中间件不生产消息,只是消息的搬运工

为什么要使用消息中间件

假设一个电商交易的场景,用户下单之后调用库存系统减库存,然后需要调用物流系统进行发货,如果交易、库存、物流是属于一个系统的,那么就是接口调用。但是随着系统的发展,各个模块越来越庞大、业务逻辑越来越复杂,必然是要做服务化和业务拆分的。这个时候就需要考虑这些系统之间如何交互,一般的处理方式就是 RPC(Remote Procedure Call)(具体实现 dubbo,SpringCloud)。系统继续发展,可能一笔交易后续需要调用多个(几个或十几个)接口来执行业务,这个时候就需要消息中间件登场来解决问题了,生产者将消息放入消息中间件,而消费者则从消息中间件中取出消息来消费

消息中间件有什么好处?

  1. 低耦合:不管是程序还是模块之间,使用消息中间件进行间接通信
  2. 异步通信能力:使得子系统之间得以充分执行自己的逻辑而无需等待
  3. 缓冲能力:消息中间件像是一个巨大的蓄水池,将高峰期大量的请求存储下来慢慢交给后台进行处理,对于秒杀业务来说尤为重要

消息中间件和 RPC 有什么区别?

RPC 和 消息中间件 的场景的差异很大程度上在于就是“依赖性”和“同步性”

  • 依赖性
    • 比如短信通知服务并不是事交易环节必须的,并不影响下单流程,不是强依赖,所以交易系统不应该依赖短信服务。如果是 RPC 调用,短信通知服务挂了,整个业务就挂了,这个就是依赖性导致的,而消息中间件则没有这个依赖性
    • 消息中间件出现以后对于交易场景可能是调用库存中心等强依赖系统执行业务,之后发布一条消息(这条消息存储于消息中间件中)。像是短信通知服务、数据统计服务等等都是依赖于消息中间件去消费这条消息来完成自己的业务逻辑
  • 同步性
    • RPC 方式是典型的同步方式,让远程调用像本地调用。消息中间件方式属于异步方式

消息中间件的使用场景

  1. 异步处理

场景说明:用户注册后,需要发送注册邮件和注册短信

为了提升响应速度,将发送注册邮件和注册短信的调用放入消息中间件交给子系统处理

  1. 应用解耦

场景说明:用户注册后,需要发送注册邮件和注册短信。传统的做法是,注册成功调用发送注册邮件和注册短信接口,但是如果邮件服务或者短信服务挂了,那么整个注册服务就挂了

引入消息中间件中件之后就不会因为邮件服务或者短信服务挂了导致整个注册服务挂了

  1. 流量削峰

流量削峰也是消息队列中的常用场景,一般在秒杀或团抢活动中使用广泛

场景说明:秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉

为解决这个问题,一般需要在应用前端加入消息队列:可以控制活动的人数;可以缓解短时间内高流量压垮应用

  1. 重要操作的日志处理

场景说明:有些重要的操作请求需要记录到数据库或者日志,将操作信息写入消息中间件,日志系统进行消费处理

  1. 消息通讯

场景说明:实现点对点消息队列,或者聊天室等

  • 点对点通讯:客户端 A 和客户端 B 使用同一队列,进行消息通讯
  • 聊天室通讯:客户端 A,客户端 B,客户端 N 订阅同一主题,进行消息发布和接收,实现类似聊天室效果

如何选择消息中间件

主流消息中间件比较,资料来源于网络

比较项 RabbitMQ RocketMQ Kafka
性能(单台) 万级(12000+) 十万级 百万计
消息持久化 支持 支持 支持
多语言支持 支持 很少 支持
社区活跃度
支持协议 多(AMQP,STOM,MQTT…) 自定义协议 自定义协议
综合评价 优点:性能较好,管理界面较丰富,在互联网公司也有较大规模的应用,有多个语言的成熟客户端
缺点:内部机制很难了解,也意味很难定制和掌控。集群不支持动态扩展
优点:模型简单,接口易用,在阿里有大规模应用,分布式系统,性能很好,版本更新很快
缺点:文档少,支持的语言较少
优点:天生分布式,性能最好,所以常见用于大数据领域
缺点:运维难度大,偶尔有数据混乱的情况,对 ZooKeeeper 强依赖,多副本机制下对带宽有一定的要求

RabbitMQ中的一些概念

AMQP协议

AMQP 是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。目标是实现一种在全行业广泛使用的标准消息中间件技术,以便降低企业和系统集成的开销,并且向大众提供工业级的集成服务,主要实现有 RabbitMQ

连接

首先作为客户端(无论是生产者还是消费者),你如果要与 RabbitMQ 通讯的话,你们之间必须创建一条 TCP 连接

连接在 RabbitMQ 原生客户端(5.0.0)版本中默认使用 java 的原生 socket,但是也支持 NIO,需要手动设置修改

信道

概念:信道是生产者/消费者与 RabbitMQ 通信的渠道。信道是建立在 TCP 连接上的虚拟连接,什么意思呢?就是说 RabbitMQ 在一条 TCP 上建立成百上千个信道来达到多个线程处理,这个 TCP 被多个线程共享,每个线程对应一个信道,信道在 RabbitMQ 都有唯一的 ID ,保证了信道私有性,对应上唯一的线程使用

为什么不建立多个 TCP 连接呢?

原因是 RabbitMQ 保证性能,系统为每个线程开辟一个 TCP 是非常消耗性能,每秒成百上千的建立销毁 TCP 会严重消耗系统,所以 RabbitMQ 选择建立多个信道(建立在 tcp 的虚拟连接)连接到 rabbit 上

虚拟主机

虚拟消息服务器,vhost,本质上就是一个 mini 版的 mq 服务器,有自己的队列、交换器和绑定,最重要的,自己的权限机制,Vhost 提供了逻辑上的分离,可以将众多客户端进行区分,又可以避免队列和交换器的命名冲突。Vhost 必须在连接时指定,RabbitMQ 包含缺省 vhost:“/”,通过缺省用户和
口令 guest 进行访问

RabbitMQ 里创建用户,必须要被指派给至少一个 vhost,并且只能访问被指派内的队列、交换器和绑定,Vhost 必须通过 RabbitMQ 的管理控制工具创建

简单来说类似于 window 系统下的分盘,不同的盘存储不同的内容,整体示例图如下:

RabbitMQ结构
RabbitMQ结构

生产者、消费者、消息

  • 生产者:消息的创建者,发送到 RabbitMQ
  • 消费者:连接到 RabbitMQ,订阅到队列上,消费消息
  • 消息:包含有效载荷和标签,有效载荷指要传输的数据,标签描述了有效载荷,并且 RabbitMQ 用它来决定谁获得消息,消费者只能拿到有效载荷,并不知道生产者是谁

交换器、队列、绑定、路由键

队列通过路由键(routing key,某种确定的规则)绑定到交换器,生产者将消息发布到交换器,交换器根据绑定的路由键将消息路由到特定队列,然后由订阅这个队列的消费者进行接收(routing_key 和 绑定键 binding_key 的最大长度是 255 个字节)

交换器类型

共有四种 Direct,Fanout,Topic,Headers,我们主要关注前3种

Direct Exchange

路由键完全匹配,消息被投递到对应的队列,direct 交换器是默认交换器,声明一个队列时,会自动绑定到默认交换器

例如:A队列绑定路由键routing key = SunnyBear,那么只有路由键为 SunnyBear 的消息可以进入A队列

Fanout Exchange

消息广播到绑定的队列,不管队列绑定了什么路由键,消息经过交换器,每个队列都有一份

Topic Exchange

通过路由键配置规则转发到队列,使用 '*''#' 通配符进行处理,使来自不同源头的消息到达同一个队列,'.' 将路由键分为了几个标识符,'*' 匹配 1 个,'#'匹配一个或多个

例如:

  • A队列绑定了路由键routing key = SunnyBear.*
  • B队列绑定了路由键routing key = SunnyBear.#
  • 那么路由键为 SunnyBear.t1 的消息会同时进入A,B队列
  • 路由键为 SunnyBear.t1.t2 的消息只会进入B队列