JavaEE 企业级分布式高级架构师(十六)RabbitMQ消息中间件(1)

RabbitMQ介绍

RabbitMQ是什么?

  • 主要是一个开源的消息代理和队列服务器,用来通过普通协议在完全不同的应用之间共享数据,它主要是使用 Erlang 语言进行编写的,并且是基于 AMQP 协议。
  • 优点:与 Spring AMQP 完美结合,拥有丰富的 API。集群模式相当丰富,提供表达式配置,HA模式,镜像队列模型。

使用场景

  • 在我们秒杀抢购商品的时候,系统会提醒我们稍等排队中,而不是像几年前一样页面卡死或报错给用户。
  • 像这种排队结算就用到了消息队列机制,放入通道里面一个一个结算处理,而不是某个时间断突然涌入大批量的查询新增把数据库给搞宕机,所以RabbitMQ本质上起到的作用就是削峰填谷,为业务保驾护航。

AMQP

Advanced Message Queuing Protocol 高级消息协议。

AMQP协议模型

在这里插入图片描述

核心概念

  • Server:服务端(broker)
  • Virtual Host:虚拟主机。作用是做消息的逻辑隔离。
  • ConnectionFactory:连接管理器。应用程序与 Rabbit 之间建立连接的管理器,程序代码中使用。
  • Connection:连接。主要是应用于服务器之间的连接。
  • Channel:信道。主要是进行消息的读写操作,每个客户端都可以建立多个Channel,每个 Channel 代表一个会话任务。
  • Message:由 Properties 和 Body 两部分组成,前者主要是对消息的处理,后者是消息主体。
  • Exchange:交换机。用于接收、分配消息。
  • Queue:用于存储生产者的消息。
  • Binding:绑定。主要是讲 Exchange 中的信息与 Message Queue 中的队列进行绑定。
  • Routing Key:路由键,用于把生产者的数据分配到交换机上。
  • BindingKey:绑定键,用于把交换机的消息绑定到队列上。

RabbitMQ工作原理

RabbitMQ架构图

在这里插入图片描述

RabbitMQ消息流转

在这里插入图片描述

  • 连接(Connection):消费者或者生产者与消息中间件建立的tcp连接;
  • 频道(Channel):也叫信道,tcp连接建立之后,必须先在连接上开频道,才能进行其他操作;
  • 登录(Login):建立频道之后,要登录到特定的虚拟机,一组虚拟机持有一组交换机和队列,其他虚拟机用户无法访问当前用户对应的虚拟机中的交换机和队列;
  • 交换机(Exchange):在 RabbitMQ 消息中间件启动时就会创建一个默认的交换机(当然也可以人为创建),与连接无关,负责整个消息中间件中消息的投递;交换机不会存储消息,如果没有任何队列与之绑定,那么交换机会丢弃收到的消息;
  • 队列(Queue):用来存储交换机投递过来的消息,通过路由键与交换机绑定,进行消息的持久化存储;
    队列由消费者或者生产者连上消息中间件后自行创建,人为指定队列名称,如果当前创建的队列 RabbitMQ 上已经存在,RabbitMQ 不会重复创建;
  • 路由键(RoutingKey):交换机和队列进行消息投递的识别码,人为指定。

RabbitMQ安装和使用

安装

  • 官网地址:https://www.rabbitmq.com/

安装包安装

  • 安装版本:rabbitmq-server-3.8.5
  • 卸载
/sbin/service rabbitmq-server stop
yum list | grep rabbitmq
yum -y remove rabbitmq-server.noarch
  
yum list | grep erlang
yum -y remove erlang-*
yum remove erlang.x86_64
rm -rf /usr/lib64/erlang
rm -rf /var/lib/rabbitmq
  • yum安装最新版本的Erlang:
