Rabbitmq(三)

60 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第7天,点击查看活动详情

4.RabbitMQ 的第一个程序

4.0 AMQP协议的回顾

image-20200312140114784

4.1 RabbitMQ支持的消息模型

image-20191126165434784

image-20191126165459282

4.2 引入依赖

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

application.yml 配置文件:

spring:
  application:
    name: rabbitmq-springboot
  rabbitmq:
    host: 192.168.204.129   # rabbitmq主机
    port: 5672              # 端口
    stream:
      username: ems         # rabbitmq用户名
      password: 123         # rabbitmq密码
    virtual-host: /ems      # 虚拟主机

我们需要提前在 rabbitmq 中创建虚拟主机,所谓虚拟主机就类似于数据库中的库,用来为我们的项目做一一的映射,日后不同的项目建立不同的虚拟主机。例:我们使用rabbitmq的web管理界面创建了一个 /ems 虚拟主机,然后创建一个用户(例如:ems) ,然后我们要让创建的用户绑定虚拟主机,使当前用户可以访问指定的虚拟主机。

4.3 第一种模型(直连)

image-20191126165840602

在上图的模型中,有以下概念:

  • P:生产者,也就是要发送消息的程序
  • C:消费者:消息的接受者,会一直等待消息到来。
  • queue:消息队列,图中红色部分。类似一个邮箱,可以缓存消息;生产者向其中投递消息,消费者从其中取出消息。
1. 开发生产者
  //创建连接工厂
  ConnectionFactory connectionFactory = new ConnectionFactory();
  connectionFactory.setHost("10.15.0.9");
  connectionFactory.setPort(5672);
  connectionFactory.setUsername("ems");
  connectionFactory.setPassword("123");
  connectionFactory.setVirtualHost("/ems");
  Connection connection = connectionFactory.newConnection();
  //创建通道
  Channel channel = connection.createChannel();
  //参数1: 是否持久化  参数2:是否独占队列 参数3:是否自动删除  参数4:其他属性
  channel.queueDeclare("hello",true,false,false,null);
  channel.basicPublish("","hello", null,"hello rabbitmq".getBytes());
  channel.close();
  connection.close();

测试类:


public class Provider {

    // 生产消息
    @Test
    public void testSendMessage() throws IOException, TimeoutException {

/*        // 创建连接mq的连接工厂对象
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 设置连接rabbitmq主机
        connectionFactory.setHost("192.168.182.128");
        // 设置端口号
        connectionFactory.setPort(5672);
        // 设置连接哪个虚拟主机
        connectionFactory.setVirtualHost("/ems");
        // 设置访问虚拟主机的用户名和密码(和虚拟主机绑定的用户)
        connectionFactory.setUsername("ems");
        connectionFactory.setPassword("123");
        // 通过上面的操作就和rabbitmq建立了连接

        // 获取连接对象
        Connection connection = connectionFactory.newConnection();*/
        // 通过工具类获取连接对象
        Connection connection = RabbitMQUtils.getConnection();

        // 获取连接中的通道对象  (真正传递消息是建立连接之后连接通过通道去传递消息,所以我们需要通过连接获取连接中的通道)
        Channel channel = connection.createChannel();  //

        // 通道绑定对应消息队列    (通道和队列之间一定要有一个声明或者一个绑定,通道才知道把消息发送给哪个消息队列)
        // 参数1:队列名称 如果队列不存在自动创建
        // 参数2:用来定义队列的特性是否要持久化,true 持久化队列(存到磁盘中) false 不持久化 (仅仅是队列持久化,不保证队列中消息持久化)
        // 参数3:exclusive 是否独占队列 true 独占队列 false 不独占(这个队列是否只有当前连接可用,其他连接不可以再用)
        // 参数4:autoDelete 是否在消费完成后自动删除队列 true 自动删除 false 不自动删除
        // 参数5:额外附加参数
        channel.queueDeclare("hello", true, false, false, null);
        /*
            并不是某个通道绑定哪个队列才只能往这个队列发送消息,通道不绑定某个队列也可以向这个队列发送消息
         */

        // 通过通道发布消息
        // 参数1:交换机名称(因为我们这次写的是点对点,直接到队列,没有交换机,写 "" )
        // 参数2:队列名称
        // 参数3:传递消息额外设置,我们可以传入一个属性(MessageProperties.PERSISTENT_TEXT_PLAIN),让我们队列中的消息进行持久化
        // 参数4:消息的具体内容
        channel.basicPublish("", "hello", null, "hello rabbitmq".getBytes());
        channel.basicPublish("", "hello", MessageProperties.PERSISTENT_TEXT_PLAIN, "hello word".getBytes());



        /*// 释放资源
        channel.close();
        connection.close();*/
        // 调用工具类关闭
        RabbitMQUtils.closeConnectionAndChanel(channel, connection);
    }
}

