RabbitMQ学习篇4_AMPQ协议、组件和架构(图形化界面举栗、代码举栗)、使用场景

本文深入讲解RabbitMQ的AMPQ协议、组件架构及使用场景,包括发布订阅、路由及主题模式,并提供代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

  • AMPQ协议

    • 为什么RabbitMQ是基于信道Channel处理而不是Connection?
  • 组件和架构

    • 可以存在没有exchange的队列吗?
    • 核心概念
    • 注意
  • 图形化界面举栗

    1. 发布订阅模式演示
    2. 路由模式演示
    3. 主题模式演示
    4. 参数模式演示
  • 代码举栗

    1. 发布订阅模式演示
    2. 路由模式演示
  • 轮询、公平分发模式

  • 使用场景

一、AMPQ协议

概述

  • AMQP全称 Advanced Message Queuing Protocal(高级消息队列协议)。
  • 是一个应用层协议的开发标准,为面向消息的中间件设计

生产者基本实现过程

  • 创建连接工厂
  • 获取连接对象
  • 获取连接信道
  • 创建交换机,声明队列、绑定关系、路由key、发送消息、接受信息
  • 发送消息
  • 关闭信道
  • 关闭连接

在这里插入图片描述

消费者基本实现流程

  • 创建连接工厂
  • 获取连接对象
  • 获取连接信道
  • 接受信息
  • 关闭信道
  • 关闭连接

在这里插入图片描述

1.为什么RabbitMQ是基于信道Channel处理而不是Connection?

为什么RabbitMQ不是使用 http短连接的Connection 而是另外开启信道处理数据?

  • 因为TCP\IP短连接需要经历3次握手、4次挥手才能通信,效率不是很高,而且连接的开关较为耗时
  • 如果不用信道,那应用程序就会以TCP连接RabbitMQ,高峰时每秒成千上万条连接会造成资源巨大浪费,而且操作系统每秒处理TCP连接数也是有限制的,必定造成性能瓶颈, 一个连接内可以开启多个信道,一个信道是一个长连接,因此可以更好地处理并发数据
  • 信道的原理是一条线程一条通道,多条线程多条通道同用一条TCP连接。一条TCP连接可以容纳无限的信道,即使每秒成千上万的请求也不会成为性能瓶颈
二、组件和架构

在这里插入图片描述

1.可以存在没有exchange的队列吗?

之前的举栗,发现生产者指定交换机为空

// 6. 发送消息到queue

/**
    * 参数
    * 
    * 交换机
    * 队列、路由key
    * 消息的状态控制
    * 消息主题
    */

   channel.basicPublish("",queueName,null,msg.getBytes());
  • 不可以,虽然没有指定交换机,但是会存在一个默认的交换机
  • Erlang语言是开发路由器的语言,由c实现。生产者发送的消息是给exchange的,而不是之间给队列。
  • 队列存储的消息分发的时候,没有指定 路由key的情况会默认分发,反之经过key筛选,分发给指定的消费者
2.核心概念

在这里插入图片描述

  1. Server:
    又称作Broker,用于接受客户端的连接,实现AMQP实体服务

  2. Connection:
    连接,应用程序与Broker的网络连接。应用程序和Broker的连接TCP\IP三次挥手四次握手。

  3. Channel:
    网络信道,几乎所有的操作都在Channel中进行,Channel是进行消息读写的通道。客户端可建立多个Channel,每个Channel代表一个会话任务

  4. Message:
    消息,服务器和应用程序之间传送的数据,有Properties和Body组成。Properties可以对消息进行修饰,比如消息的优先级、延迟等高级特性;Body则是消息体内容,即我们要传输的数据;
    仅仅创建了客户端到Broker之间的连接后,客户端还是不能发送消息的。需要为每一个Connection创建Channel,AMQP协议规定只有通过Channel才能执行AMQP的命令。一个Connection可以包含多个Channel。之所以需要Channel,是因为TCP连接的建立和释放都是十分昂贵的,如果一个客户端每一个线程都需要与Broker交互,如果每一个线程都建立一个TCP连接,暂且不考虑TCP连接是否浪费,就算操作系统也无法承受每秒建立如此多的TCP连接。RabbitMQ建议客户端线程之间不要共用Channel,至少要保证共用Channel的线程发送消息必须是串行的,但是建议尽量共用Connection。

  5. Virtual Host:
    虚拟地址,是一个逻辑概念,用于进行逻辑隔离,是最上层的消息路由。一个Virtual Host里面可以有若干个Exchange和Queue,同一个Virtual Host里面不能有相同名称的Exchange或者Queue;
    Virtual Host是权限控制的最小粒度;
    在这里插入图片描述

  6. Exchange:
    交换机,用于接收消息,可根据路由键将消息转发到绑定的队列。不具备消息存储的能力

  7. Binding:
    Exchange和Queue之间的虚拟连接,Exchange在与多个Message Queue发生Binding后会生成一张路由表,路由表中存储着Message Queue所需消息的限制条件即Binding Key。
    当Exchange收到Message时会解析其Header得到Routing Key,Exchange根据Routing Key与Exchange Type将Message路由到Message Queue。
    Binding Key由Consumer在Binding Exchange与Message Queue时指定,维持 Routing Key 和 交换机、队列之间的关系
    Routing Key由Producer发送Message时指定,两者的匹配方式由Exchange Type决定。

  8. Routing Key:
    一个路由规则,虚拟机可用它来确定如何路由一个特定的消息;有RoutingKey的话会过滤信息,只给部分的消费者;没有的话直接发布订阅给全部的消费者一份

  9. Queue:
    也称作Message Queue,即消息队列,用于保存消息并将他们转发给消费者;