wget https://packages.erlang-solutions.com/erlang-solutions-1.0-1.noarch.rpm
sudo rpm -Uvh erlang-solutions-1.0-1.noarch.rpm --force --nodeps
sudo yum install erlang
  • 手动安装指定版本的Erlang:下载地址:https://dl.bintray.com/rabbitmq-erlang/rpm/erlang/
    在这里插入图片描述
  • 下载后上传服务器,执行安装命令
rpm -ivh erlang-23.0.2-1.el8.x86_64.rpm
# 验证是否安装成功
erl -v
  • 下载RabbitMQ安装包:
    在这里插入图片描述
  • 将安装包下载上传到服务器,执行安装命令
yum install rabbitmq-server-3.8.5-1.el7.noarch.rpm

快速安装脚本

  • 使用 PackageCloud 安装
    在这里插入图片描述
  • RabbitMQ 服务器安装脚本
curl -s https://packagecloud.io/install/repositories/rabbitmq/rabbitmq-server/script.rpm.sh | sudo bash

在这里插入图片描述

  • Erlang 安装脚本
curl -s https://packagecloud.io/install/repositories/rabbitmq/erlang/script.rpm.sh | sudo bash

在这里插入图片描述

查看下载的仓库

ll /etc/yum.repos.d/

在这里插入图片描述

cat /etc/yum.repos.d/rabbitmq_erlang.repo 

在这里插入图片描述

cat /etc/yum.repos.d/rabbitmq_rabbitmq-server.repo

在这里插入图片描述

执行安装

  • 安装 erlang
yum install erlang

在这里插入图片描述
在这里插入图片描述

  • 安装 rabbitmq-server
yum install rabbitmq-server

在这里插入图片描述
在这里插入图片描述

启动与访问

  • 启动服务、停止服务和查看服务是否运行
/sbin/service rabbitmq-server start &  # & 表示后台启动
/sbin/service rabbitmq-server stop
ps -ef | grep rabbitmq
lsof -i:5672

在这里插入图片描述

  • 开启后台管理插件:如果你想要进入后台管理页面,一定要记得先开启该插件
rabbitmq-plugins enable rabbitmq_management

在这里插入图片描述

  • 修改配置文件:目的主要是配置管理后台的登录名密码,修改为:{loopback_users, [guest]}
vim /usr/lib/rabbitmq/lib/rabbitmq_server-3.7.15/ebin/rabbit.app

在这里插入图片描述

  • rabbitmq server 端口:5672
  • rabbitmq登录控制台端口:15672
  • 访问后台管理页面:http://192.168.254.106:15672,用户名和密码都是 guest
    在这里插入图片描述
  • 首页:http://192.168.254.106:15672/
    在这里插入图片描述

使用

命令行基本操作

  • 启动应用:rabbitmqctl start_app
  • 停止应用:rabbitmqctl stop_app
  • 查看状态:rabbitmqctl status
  • 添加用户:rabbitmqctl add_user username password
  • 用户列表:rabbitmqctl list_users
  • 移除用户:rabbitmqctl delete_user username
  • 设置用户权限:rabbitmqctl set_permissions -p vhostpath username
  • 移除用户权限:rabbitmqctl clear_permissions -p vhostpath username
  • 查看用户权限:rabbitmqctl list_user_permissions username
  • 重置密码:rabbitmqctl change_password username newpassword
  • 创建虚拟主机:rabbitmqctl add_vhost vhostpath
  • 查看所有虚拟主机:rabbitmqctl list_vhosts
  • 列出虚拟主机上所有权限:rabbitmqctl list_permissions -p vhostpath
  • 删除虚拟主机:rabbitmqctl delete_vhost vhostpath
  • 查看所有队列:rabbitmqctl list_queues
  • 清除队列里的消息:rabbitmqctl -p vhostpath purge_queue blue

