一、什么是消息中间件

消息队列已经逐渐成为企业IT系统内部通信的核心手段。它具有低耦合、可靠投递、广播、流量控制、最终一致性等一系列功能,成为异步RPC的主要手段之一。当今市面上有很多主流的消息中间件,如老牌的ActiveMQ、RabbitMQ,炙手可热的Kafka,阿里巴巴自主开发RocketMQ等。

RabitMQ是使用Erlang编写的一个开源的消息队列,本身支持很多的协议:AMQP,XMPP, SMTP,STOMP,也正是如此,使的它变的非常重量级,更适合于企业级的开发。同时实现了Broker架构,核心思想是生产者不会将消息直接发送给队列,消息在发送给客户端时先在中心队列排队。对路由(Routing),负载均衡(Load balance)、数据持久化都有很好的支持。多用于进行企业级的ESB(企业服务总线)整合。

消息中间件的组成

  • Broker:消息服务器,作为server提供消息核心服务。
  • Producer:消息生产者,业务的发起方,负责生产消息传输给broker。
  • Consumer:消息消费者,业务的处理方,负责从broker获取消息并进行业务逻辑处理。
  • Topic:主题,发布订阅模式下的消息统一汇集地,不同生产者向topic发送消息,由MQ服务器分发到不同的订阅者,实现消息的广播。
  • Queue:队列,PTP模式下,特定生产者向特定queue发送消息,消费者订阅特定的queue完成指定消息的接收。
  • Message:消息体,根据不同通信协议定义的固定格式进行编码的数据包,来封装业务数据,实现消息的传输。
二、应用场景

解耦

消息中间件介绍&RabitMQ环境搭建(Linux)_erlang

如上图,未使用MQ的情况下,如果B、C、D三个系统,有需求改变,那么A系统都会响应的更改,使用MQ之后,我们只需要把数据发送的MQ中,B、C、D自己根据需求去mq订阅响应的内容即可,从而达到系统耦合的结果。

异步

消息中间件介绍&RabitMQ环境搭建(Linux)_docker_02

如果某些需求场景不需要立即返回数据结果,那么就可以采用MQ的形式,对消息异步的处理,这样可以提高系统的响应速度。

削峰填谷

如果请求超过了服务器承受的最大值,那么就可能会击垮服务器,这时候,把请求通过mq队列缓存起来,来限制请求的峰值,从而达到保护服务器的目的。

三、消息中间件选型

消息中间件介绍&RabitMQ环境搭建(Linux)_docker_03

 

四、RabitMQ环境搭建

单机模式

#拉取镜像
docker pull rabbitmq:3.9-management
#创建容器并启动
[root@bogon ~]# docker run  -d -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.9-management

登录 http://192.168.139.154:15672/#/ 默认账号密码是guest,guest

注意:如果安装过程出现这个错误,重启一下docker就可以解决了systemctl restart docker。

集群模式

安装容器

[root@bogon ~]# docker run -d --hostname rabbit1 --name rabbitmq1 -p 15672:15672 -p 5672:5672 -e RABBITMQ_ERLANG_COOKIE='rabbitmq_cookie' rabbitmq:3.9-management
[root@bogon ~]# docker run -d --hostname rabbit2 --name rabbitmq2 -p 5673:5672 --link rabbitmq1:rabbit1 -e RABBITMQ_ERLANG_COOKIE='rabbitmq_cookie' rabbitmq:3.9-management
[root@bogon ~]# docker run -d --hostname rabbit3 --name rabbitmq3 -p 5674:5672 --link rabbitmq1:rabbit1 --link rabbitmq2:rabbit2 -e RABBITMQ_ERLANG_COOKIE='rabbitmq_cookie' rabbitmq:3.9-management

添加节点

#节点1
[root@bogon ~]# docker exec -it rabbitmq1 bash
root@rabbit1:/# rabbitmqctl stop_app
RABBITMQ_ERLANG_COOKIE env variable support is deprecated and will be REMOVED in a future version. Use the $HOME/.erlang.cookie file or the --erlang-cookie switch instead.
Stopping rabbit application on node rabbit@rabbit1 ...
root@rabbit1:/# rabbitmqctl reset
RABBITMQ_ERLANG_COOKIE env variable support is deprecated and will be REMOVED in a future version. Use the $HOME/.erlang.cookie file or the --erlang-cookie switch instead.
Resetting node rabbit@rabbit1 ...
root@rabbit1:/# rabbitmqctl start_app
RABBITMQ_ERLANG_COOKIE env variable support is deprecated and will be REMOVED in a future version. Use the $HOME/.erlang.cookie file or the --erlang-cookie switch instead.
Starting node rabbit@rabbit1 ...
root@rabbit1:/# 

此时安装完成的为普通集群模式

  • Exchange 的元数据信息在所有节点上是一致的,而 Queue(存放消息的队列)的完整数据则只会存在于创建它的那个节点上。其他节点只知道这个 queue 的 metadata 信息和一个指向 queue 的 owner node 的指针;
  • RabbitMQ 集群会始终同步四种类型的内部元数据(类似索引):
  1. 队列元数据:队列名称和它的属性;
  2. 交换器元数据:交换器名称、类型和属性;
  3. 绑定元数据:一张简单的表格展示了如何将消息路由到队列;
  4. vhost元数据:为 vhost 内的队列、交换器和绑定提供命名空间和安全属性;因此,当用户访问其中任何一个 RabbitMQ 节点时,通过 rabbitmqctl 查询到的元数据信息都是相同的。