运行流程

在这里插入图片描述

3.注意
  • 简单模式和工作模式有一个默认的交换机,生产者得消息都是发送给交换机,由交换机分发
  • 注意图形化界面 Ack message requeue false 会模拟消费信息,导致队列的消息被消费就消失了,使用 Nack message requeue true 预览队列中的信息,不会消费。
三、图形化界面演示
1.发布订阅模式演示

创建 路由器exchange

在这里插入图片描述

队列绑定 路由器
在这里插入图片描述

队列查看消息

在这里插入图片描述

2.路由模式演示

路由交换机绑定队列,给每个对列指定Routing Key

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

指定Routing Key 为 email 发送信息测试,发现只有Q1、Q3收到信息

在这里插入图片描述

3.主题模式演示
  • . 分割表示一个字符
  • # 0个或多个字符匹配
  • * 一个字符匹配

在这里插入图片描述

4.参数模式演示

请添加图片描述
请添加图片描述

四、代码演示
1.fanout模式

注意:

  • 交换机my-fanout和 xiaosiQueue1、xiaosiQueue2、xiaosiQueue3已经在可视化界面创建并已完成绑定,不再需要创建新的队列
  • 本次演示fanout,每条队列都能收到存储信息,然后模拟三条线程消费。

代码


 
================  Producer ======================================  Producer=====================
package henu.soft.xiaosi.routing;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Producer {

    public static void main(String[] args) {

        // 所有的中间件技术都是基于TCP\IP协议之上构建新型的协议规范,主不过rabbitmq遵循amqp协议 ip、port


        /**
         * 原生方式fanout使用rabbitmq
         */
        // 1. 创建连接工厂

        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setPort(5672);
        factory.setUsername("admin");
        factory.setPassword("admin");
        factory.setVirtualHost("/xiaosi");

        Connection connection = null;
        Channel channel = null;

        try {
            // 2. 创建连接Connection

            connection = factory.newConnection("生产者");


            // 3. 通过连接获取管道channel

            channel = connection.createChannel();


            // 4. 创建交换机,声明队列、绑定关系、路由key、发送消息、接受信息

            // 创建交换机,因为图形化界面已经创建过了,这里不需要再次创建

            String exchangeName = "my-fanout";

            String type = "fanout";

            // RouteKey
            String routeKey = "";

            

            // 5. 准备消息内容
            String msg = "fanout 下的 xiaosi 的 RabbitMQ~~";


            // 6. 发送消息到queue

            /**
             * 参数
             *
             * fanout交换机
             * 路由key
             * 消息的状态控制
             * 消息主题
             */

            channel.basicPublish(exchangeName, routeKey, null, msg.getBytes());

            System.out.println("消息发送成功!");


        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            try {
                // 7. 关闭通道
                if (channel != null && channel.isOpen()) {
                    channel.close();
                }
                // 8. 关闭连接
                if (connection != null && channel.isOpen()) {
                    connection.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (TimeoutException e) {
                e.printStackTrace();
            }


        }


    }
}


 
================  Consumer ======================================  Consumer =====================

package henu.soft.xiaosi.routing;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Consumer {


    public void consumer(String queueName) {
        /**
         * fanout原生方式使用rabbitmq
         */
        // 1. 创建连接工厂

        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setPort(5672);
        factory.setUsername("admin");
        factory.setPassword("admin");
        factory.setVirtualHost("/xiaosi");

        Connection connection = null;
        Channel channel = null;

        try {
            // 2. 创建连接Connection

            connection = factory.newConnection("消费者");


            // 3. 通过连接获取管道channel

            channel = connection.createChannel();


            channel.basicConsume(
                    queueName,
                    true,
                    new DeliverCallback() {
                        public void handle(String consumerTag, Delivery message) throws IOException {
                            System.out.println(Thread.currentThread().getName() + " 收到的信息 ===》" + new String(message.getBody(), "UTF-8"));
                        }

                    },
                    new CancelCallback() {
                        public void handle(String consumerTag) throws IOException {
                            System.out.println("消费者接受消息失败!");

                        }
                    });


            System.out.println(Thread.currentThread().getName() + " 开始接受新消息!");
            System.in.read();


        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            try {
                // 7. 关闭通道
                if (channel != null && channel.isOpen()) {
                    channel.close();
                }
                // 8. 关闭连接
                if (connection != null && channel.isOpen()) {
                    connection.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (TimeoutException e) {
                e.printStackTrace();
            }


        }
    }

}

class test {
    public static void main(String[] args) {
        Consumer consumer = new Consumer();


        /**
         * 需要注意的是 可视化界面绑定了 my-fanout 交换机和 xiaosiQueue1、xiaosiQueue2、xiaosiQueue3三个队列
         */
        new Thread(() -> {
            consumer.consumer("xiaosiQueue1");
        }, "消费者1").start();

        new Thread(() -> {
            consumer.consumer("xiaosiQueue2");
        }, "消费者3").start();

        new Thread(() -> {
            consumer.consumer("xiaosiQueue3");
        }, "消费者2").start();


    }
}


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

2.direct模式

注意

  • 交换机my-direct + Route Key 和 Q1、Q2、Q3 已经在可视化界面创建并已完成绑定,不再需要创建新的队列
  • 本次演示Route key 设置为 email,Q1、Q3收到存储信息,然后模拟三条线程消费,只有两条能消费。

代码


======================= Producer =======================================
package henu.soft.xiaosi.direct;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Producer {

    public static void main(String[] args) {

        // 所有的中间件技术都是基于TCP\IP协议之上构建新型的协议规范,主不过rabbitmq遵循amqp协议 ip、port


        /**
         * 原生方式direct使用rabbitmq
         */
        // 1. 创建连接工厂

        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setPort(5672);
        factory.setUsername("admin");
        factory.setPassword("admin");
        factory.setVirtualHost("/xiaosi");

        Connection connection = null;
        Channel channel = null;

        try {
            // 2. 创建连接Connection

            connection = factory.newConnection("生产者");


            // 3. 通过连接获取管道channel

            channel = connection.createChannel();


            // 4. 创建交换机,声明队列、绑定关系、路由key、发送消息、接受信息

            // 创建交换机,因为图形化界面已经创建过了,这里不需要再次创建

            String exchangeName = "my-direct";

            String type = "direct";

            // RouteKey
            /**
             * Q1===>email
             * Q2====>phone
             * Q3====>email、phone
             */
            String routeKey = "email";




            /**
             * 参数:
             * 队列名称
             * 是否需要持久化
             * 排他性
             * 是否自动删除(最后一个消费者消费完是否删除队列
             * 携带附加参数
             */


            // 5. 准备消息内容
            String msg = "direct + Route Key ======> xiaosi 的 RabbitMQ~~";


            // 6. 发送消息到queue

            /**
             * 参数
             *
             * fanout交换机
             * 路由key
             * 消息的状态控制
             * 消息主题
             */

            channel.basicPublish(exchangeName, routeKey, null, msg.getBytes());

            System.out.println("消息发送成功!");


        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            try {
                // 7. 关闭通道
                if (channel != null && channel.isOpen()) {
                    channel.close();
                }
                // 8. 关闭连接
                if (connection != null && channel.isOpen()) {
                    connection.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (TimeoutException e) {
                e.printStackTrace();
            }


        }


    }
}

======================= Consumer ============================

package henu.soft.xiaosi.direct;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Consumer {


    public void consumer(String queueName,String threadName) {
        /**
         * direct原生方式使用rabbitmq
         */
        // 1. 创建连接工厂

        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setPort(5672);
        factory.setUsername("admin");
        factory.setPassword("admin");
        factory.setVirtualHost("/xiaosi");

        Connection connection = null;
        Channel channel = null;

        try {
            // 2. 创建连接Connection

            connection = factory.newConnection("消费者");


            // 3. 通过连接获取管道channel

            channel = connection.createChannel();


            channel.basicConsume(
                    queueName,
                    true,
                    new DeliverCallback() {
                        public void handle(String consumerTag, Delivery message) throws IOException {
                            System.out.println(threadName + " 收到的信息 ===》" + new String(message.getBody(), "UTF-8"));
                        }

                    },
                    new CancelCallback() {
                        public void handle(String consumerTag) throws IOException {
                            System.out.println("消费者接受消息失败!");

                        }
                    });


            System.out.println(Thread.currentThread().getName() + " 开始接受新消息!");
            System.in.read();


        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            try {
                // 7. 关闭通道
                if (channel != null && channel.isOpen()) {
                    channel.close();
                }
                // 8. 关闭连接
                if (connection != null && channel.isOpen()) {
                    connection.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (TimeoutException e) {
                e.printStackTrace();
            }


        }
    }

}

class test {
    public static void main(String[] args) {
        Consumer consumer = new Consumer();


        /**
         * 需要注意的是 可视化界面绑定了 my-direct交换机 + Route Ksy 和 Q1、Q2、Q3三个队列
         */
        new Thread(() -> {
            consumer.consumer("Q1",Thread.currentThread().getName());
        }, "消费者1").start();

        new Thread(() -> {
            consumer.consumer("Q2",Thread.currentThread().getName());
        }, "消费者2").start();

        new Thread(() -> {
            consumer.consumer("Q3",Thread.currentThread().getName());
        }, "消费者3").start();


    }
}

在这里插入图片描述

3.代码声明交换机、队列绑定
 // 4. 创建交换机,声明队列、绑定关系、路由key、发送消息、接受信息

            String exchangeName = "direct_message_exchange";
            String exchangeType = "direct";

            // 1. 声明交换机
            channel.exchangeDeclare(exchangeName,exchangeType,true);

            // 2. 声明队列


            String queueName1 = "xiaoxiaosi1";
            String queueName2 = "xiaoxiaosi2";
            String queueName3 = "xiaoxiaosi3";
            /**
             * 参数:
             * 队列名称
             * 是否需要持久化
             * 排他性
             * 是否自动删除(最后一个消费者消费完是否删除队列
             * 携带附加参数
             */

            channel.queueDeclare(queueName1,false,false,false,null);
            channel.queueDeclare(queueName2,false,false,false,null);
            channel.queueDeclare(queueName3,false,false,false,null);

            // 3. 准备消息内容
            String msg = "xiaoxiaosiRabbitMQ~~";

            // 4. 绑定交换机和队列
            channel.queueBind("xiaoxiaosi1",exchangeName,"xiaoxiao");
            channel.queueBind("xiaoxiaosi2",exchangeName,"xiaoxiao");
            channel.queueBind("xiaoxiaosi3",exchangeName,"xiaoxiaosi");


            // 5. 发送消息到queue

            /**
             * 参数
             *
             * 交换机
             * 队列、路由key
             * 消息的状态控制
             * 消息主题
             */

            String routeKey = "xiaoxiao";
            channel.basicPublish(exchangeName,routeKey,null,msg.getBytes());

            System.out.println("消息发送成功!");

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

4.轮询、公平分发举栗

注意

  • 默认的轮询分发模式下,不会因为服务器能力影响分发数量。应答方式可以为自动应答,但是一般真实环境都改为手动。
  • 公平分发的模式下,一定要将自动应答改为手动应答,
  • 公平分发模式下,需要设置basicQos消费者一次取多少条消息处理,需要根据服务器具体性能及相关因素确定。
    在这里插入图片描述
五、使用场景(解耦、削峰、异步)

下订单业务场景

  • 串行方式:将订单消息写入数据库成功后,发送注册邮件,在发送注册信息。以上三个任务完成后,返回给客户端
    在这里插入图片描述
    在这里插入图片描述

  • 并行方式 异步线程池:将订单信息写入数据库之后,三个任务同时交给线程池,最后返回给客户端。可以减少处理的时间
    在这里插入图片描述
    在这里插入图片描述

  • 异步消息队列方式:把下单作为生产者、其他三个任务作为消费者
    在这里插入图片描述
    在这里插入图片描述
    方便解耦
    在这里插入图片描述

流量削峰
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

scl、

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

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

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

打赏作者

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

抵扣说明:

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

余额充值