命令行进阶操作

  • 移除所有数据:rabbitmqctl reset 【要在 rabbitmqctl stop_app 之后使用】
  • 组成集群命令:rabbitmqctl join_cluster [–ram]
  • 查看集群状态:rabbitmqctl cluster_status
  • 修改集群节点的存储形式:rabbitmqctl change_cluster_node_type disc | ram
  • 忘记(摘除)节点:rabbitmqctl forget_cluster_node [–offline]
  • 修改节点名称:rabbitmqctl rename_cluster_node oldnode1 newnode1 [oldnode2] [newnode2…]

工作台操作

也就是在 rabbitmq 的后台管理页面对**连接(Connections)、信道(Channels)、交换机(Exchanges)、队列(Queues)和用户(Admin)**进行管理操作。

RabbitMQ原生Java API操作

思路

  • 获取连接工厂 ConnectionFactory
  • 通过连接工厂,获取一个 Connection 连接对象
  • 声明一个 Exchange 交换机(新版本)
  • 通过 Connection,获取信道 Channel,主要用于发送和接收消息
  • 将消息存储到 Message Queue 队列中
  • 两个角色:生产者 Producer 和 消费者 Consumer

代码实现

  • 创建工程:新建一个 Spring Initializr 工程,命名为 01-rabbitmq-helloworld。
  • 添加依赖
<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.7.2</version>
</dependency>
  • 基础信息
public interface BaseInfo {
    String RABBITMQ_HOST_IP = "192.168.254.106";
    int RABBITMQ_HOST_PORT = 5672;
    String RABBITMQ_VIRTUAL_HOST = "/";
    String RABBITMQ_FIRST_EXCHANGE = "first-exchange";
    String RABBITMQ_FIRST_ROUTING_KEY = "first-msg";
    String RABBITMQ_FIRST_QUEUE_NAME = "first-queue";

    String RABBITMQ_SECOND_EXCHANGE = "second-exchange";
    String RABBITMQ_SECOND_ROUTING_KEY = "second-msg";
}

实现一

  • 新建 Producer 生产者类
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.util.concurrent.TimeUnit;

import static com.yw.rabbitmq.common.BaseInfo.*;

public class FirstProducer {
    public static void main(String[] args) throws Exception {
        // 1. 创建一个 ConnectionFactory 工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(RABBITMQ_HOST_IP);
        factory.setPort(RABBITMQ_HOST_PORT);
        factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);

        // 2. 创建一个 Connection 连接
        Connection conn = factory.newConnection();

        // 3. 获取一个 Channel 信道
        Channel channel = conn.createChannel();

        // 4. 声明一个交换机
        // 参数一:指定交换机名称
        // 参数二:BuiltinExchangeType.DIRECT : 交换机类型是直连
        // 参数三:指明是否消息是否要持久化到队列上
        channel.exchangeDeclare(RABBITMQ_FIRST_EXCHANGE, BuiltinExchangeType.DIRECT, true);

        // 5. 通过 Channel 发送消息
        String hello = "Hello World";
        for (int i = 0; i < 100; i++) {
            String msg = hello + i;
            channel.basicPublish(RABBITMQ_FIRST_EXCHANGE, RABBITMQ_FIRST_ROUTING_KEY, null, msg.getBytes());
            System.out.println(msg);
            TimeUnit.MILLISECONDS.sleep(100);
        }

        // 6. 关闭资源
        channel.close();
        conn.close();
    }
}
  • 新建 Consumer 消费者类
import com.rabbitmq.client.*;

import static com.yw.rabbitmq.common.BaseInfo.*;
import static java.nio.charset.StandardCharsets.UTF_8;

public class FirstConsumer {
    public static void main(String[] args) throws Exception {
        // 1. 创建一个 ConnectionFactory 工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(RABBITMQ_HOST_IP);
        factory.setPort(RABBITMQ_HOST_PORT);
        factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);

        // 2. 创建一个 Connection 连接
        Connection conn = factory.newConnection();

        // 3. 获取一个 Channel 信道
        Channel channel = conn.createChannel();

        // 4. 声明一个交换机
        channel.exchangeDeclare(RABBITMQ_FIRST_EXCHANGE, BuiltinExchangeType.DIRECT, true);