配置镜像集群模式

概念:
把队列做成镜像队列,让各队列存在于多个节点中,属于 RabbitMQ 的高可用性方案。镜像模式和普通模式的不同在于,queue和 message 会在集群各节点之间同步,而不是在 consumer 获取数据时临时拉取。

消息中间件介绍&RabitMQ环境搭建(Linux)_发送消息_04

 

name:随便取,策略名称
Pattern:^ 匹配符,只有一个^代表匹配所有
Definition:ha-mode=all 为匹配类型,分为3种模式:all(表示所有的queue)

添加一个队列

消息中间件介绍&RabitMQ环境搭建(Linux)_发送消息_05

 可见队列已经同步到其他节点上。

五、RabitMQ消息类型

消息中间件介绍&RabitMQ环境搭建(Linux)_erlang_06

第一种:点对点 生产者将消息发送到队列,然后消费者从队列中取消息依次消费,消费之后,消息出队列,本次消费结束

第二种:工作队列。又称任务队列。主要思想就是避免执行资源密集型任务时,必须等待它执行完成。我们将任务封装为消息并将其发送到队列。 在后台运行的工作进程将获取任务并最终执行作业。消息在多个消费者共享,但是一个消息只能被一个消费者消费。
总之:让多个消费者绑定到一个队列,共同消费队列中的消息。队列中的消息一旦消
费,就会消失,因此任务是不会被重复执行的 。

第三种:发布订阅、Routing(路由键)、Topics(主题)

1、1个生产者,多个消费者

2、每一个消费者都有自己的一个队列

3、生产者没有将消息直接发送到队列,而是发送到了交换机

4、每个队列都要绑定到交换机

5、生产者发送的消息,经过交换机到达队列,实现一个消息被多个消费者获取的目的。

X(Exchanges):交换机一方面:接收生产者发送的消息。另一方面:知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型direct(直连交换机,把消息交给符合指定routing key 的队列)、topic(通配符,把消息交给符合routing pattern(路由模式) 的队列)、headers 和fanout(扇形交换机或者广播,将消息交给所有绑定到交换机的队列)。Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失。

(1)订阅模型-Fanout

Fanout,也称为广播。
在广播模式下,消息发送流程是这样的:

1) 可以有多个消费者
2) 每个消费者有自己的queue(队列)
3) 每个队列都要绑定到Exchange(交换机)
4) 生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定。
5) 交换机把消息发送给绑定过的所有队列
6) 队列的消费者都能拿到消息。实现一条消息被多个消费者消

(2)订阅模型-Direct:

消息中间件介绍&RabitMQ环境搭建(Linux)_erlang_07

 

 

在Direct模型下,队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key),消息的发送方在向Exchange发送消息时,也必须指定消息的routing key。

P:生产者,向Exchange发送消息,发送消息时,会指定一个routing key。

X:Exchange(交换机),接收生产者的消息,然后把消息递交给 与routing key完全匹配的队列

C1:消费者,其所在队列指定了需要routing key 为 error 的消息

C2:消费者,其所在队列指定了需要routing key 为 info、error、warning 的消息

(3)订阅模型-Topic

Topic 类型的 Exchange 与 Direct 相比,都是可以根据 RoutingKey 把消息路由到不同的队列。只不过 Topic 类型 Exchange 可以让队列在绑定 Routing key 的时候使用通配符

通配符规则:#:匹配一个或多个词

*:匹配不多不少恰好 1 个词

六、代码

连接工具

package com.xiaojie.rabbitmq;
 
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
 
import java.io.IOException;
import java.util.concurrent.TimeoutException;
 
/**
 * @author xiaojie
 * @version 1.0
 * @description: 创建mq连接
 * @date 2021/9/24 22:54
 */
public class MyConnection {
 
    public static Connection getConnection() throws  IOException, TimeoutException {
        // 1.创建连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2.设置连接地址
        connectionFactory.setHost("192.168.139.154");
        // 3.设置端口号
        connectionFactory.setPort(5672);
        // 4.设置账号和密码
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        // 5.设置VirtualHost
        connectionFactory.setVirtualHost("/xiaojie");
        return connectionFactory.newConnection();
    }
}

 Direct模式

package com.xiaojie.rabbitmq.direct;
 
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.xiaojie.rabbitmq.MyConnection;
 
import java.io.IOException;
import java.util.concurrent.TimeoutException;
 
/**
 * @author xiaojie
 * @version 1.0
 * @description: direct生产者
 * @date 2021/9/24 23:39
 */
public class Provider {
    public static final  String EXCHANGE="my_direct_exchange";
 
    public static void main(String[] args) throws IOException, TimeoutException {
        //创建连接
        Connection connection = MyConnection.getConnection();
        //创建通道
        Channel channel = connection.createChannel();
        //生产者绑定交换机 参数1 交换机的名称。2,交换机的类型
        channel.exchangeDeclare(EXCHANGE, "direct");
        //路由键
        String routingKey="email";
        //创建消息
        String msg="direct---交换机的消息。。。。。";
        //发送消息
        channel.basicPublish(EXCHANGE, routingKey, null, msg.getBytes());
        System.out.println("生产者启动成功。。。。。");
        //关闭连接,管道
        channel.close();
        connection.close();
    }
}