RabbitMQ基础篇

文章目录

1 RabbitMQ概述

RabbitMQ简易安装教程

# 拉取镜像
docker pull rabbitmq:3.13-management

# -d 参数:后台运行 Docker 容器
# --name 参数:设置容器名称
# -p 参数:映射端口号,格式是“宿主机端口号:容器内端口号”。5672供客户端程序访问,15672供后台管理界面访问
# -v 参数:卷映射目录
# -e 参数:设置容器内的环境变量,这里我们设置了登录RabbitMQ管理后台的默认用户和密码
docker run -d \
--name rabbitmq \
-p 5672:5672 \
-p 15672:15672 \
-v rabbitmq-plugin:/plugins \
-e RABBITMQ_DEFAULT_USER=guest \
-e RABBITMQ_DEFAULT_PASS=123456 \
rabbitmq:3.13-management

docker exec -it 5129c41ad3d8 /bin/bash # 数字是rabbitmq的id,可以通过docker ps查看

rabbitmq-plugins enable rabbitmq_management #启用 RabbitMQ Management 插件,使得你可以轻松地监控和管理 RabbitMQ 服务器

访问登录:http://192.168.145.130:15672,账号密码就是上面指定的

1.1 消息队列

消息队列是实现应用程序之间通信的中间件

消息队列的好处

  • 消息的发送者和接收者进行异步通信
  • 流量高峰保证服务稳定,消息队列可以暂存大量消息,达到流量削峰
  • 扩展性高,可以水平扩展以支持更多的发送者和接收者,相应地增加或减少资源处理(功能处理)
  • 解耦:消息的发送者和接收者只专注于消息,无需关系彼此细节

主流MQ对比

image-20240526195043655

1.2 RabbitMQ体系结构

image-20240527160657626
  • Channel(信道):信道是生产者消费者和RabbitMQ服务器之间通信的桥梁。所有的消息发布和消费都由信道来完成的

    • 建立在TCP连接上的虚拟连接,允许在单个TCP连接上建立多个信道,从而实现多线程处理
    • 每个线程对应一个信道,信道在RabbitMQ中具有唯一的ID,保证了信道的私有性
    • 引入信道的概念是为了减少建立和销毁TCP连接的开销,提高系统性能
  • Exchange(交换机):负责接收消息并根据路由键将消息转发到绑定的队列

  • Queue(队列):队列是RabbitMQ中用于存储消息的容器,消息按照先进先出的顺序进行处理

  • Virtual Host(虚拟主机):是RabbitMQ中的命名空间(理解为分组),用于隔离不同的环境或应用程序。每个虚拟主机都有自己的队列、交换机和绑定关系

  • Broker(代理服务器):指RabbitMQ服务器本身,多个Broker组合成一个RabbitMQ集群

2 RabbitMQ工作模式

image-20240527195355740

  • 简单模式:生产者向默认交换机发送消息,默认交换机将消息放到队列中,消费者监听并消费消息
  • 工作队列模式:生产者向默认交换机发送消息,默认交换机将消息放到消息队列,多个消费者监听消息队列竞争消息(其实就是简单模式的升级版)
  • 发布/订阅模式:扇出交换机接收消息并将消息发送给所有订阅了该交换机的队列
  • 消息生产者将消息发送给Direct交换机,Direct交换机根据routing key路由键将消息发送到指定队列(用的最多)
  • 消息生产者将消息发送给Topic交换机,Topic交换机根据通配符形式的路由键将消息发送到指定队列

项目导入依赖:采用原生的方式,开发中都是集成框架的

<dependencies>
    <dependency>
        <groupId>com.rabbitmq</groupId>
        <artifactId>amqp-client</artifactId>
        <version>5.20.0</version>
    </dependency>
</dependencies>

封装连接工具类:

import com.rabbitmq.client.Connection;  
import com.rabbitmq.client.ConnectionFactory;  
  
public class ConnectionUtil {
     
    public static final String HOST_ADDRESS = "192.168.145.160";
  
    public static Connection getConnection() throws Exception {
     
  
        // 定义连接工厂  
        ConnectionFactory factory = new ConnectionFactory();
        // 设置服务地址  
        factory.setHost(HOST_ADDRESS);
        // 端口  
        factory.setPort(5672);
        //设置账号信息,用户名、密码、vhost  
        factory.setVirtualHost("/");  
        factory.setUsername("guest");  
        factory.setPassword("123456");

        // 通过工程获取连接  
        Connection connection = factory.newConnection();
        return connection;  
    }
}

2.1 简单模式(Simple Queue)

生产者向默认交换机发送消息,默认交换机将消息放到队列中,消费者监听并消费消息