        // 5. 声明一个队列
        // 参数一:队列名
        // 参数二:指明是否消息要持久化到队列上,关机后消息也不会丢
        // 参数三:是否独占,类似加了一把锁,只有一个 Channel 能监听,保证了消费顺序
        // 参数四:是否自动删除,如果队列跟交换机没有绑定关系,是否自动删除
        // 参数五:扩展参数
        channel.queueDeclare(RABBITMQ_FIRST_QUEUE_NAME, true, false, false, null);

        // 6. 队列绑定
        channel.queueBind(RABBITMQ_FIRST_QUEUE_NAME, RABBITMQ_FIRST_EXCHANGE, RABBITMQ_FIRST_ROUTING_KEY);

        // 7. 异步获取投递消息
        // 就相当于根据 路由key 获取信道中的数据
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), UTF_8);

            System.out.println(" [x] Received '" + message + "'");
            try {
                System.out.println(message);
            } finally {
                System.out.println(" [x] Done!");
            }
        };
        boolean autoAck = true;

        channel.basicConsume(RABBITMQ_FIRST_QUEUE_NAME, autoAck, deliverCallback, consumerTag -> {
        });

//        // 8. 关闭资源
//        channel.close();
//        conn.close();
    }
}

实现二

  • 新建 Producer 生产者类
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.util.concurrent.TimeUnit;

import static com.yw.rabbitmq.common.BaseInfo.*;

public class SecondProducer {
    public static void main(String[] args) throws Exception {
        // 1. 创建一个 ConnectionFactory 工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(RABBITMQ_HOST_IP);
        factory.setPort(RABBITMQ_HOST_PORT);
        factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);

        // 2. 创建一个 Connection 连接
        Connection conn = factory.newConnection();

        // 3. 获取一个 Channel 信道
        Channel channel = conn.createChannel();

        // 4. 声明一个交换机
        channel.exchangeDeclare(RABBITMQ_SECOND_EXCHANGE, BuiltinExchangeType.DIRECT, true);

        // 5. 通过 Channel 发送消息
        String hello = "==>> Hello World";
        for (int i = 0; i < 100; i++) {
            String msg = hello + i;
            channel.basicPublish(RABBITMQ_SECOND_EXCHANGE, RABBITMQ_SECOND_ROUTING_KEY, null, msg.getBytes());
            System.out.println(msg);
            TimeUnit.MILLISECONDS.sleep(100);
        }

        // 6. 关闭资源
        channel.close();
        conn.close();
    }
}
  • 新建 Consumer 消费者类
import com.rabbitmq.client.*;

import java.io.IOException;

import static com.yw.rabbitmq.common.BaseInfo.*;
import static java.nio.charset.StandardCharsets.UTF_8;

public class SecondConsumer {
    public static void main(String[] args) throws Exception {
        // 1. 创建一个 ConnectionFactory 工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(RABBITMQ_HOST_IP);
        factory.setPort(RABBITMQ_HOST_PORT);
        factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);

        // 2. 创建一个 Connection 连接
        Connection conn = factory.newConnection();

        // 3. 获取一个 Channel 信道
        Channel channel = conn.createChannel();

        // 4. 声明一个交换机
        channel.exchangeDeclare(RABBITMQ_SECOND_EXCHANGE, BuiltinExchangeType.DIRECT, true);

        // 5. 声明一个队列
        String queueName = channel.queueDeclare().getQueue();

        // 6. 队列绑定
        channel.queueBind(queueName, RABBITMQ_SECOND_EXCHANGE, RABBITMQ_SECOND_ROUTING_KEY);