image-20220823114552305

image-20220823114622069

image-20220823114729718

2. 开发消费者

消费者得和生产者绑定同一个虚拟主机,紧接着在生产者发的队列中拿到消息,所以消费者也要找到对应的虚拟主机、对应的队列才能去消费。

  //创建连接工厂
  ConnectionFactory connectionFactory = new ConnectionFactory();
  connectionFactory.setHost("10.15.0.9");
  connectionFactory.setPort(5672);
  connectionFactory.setUsername("ems");
  connectionFactory.setPassword("123");
  connectionFactory.setVirtualHost("/ems");
  Connection connection = connectionFactory.newConnection();
  Channel channel = connection.createChannel();
  channel.queueDeclare("hello", true, false, false, null);
  channel.basicConsume("hello",true,new DefaultConsumer(channel){
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
      System.out.println(new String(body));
    }
  });

测试

public class Consumer {

    public static void main(String[] args) throws IOException, TimeoutException {
        // 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.182.128");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/ems");
        connectionFactory.setUsername("ems");
        connectionFactory.setPassword("123");

        // 创建连接对象
        Connection connection = connectionFactory.newConnection();

        // 创建通道
        Channel channel = connection.createChannel();

        // 通道绑定队列
        channel.queueDeclare("hello", false, false, false, null); // 消费端一定要和生产端对应上

        // 消费消息
        // 参数1:消费哪一个队列的消息
        // 参数2:是否开启消息自动确认机制
        // 参数3:消费消息时的回调接口,把当前的通道传进去
        channel.basicConsume("hello", true, new DefaultConsumer(channel){
            // 这里第三个参数使用了匿名内部类实现

            @Override           // 参数1:标签  参数2:消息传递的信封    参数3:传的一些属性   参数4 body :消息队列中取出的消息
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("new String(body) = " + new String(body));
            }
        });

        // 释放资源
        // (如果不释放的话会一直监听我们的队列,建议不释放,因为我们要一直做处理,如果释放可能还没来得及执行回调方法,main线程已经关闭了)
        // channel.close();
        // connection.close();

    }
}

image-20220823160427372

image-20220823160538681

我们可以发现,上面生产者和消费者有代码冗余,所以我们可以封装工具类RabbitMQUtils,并将生产者和消费者相应冗余代码替换掉。

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

public class RabbitMQUtils {

    private static ConnectionFactory connectionFactory;

    static {
        // 创建连接工厂,连接工厂属于重量级资源 (静态代码块中的资源是在类加载的时候执行,只执行一次)
        connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.182.128");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/ems");
        connectionFactory.setUsername("ems");
        connectionFactory.setPassword("123");
    }

    // 定义提供连接对象的方法
    public static Connection getConnection(){
        try {
            // 返回连接对象
            return connectionFactory.newConnection();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    // 关闭通道和关闭连接工具方法
    public static void closeConnectionAndChanel(Channel channel, Connection conn){
        try {
            if(channel != null) channel.close();
            if(conn != null) conn.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
3. 参数的说明
  channel.queueDeclare("hello",true,false,false,null);
	'参数1':用来声明通道对应的队列
  '参数2':用来指定是否持久化队列
  '参数3':用来指定是否独占队列
  '参数4':用来指定是否自动删除队列
  '参数5':对队列的额外配置