image-20240527195355740

生产者:发送消息

public class Producer {
     
  
    public static void main(String[] args) throws Exception {
   
        // 获取连接
        Connection connection = ConnectionUtil.getConnection();
  
        // 创建频道  
        Channel channel = connection.createChannel();  
  
        // 声明(创建)队列  
        // queue      参数1:队列名称  
        // durable    参数2:是否定义持久化队列,当 MQ 重启之后还在  
        // exclusive  参数3:是否独占本次连接。若独占,只能有一个消费者监听这个队列且 Connection 关闭时删除这个队列  
        // autoDelete 参数4:是否在不使用的时候自动删除队列,也就是在没有Consumer时自动删除  
        // arguments  参数5:队列其它参数  
        channel.queueDeclare("simple_queue", true, false, false, null);  
  
        // 要发送的信息  
        String message = "你好;小兔子!";  
  
        // 参数1:交换机名称,如果没有指定则使用默认Default Exchange  
        // 参数2:路由key,简单模式可以传递队列名称  
        // 参数3:配置信息  
        // 参数4:消息内容  
        channel.basicPublish("", "simple_queue", null, message.getBytes());  
  
        System.out.println("已发送消息:" + message);  
  
        // 关闭资源  
        channel.close();  
        connection.close();
    }
}

运行效果:新增队列:simple_queue

image-20240527200148062

image-20240527200606545

消费者

public class Consumer {
     
  
    public static void main(String[] args) throws Exception {
   

        // 获取连接
        Connection connection = ConnectionUtil.getConnection();
        // 创建Channel
        Channel channel = connection.createChannel();  
  
        // 创建队列
        // 如果没有一个名字叫simple_queue的队列,则会创建该队列,如果有则不会创建  
        // 参数1. queue:队列名称  
        // 参数2. durable:是否持久化。如果持久化,则当MQ重启之后还在  
        // 参数3. exclusive:是否独占。  
        // 参数4. autoDelete:是否自动删除。当没有Consumer时,自动删除掉  
        // 参数5. arguments:其它参数。  
        channel.queueDeclare("simple_queue",true,false,false,null);  
  
        // 接收消息  
        DefaultConsumer consumer = new DefaultConsumer(channel){
   
            // 回调方法,当收到消息后,会自动执行该方法  
            // 参数1. consumerTag:标识  
            // 参数2. envelope:获取一些信息,交换机,路由key...  
            // 参数3. properties:配置信息  
            // 参数4. body:数据  
            @Override  
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
   
                System.out.println("consumerTag:"+consumerTag);  
                System.out.println("Exchange:"+envelope.getExchange());  
                System.out.println("RoutingKey:"+envelope.getRoutingKey());  
                System.out.println("properties:"+properties);  
                System.out.println("body:"+new String(body));
            }
        };
        // 参数1. queue:队列名称  
        // 参数2. autoAck:是否自动确认,类似咱们发短信,发送成功会收到一个确认消息  
        // 参数3. callback:回调对象  
        // 消费者类似一个监听程序,主要是用来监听消息  
        channel.basicConsume("simple_queue",true,consumer);
    }
}

控制台打印:

image-20240527202647372

消息被消费掉了,所以RabbitMQ服务器上没有了

image-20240527201201641

2.2 工作队列模式(Work Queues)

生产者向默认交换机发送消息,默认交换机将消息放到消息队列,多个消费者监听消息队列竞争消息(其实就是简单模式的升级版)

生产者

public class Producer {
     
  
    public static final String QUEUE_NAME = "work_queue";  
  
    public static void main(String[] args) throws Exception {
     
  
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();  
  
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);  
  
        for (int i = 1; i <= 10; i++) {
   
            String body = i+"hello rabbitmq~~~";
            channel.basicPublish("",QUEUE_NAME,null,body.getBytes());
        }  
  
        channel.close();
        connection.close();
    }
}

发送消息:

image-20240527204259210

消费者1:

public class Consumer1 {
     
  
    static final String QUEUE_NAME = "work_queue";  
  
    public static void main(String[] args) throws Exception {
   
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);  
  
        Consumer consumer = new DefaultConsumer(channel){
   
            @Override  
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
   
                System.out.println("Consumer1 body:"+new String(body));
            }
        };
        channel.basicConsume(QUEUE_NAME,true,consumer);
    }
}

消费者2:

public class Consumer2 {
   
    static final String QUEUE_NAME = "work_queue";  
  
    public static void main(String[] args) throws Exception {
   
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);

        Consumer consumer = new DefaultConsumer(channel){
   
            @Override  
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
   
                System.out.println("Consumer2 body:"+new String(body));
            }
        };
        channel.basicConsume(QUEUE_NAME,true,consumer);
    }
}