        // 7. 消费消息
        while (true) {
            boolean autoAck = false;
            String consumerTag = "";
            channel.basicConsume(queueName, autoAck, consumerTag, new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String routingKey = envelope.getRoutingKey();
                    String contentType = properties.getContentType();
                    System.out.println("消费的路由键:" + routingKey + " 消费的内容类型:" + contentType);

                    long deliveryTag = envelope.getDeliveryTag();

                    // 确认消息
                    channel.basicAck(deliveryTag, false);

                    System.out.println("消费的消息体:" + new String(body, UTF_8));
                }
            });
        }
    }
}

RabbitMQ核心概念

Exchange交换机

  • 作用:接收消息,并根据路由键转发消息所绑定的队列

图示

在这里插入图片描述

  • 蓝色框:生产消息,经过交换机,到达队列
  • 绿色框:消费者,从队列中获取消息进行消费
  • 红色框:RabbitMQ Server
  • 黄色框:交换机绑定队列

交换机属性

Type属性
  • direct:直连
  • topic:主题
  • fanout:广播
  • headers:头消息匹配(不常用)
常见属性
  • Durability:是否持久化【true、false】
  • Auto delete:当 Exchange 上所有队列都删除后,它也将自动被删除。拓展:在队列上,找不到关联的交换机,队列也要被清除。
  • Internal:当前 Exchange 是否只在 RabbitMQ 内部使用,一般保持默认值 false【较少使用】。除非熟悉 Erlang 语言,可自定义扩展插件。
  • Arguments:扩展参数,用于扩展 AMQP 协议定制使用。

Direct Exchange直连交换机

  • 作用:发送到 Direct Exchange 的消息,都会被转发到 RouteKey 中指定的 Queue 中。就是一对一的作用。
  • 注意:Direct 模式可以使用 RabbitMQ 自带的 Exchange:default Exchange,所以不需要将 Exchange 进行任何绑定(binding)操作,消息传递时,RouteKey 必须完全匹配才会被队列接收,否则该消息会被抛弃。
  • 图示
    在这里插入图片描述
案例
  • 生产者Producer
public class DirectProducer {
    public static void main(String[] args) throws Exception {
        // 1. 创建一个 ConnectionFactory 工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(RABBITMQ_HOST_IP);
        factory.setPort(RABBITMQ_HOST_PORT);
        factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);

        // 2. 创建一个 Connection 连接
        Connection conn = factory.newConnection();

        // 3. 获取一个 Channel 信道
        Channel channel = conn.createChannel();

        // 4. 声明一个交换机
        channel.exchangeDeclare(RABBITMQ_DIRECT_EXCHANGE, BuiltinExchangeType.DIRECT, true);

        // 5. 通过 Channel 发送消息
        String msg = "hello, direct-exchange";
        channel.basicPublish(RABBITMQ_DIRECT_EXCHANGE, RABBITMQ_DIRECT_ROUTING_KEY, null, msg.getBytes());

//        // 6. 关闭资源
//        channel.close();
//        conn.close();
    }
}
  • 消费者Consumer
public class DirectConsumer {
    public static void main(String[] args) throws Exception {
        // 1. 创建一个 ConnectionFactory 工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(RABBITMQ_HOST_IP);
        factory.setPort(RABBITMQ_HOST_PORT);
        factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);

        // 2. 创建一个 Connection 连接
        Connection conn = factory.newConnection();

        // 3. 获取一个 Channel 信道
        Channel channel = conn.createChannel();

        // 4. 声明交换机
        channel.exchangeDeclare(RABBITMQ_DIRECT_EXCHANGE, BuiltinExchangeType.DIRECT, true);
        
        // 5. 声明&绑定队列
        String queueName = channel.queueDeclare().getQueue();
        channel.queueBind(queueName, RABBITMQ_DIRECT_EXCHANGE, RABBITMQ_DIRECT_ROUTING_KEY);
        
        // 6. 消费消息
        while (true) {
            boolean autoAck = false;
            String consumerTag = "";
            channel.basicConsume(queueName, autoAck, consumerTag, new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    // 获取 routingKey & contentType
                    String routingKey = envelope.getRoutingKey();
                    String contentType = properties.getContentType();
                    System.out.println("消费的 Routing Key:" + routingKey + " \n消费的 Content Type:" + contentType);

                    // 获取传送标签
                    long deliveryTag = envelope.getDeliveryTag();

                    // 确认消息
                    channel.basicAck(deliveryTag, false);

                    System.out.println("消费的 Body:");
                    String bodyMsg = new String(body, UTF_8);
                    System.out.println(bodyMsg);
                }
            });
        }
    }
}
  • BaseInfo.java
public interface BaseInfo {
    String RABBITMQ_HOST_IP = "192.168.254.106";
    int RABBITMQ_HOST_PORT = 5672;
    String RABBITMQ_VIRTUAL_HOST = "/";

    String RABBITMQ_DIRECT_EXCHANGE = "direct-exchange";
    String RABBITMQ_DIRECT_ROUTING_KEY = "direct-msg";
}

Topic Exchange主题交换机

  • 作用:发送到 Topic Exchange(主题交换机) 上的消息,会被指定给主题相关的 Queue(队列)上。主要是将 RouteKey 和设置的 Topic 进行模糊匹配。
  • 注意:可以使用通配符进行模糊匹配。符号 # 匹配一个或多个词,如:hello.# → hello.girl.cuihua。符号 * 匹配一个词,如:hello.* → hello.cuihua
  • 图示
    在这里插入图片描述
案例
  • 生产者Producer
public class TopicProducer {
    public static void main(String[] args) throws Exception {
        // 1. 创建一个 ConnectionFactory 工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(RABBITMQ_HOST_IP);
        factory.setPort(RABBITMQ_HOST_PORT);
        factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);

        // 2. 创建一个 Connection 连接
        Connection conn = factory.newConnection();

        // 3. 获取一个 Channel 信道
        Channel channel = conn.createChannel();

        // 4. 声明一个交换机
        channel.exchangeDeclare(RABBITMQ_TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC, true);

        // 5. 发布消息
        String msg = "hello, topic-exchange";
        channel.basicPublish(RABBITMQ_TOPIC_EXCHANGE, RABBITMQ_TOPIC_ROUTING_KEY1, null, msg.getBytes());
        channel.basicPublish(RABBITMQ_TOPIC_EXCHANGE, RABBITMQ_TOPIC_ROUTING_KEY2, null, msg.getBytes());
        channel.basicPublish(RABBITMQ_TOPIC_EXCHANGE, RABBITMQ_TOPIC_ROUTING_KEY3, null, msg.getBytes());
    }
}
  • 消费者Consumer
public class TopicConsumer {
    public static void main(String[] args) throws Exception {
        // 1. 创建一个 ConnectionFactory 工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(RABBITMQ_HOST_IP);
        factory.setPort(RABBITMQ_HOST_PORT);
        factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);

        // 2. 创建一个 Connection 连接
        Connection conn = factory.newConnection();

        // 3. 获取一个 Channel 信道
        Channel channel = conn.createChannel();

        // 4. 声明交换机
        channel.exchangeDeclare(RABBITMQ_TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC, true);

        // 5. 声明&绑定队列
//        String routingKey = "topic-msg.*";
        String routingKey = "topic-msg.#";
        String queueName = channel.queueDeclare().getQueue();
        channel.queueBind(queueName, RABBITMQ_TOPIC_EXCHANGE, routingKey);

        // 6. 消费消息
        while (true) {
            boolean autoAck = false;
            String consumerTag = "";
            channel.basicConsume(queueName, autoAck, consumerTag, new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    // 获取 routingKey & contentType
                    String routingKey = envelope.getRoutingKey();
                    String contentType = properties.getContentType();
                    System.out.println("消费的 Routing Key:" + routingKey + " \n消费的 Content Type:" + contentType);

                    // 获取传送标签
                    long deliveryTag = envelope.getDeliveryTag();

                    // 确认消息
                    channel.basicAck(deliveryTag, false);

                    System.out.println("消费的 Body:");
                    String bodyMsg = new String(body, UTF_8);
                    System.out.println(bodyMsg);
                }
            });
        }
    }
}
  • 修改BaseInfo.java
    在这里插入图片描述