注意:运行的时候先启动两个消费端程序,然后再启动生产者端程序

运行结果:两个消费者竞争消息队列中消息

image-20240527204854307

2.3 发布/订阅模式(Publish/Subscribe)

rabbitmq消息通讯过程:消息生产者将消息发送给交换机,由交换机处理消息。Exchange(交换机)只负责转发消息,不存储消息,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!

常见的交换机类型

  • Fanout Exchange(扇出交换机),将消息发送给所有绑定到交换机的队列
  • Direct Exchange(直连交换机),把消息交给符合指定routing key的队列
  • Topic Exchange(主题交换机),把消息交给符合routing pattern(路由模式)的队列
  • Default Exchange(默认交换机),把消息发送给指定队列

发布/订阅模式:扇出交换机接收消息并将消息发送给所有订阅了该交换机的队列

生产者:

public class Producer {
   
    public static void main(String[] args) throws Exception {
   
      // 1、获取连接  
        Connection connection = ConnectionUtil.getConnection();
      // 2、创建频道  
        Channel channel = connection.createChannel();  
  
        // 参数1. exchange:交换机名称  
        // 参数2. type:交换机类型  
        //     DIRECT("direct"):定向  
        //     FANOUT("fanout"):扇形(广播),发送消息到每一个与之绑定队列。  
        //     TOPIC("topic"):通配符的方式  
        //     HEADERS("headers"):参数匹配  
        // 参数3. durable:是否持久化  
        // 参数4. autoDelete:自动删除  
        // 参数5. internal:内部使用。一般false  
        // 参数6. arguments:其它参数  
        String exchangeName = "test_fanout";  
  
        // 3、创建交换机  
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT,true,false,false,null);  
  
        // 4、创建队列  
        String queue1Name = "test_fanout_queue1";
        String queue2Name = "test_fanout_queue2";

        channel.queueDeclare(queue1Name,true,false,false,null);  
        channel.queueDeclare(queue2Name,true,false,false,null);  
  
        // 5、绑定队列和交换机  
      // 参数1. queue:队列名称  
      // 参数2. exchange:交换机名称  
      // 参数3. routingKey:路由键,绑定规则  
      //     如果交换机的类型为fanout,routingKey设置为""  
        channel.queueBind(queue1Name,exchangeName,"");  
        channel.queueBind(queue2Name,exchangeName,"");  
  
        String body = "日志信息:张三调用了findAll方法...日志级别:info...";  
  
        // 6、发送消息  
        channel.basicPublish(exchangeName,"",null,body.getBytes());  
  
        // 7、释放资源  
        channel.close();  
        connection.close();
    }
}

消费者1:

public class Consumer1 {
     
  
    public static void main(String[] args) throws Exception {
     
  
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        String queue1Name = "test_fanout_queue1";  
  
        channel.queueDeclare(queue1Name,true,false,false,null);
        Consumer consumer = new DefaultConsumer(channel){
   
            @Override  
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
   
                System.out.println("body:"+new String(body));  
                System.out.println("队列 1 消费者 1 将日志信息打印到控制台.....");
            }  
  
        };
        channel.basicConsume(queue1Name,true,consumer);
    }
}

消费者2

public class Consumer2 {
     
  
    public static void main(String[] args) throws Exception {
   
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        String queue2Name = "test_fanout_queue2";
        channel.queueDeclare(queue2Name,true,false,false,null);  
  
        Consumer consumer = new DefaultConsumer(channel){
   
            @Override  
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
   
                System.out.println("body:"+new String(body));  
                System.out.println("队列 2 消费者 2 将日志信息打印到控制台.....");
            }
        };
        channel.basicConsume(queue2Name,true,consumer);
    }
}

先启动两个消费者,再启动生产者发送消息

image-20240527211334592

交换机和队列的绑定关系如下图所示:

image-20240527211442292

发布订阅模式与工作队列模式的区别:

  • 工作队列模式消息由默认交换机处理,发布订阅模式消息由指定交换机处理
  • 监听同一个队列的消费端程序彼此之间是竞争关系
  • 绑定同一个交换机的多个队列在发布订阅模式下,消息是广播的,每个队列都能接收到消息

2.4 路由模式(Routing)

消息生产者将消息发送给Direct交换机,Direct交换机根据routing key路由键将消息发送到指定队列(用的最多)

当Direct交换机用相同的路由键routing key绑定多个队列,就会有广播效果(类似发布订阅)

生产者:

public class Producer {
   

    public static void main(String[] args) throws Exception {
   

        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值