Fanout Exchange广播交换机

  • 作用:直接广播,不走路由键,直接将队列绑定到交换机上。发送到交换机的消息,全都会被转发到与该交换机绑定的队列上。转发消息是最快的。
  • 图示
    在这里插入图片描述
案例
  • 生产者Producer
public class FanoutProducer {
    public static void main(String[] args) throws Exception {
        // 1. 创建一个 ConnectionFactory 工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(RABBITMQ_HOST_IP);
        factory.setPort(RABBITMQ_HOST_PORT);
        factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);

        // 2. 创建一个 Connection 连接
        Connection conn = factory.newConnection();

        // 3. 获取一个 Channel 信道
        Channel channel = conn.createChannel();

        // 4. 声明一个交换机
        channel.exchangeDeclare(RABBITMQ_FANOUT_EXCHANGE, BuiltinExchangeType.FANOUT, true);

        // 5. 发布消息
        for (int i = 0; i < 10; i++) {
            String msg = "hello, fanout-exchange" + i;
            // 不设置路由键,或者随便设置
            String routingKey = "";
            channel.basicPublish(RABBITMQ_FANOUT_EXCHANGE, routingKey, null, msg.getBytes());
        }
    }
}
  • 消费者Consumer
public class FanoutConsumer {
    public static void main(String[] args) throws Exception {
        // 1. 创建一个 ConnectionFactory 工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(RABBITMQ_HOST_IP);
        factory.setPort(RABBITMQ_HOST_PORT);
        factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);

        // 2. 创建一个 Connection 连接
        Connection conn = factory.newConnection();

        // 3. 获取一个 Channel 信道
        Channel channel = conn.createChannel();

        // 4. 声明交换机
        channel.exchangeDeclare(RABBITMQ_FANOUT_EXCHANGE, BuiltinExchangeType.FANOUT, true);

        // 5. 声明&绑定队列
        String queueName = channel.queueDeclare().getQueue();
        channel.queueBind(queueName, RABBITMQ_FANOUT_EXCHANGE, RABBITMQ_FANOUT_ROUTING_KEY);

        // 6. 消费消息
        while (true) {
            boolean autoAck = false;
            String consumerTag = "";
            channel.basicConsume(queueName, autoAck, consumerTag, new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    // 获取 routingKey & contentType
                    String routingKey = envelope.getRoutingKey();
                    String contentType = properties.getContentType();
                    System.out.println("消费的 Routing Key:" + routingKey + " \n消费的 Content Type:" + contentType);

                    // 获取传送标签
                    long deliveryTag = envelope.getDeliveryTag();

                    // 确认消息
                    channel.basicAck(deliveryTag, false);

                    System.out.println("消费的 Body:");
                    String bodyMsg = new String(body, UTF_8);
                    System.out.println(bodyMsg);
                }
            });
        }
    }
}
  • 修改BaseInfo.java
    在这里插入图片描述

Binding绑定

  • Exchange 和 Exchange、Queue 之间的连接关系
  • Binding 中可以包含 RoutingKey 或者参数

Queue消息队列

  • 消息队列:存储消息数据
  • Durability 是否持久化:Durable 是、Transient 否
  • Auto delete:如果选 yes,代表当最后一个监听被移除之后,该 Queue 会自动被删除

Message消息

  • 服务器和应用程序之间,进行传送的数据
  • 就是一段数据,由 Properties 和 Payload(Body)组成

常用属性

  • delivery mode 消息送达模式:持久化、非内存级别的非持久化
  • headers(自定义属性)

其它属性

  • content_type:消息内容的类型
  • content_encoding:消息内容的编码格式
  • priority:消息的优先级
  • correlation_id:关联id
  • reply_to:用于指定回复的队列的名称
  • expiration:消息的失效时间
  • message_id:消息id
  • timestamp:消息的时间戳
  • type:类型
  • user_id:用户id
  • app_id:应用程序id
  • cluster_id:集群id

案例

  • 生产者Producer
public class MessageProducer {
    public static void main(String[] args) throws Exception {
        // 1. 创建一个 ConnectionFactory 工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(RABBITMQ_HOST_IP);
        factory.setPort(RABBITMQ_HOST_PORT);
        factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);

        // 2. 创建一个 Connection 连接
        Connection conn = factory.newConnection();

        // 3. 获取一个 Channel 信道
        Channel channel = conn.createChannel();

        // 4. 声明交换机
        channel.exchangeDeclare(RABBITMQ_DIRECT_EXCHANGE, BuiltinExchangeType.DIRECT, true);

        // 5. 添加headers信息
        HashMap<String, Object> headers = new HashMap<>();
        headers.put("msg-01", "hello");
        headers.put("msg-02", "world");

        // 6. 添加额外的属性信息
        AMQP.BasicProperties props = new AMQP.BasicProperties.Builder()
                .deliveryMode(2) // 持久化消息
                .contentEncoding(String.valueOf(UTF_8))
                .expiration("10000")
                .headers(headers)
                .build();

        // 5. 通过 Channel 发送消息
        String hello = "Hello, message";
        for (int i = 0; i < 100; i++) {
            String msg = hello + i;
            channel.basicPublish(RABBITMQ_DIRECT_EXCHANGE, RABBITMQ_DIRECT_ROUTING_KEY, props, msg.getBytes());
            System.out.println(msg);
            TimeUnit.MILLISECONDS.sleep(100);
        }
    }
}
  • 消费者Consumer
public class MessageConsumer {
    public static void main(String[] args) throws Exception {
        // 1. 创建一个 ConnectionFactory 工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(RABBITMQ_HOST_IP);
        factory.setPort(RABBITMQ_HOST_PORT);
        factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);

        // 2. 创建一个 Connection 连接
        Connection conn = factory.newConnection();

        // 3. 获取一个 Channel 信道
        Channel channel = conn.createChannel();

        // 4. 声明一个交换机
        channel.exchangeDeclare(RABBITMQ_DIRECT_EXCHANGE, BuiltinExchangeType.DIRECT, true);

        // 5. 声明&绑定队列
        String queueName = channel.queueDeclare().getQueue();
        channel.queueBind(queueName, RABBITMQ_DIRECT_EXCHANGE, RABBITMQ_DIRECT_ROUTING_KEY);

        // 6. 消费消息
        while (true) {
            boolean autoAck = false;
            String consumerTag = "";
            channel.basicConsume(queueName, autoAck, consumerTag, new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    // 获取 routingKey & contentType
                    String routingKey = envelope.getRoutingKey();
                    String contentType = properties.getContentType();
                    Map<String, Object> headers = properties.getHeaders();
                    System.out.println("消费的 Routing Key:" + routingKey + " \n消费的 Content Type:" + contentType);
                    System.out.println("消费的 headers:" + JSON.toJSONString(headers));

                    // 获取传送标签
                    long deliveryTag = envelope.getDeliveryTag();

                    // 确认消息
                    channel.basicAck(deliveryTag, false);


                    System.out.println("消费的 Body:");
                    String bodyMsg = new String(body, UTF_8);
                    System.out.println(bodyMsg);
                }
            });
        }
    }
}

Virtual Host虚拟机

  • 虚拟地址,用于进行逻辑隔离,最上层的消息路由
  • 一个 Virtual Host 里面可以有若干个 Exchange 和 Queue
  • 同一个 Virtual Host 里面不能有相同名称的 Exchange 或 Queue
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

讲文明的喜羊羊拒绝pua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值