哈哈 你也想学RabbitMQ吗(2)

RabbitMQ工作模式

1. 简单模式(Simple)

在这里插入图片描述

P:生产者

C:消费者

Queue:消息队列

特点:一个生产者,一个消费者,消息只能被消费一次,也称为点对点模式

适用场景:消息只能被单个消费者处理

2. 工作队列模式(WorkQueue)

在这里插入图片描述

一个生产者P,多个消费者C1、C2,在多个消息的情况下,队列会将消息分派给不同的消费者,每个消费者都会收到不同的消息

特点:消息不会重复(C1,C2共同消费队列中的消息),分配给不同的消费者

适用场景:集群环境中做异步处理

3. 发布/订阅模式(Publish/Subscribe)

在这里插入图片描述

一个生产者,多个消费者,X表示交换机,交换机会复制多份消息到不同的队列,每个消费者都接收相同的消息

适用场景:消息需要被多个消费者同时接收的场景

关于交换机

交换机转发消息有一套转发的规则,不同的规则对应不同的交换机类型

RabbitMQ实现了4种交换机类型(AMOP协议定义的)

1、Direct(直接交换机):生产者发送消息时,会指定一个“目标队列”的名字,交换机收到消息后会查看绑定的队列中是否有名字匹配的队列,如果有则将消息塞给对应的队列,如果没有则丢弃消息。

2、Fanout(扇出交换机):
在这里插入图片描述

3、Topic(主题交换机):

2个概念

1)bindingKey:队列和交换机绑定的时候指定一个单词(暗号)

2)routingKey:生产者发送的消息的时候也指定一个单词

如果当前routingKey和bindingKey能够对上暗号,则发送消息

在这里插入图片描述

匹配规则:

在这里插入图片描述

4、Header:消息头交换机(不常见)

4. 路由模式

在这里插入图片描述

路由模式是发布订阅模式的变种,在发布订阅的基础上增加路由Key。发布订阅模式是无条件把所有的消息分发给所有的消费者,路由模式是Exchange根据RoutingKey的规则将数据筛选后发送给对应的消费者队列

适用场景:需要根据特定的规则分发消息的场景

5. 通配符模式

在这里插入图片描述
路由模式的升级版,增加了通配符功能,交换机根据RoutingKey将消息转发给与RoutingKey匹配的队列

6. RPC模式

在这里插入图片描述
1、客户端发送消息到一个指定的队列,并在消息属性中设置replyTo字段,这个字段指定了一个回调队列,用于接收服务端的响应

2、服务端接收到请求后,处理请求并发送响应消息到replyTo指定的队列

7. 发布确认模式

发布确认模式是RabbitMQ提供的一种确保消息可靠发送到RabbitMQ服务器的机制。这种模式下,生产者可以等待RabbitMQ服务器的确认,确保消息已经被服务器接收并处理。(保证消息到达服务器,不是保证消息到达消费者)
1、生产者将Channel设置为confirm模式(调用channel.confirmSelect()完成)后,发布的每一条消息都会获得员工唯一的ID,生产者可以将这些序号于消息关联,以便跟踪消息的状态
2、当消息被RabbitMQ服务器接收并处理后,服务器会异步的向生产者发送一个ACK给生产者,表示消息已经送达
在这里插入图片描述

RabbitMQ工作模式使用案例

1. 简单模式

在这里插入图片描述

一个生产者,一个消费者

生产者:

public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
    //1. 建立连接(Connection)
    ConnectionFactory connectionFactory = new ConnectionFactory();
    //设置连接参数:ip,端口号,用户名,密码,虚拟机
    connectionFactory.setHost("");//ip
    connectionFactory.setPort(5672);//端口号,默认是5672
    connectionFactory.setUsername("Test");//用户名
    connectionFactory.setPassword("123");//密码
    connectionFactory.setVirtualHost("TestVirtualHost");//设置操作的虚拟机
    Connection connection = connectionFactory.newConnection();//从连接工厂中获取到连接
    //2. 开启通道(Channel)
    Channel channel = connection.createChannel();//通过connection开启通道
    //3.声明队列
    channel.queueDeclare("testQueue", true, false, false, null);
    String msg = "HelloRabbitMQ";//消息的内容
    //4.发送消息
    channel.basicPublish("", "testQueue", null, msg.getBytes());
    //5.释放资源
    channel.close();
    connection.close();
}

消费者:

public class Consumer {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 建立连接(Connection)
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //设置连接参数:ip,端口号,用户名,密码,虚拟机
        connectionFactory.setHost("");//ip
        connectionFactory.setPort(5672);//端口号,默认是5672
        connectionFactory.setUsername("Test");//用户名
        connectionFactory.setPassword("123");//密码
        connectionFactory.setVirtualHost("TestVirtualHost");//设置操作的虚拟机
        Connection connection = connectionFactory.newConnection();//从连接工厂中获取到连接
        //2. 开启通道(Channel)
        Channel channel = connection.createChannel();//通过connection开启通道
        //3. 声明队列(如果生产者声明过了,可以省略)
        channel.queueDeclare("testQueue", true, false, false, null);
        //4.消费消息
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("接收到消息" + new String(body));
            }
        };
        channel.basicConsume("testQueue", true, consumer);
        //5.释放资源
        channel.close();
        connection.close();

    }
}

2. 工作队列模式

在这里插入图片描述

生产者代码:

public class Producer {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 建立连接(Connection)
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //设置连接参数:ip,端口号,用户名,密码,虚拟机
        connectionFactory.setHost("");//ip
        connectionFactory.setPort(5672);//端口号,默认是5672
        connectionFactory.setUsername("Test");//用户名
        connectionFactory.setPassword("123");//密码
        connectionFactory.setVirtualHost("TestVirtualHost");//设置操作的虚拟机
        Connection connection = connectionFactory.newConnection();//从连接工厂中获取到连接
        //2. 开启通道(Channel)
        Channel channel = connection.createChannel();//通过connection开启通道
        //3.声明队列
        channel.queueDeclare("work.queue", true, false, false, null);
        //发送10条消息
        for (int i = 0; i < 10; i++) {
            String msg = "HelloWorkQueue" + i;//消息的内容
            channel.basicPublish("", "work.queue", null, msg.getBytes());
        }
        System.out.println("消息发送成功");
        //6.释放资源
        channel.close();
        connection.close();

    }
}

编写两个消费者代码

public class Consumer1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 建立连接(Connection)
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //设置连接参数:ip,端口号,用户名,密码,虚拟机
        connectionFactory.setHost("");//ip
        connectionFactory.setPort(5672);//端口号,默认是5672
        connectionFactory.setUsername("Test");//用户名
        connectionFactory.setPassword("123");//密码
        connectionFactory.setVirtualHost("TestVirtualHost");//设置操作的虚拟机
        Connection connection = connectionFactory.newConnection();//从连接工厂中获取到连接
        //2. 开启通道(Channel)
        Channel channel = connection.createChannel();//通过connection开启通道
        //4.声明队列
        channel.queueDeclare("work.queue", true, false, false, null);

        //5. 消费消息
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("接收到消息" + new String(body));
            }
        };
        channel.basicConsume("work.queue", true, consumer);
    }
}
public class Consumer2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 建立连接(Connection)
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //设置连接参数:ip,端口号,用户名,密码,虚拟机
        connectionFactory.setHost("");//ip
        connectionFactory.setPort(5672);//端口号,默认是5672
        connectionFactory.setUsername("Test");//用户名
        connectionFactory.setPassword("123");//密码
        connectionFactory.setVirtualHost("TestVirtualHost");//设置操作的虚拟机
        Connection connection = connectionFactory.newConnection();//从连接工厂中获取到连接
        //2. 开启通道(Channel)
        Channel channel = connection.createChannel();//通过connection开启通道
        //4.声明队列
        channel.queueDeclare("work.queue", true, false, false, null);
        //5. 消费消息
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("接收到消息" + new String(body));
            }
        };
        channel.basicConsume("work.queue", true, consumer);
    }
}

首先运行两个消费者,观察RabbitMQ管理界面

在这里插入图片描述

接着运行生产者,观察两个消费者程序的日志

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

3. 发布订阅模式

在这里插入图片描述

参数

参数:
1. exchange:交换机名称
2. type:交换机类型
    DIRECT("direct"), 定向,直连,routing
    FANOUT("fanout"),扇形(⼴播), 每个队列都能收到消息
	TOPIC("topic"),通配符
	HEADERS("headers") 参数匹配(⼯作⽤的较少)
3. durable: 是否持久化.
true-持久化,false⾮持久化.
持久化可以将交换器存盘,在服务器重启的时候不会丢失相关信息
4. autoDelete: ⾃动删除.
⾃动删除的前提是⾄少有⼀个队列或者交换器与这个交换器绑定, 之后所有与这个交换器绑定的
队列或者交换器都与此解绑.
⽽不是这种理解: 当与此交换器连接的客⼾端都断开时,RabbitMQ会⾃动删除本交换器.
5. internal: 内部使⽤, ⼀般falase.
如果设置为true, 表⽰内部使⽤.
客⼾端程序⽆法直接发送消息到这个交换器中,只能通过交换器路由到交换器这种⽅式
6. arguments: 参数

编写生产者代码

public class Producer {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 建立连接(Connection)
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //设置连接参数:ip,端口号,用户名,密码,虚拟机
        connectionFactory.setHost("");//ip
        connectionFactory.setPort(5672);//端口号,默认是5672
        connectionFactory.setUsername("Test");//用户名
        connectionFactory.setPassword("123");//密码
        connectionFactory.setVirtualHost("TestVirtualHost");//设置操作的虚拟机
        Connection connection = connectionFactory.newConnection();//从连接工厂中获取到连接
        //2. 开启通道(Channel)
        Channel channel = connection.createChannel();//通过connection开启通道
        //3. 声明交换机
        /**
         * 参数:
         * 1. exchange:交换机名称
         * 2. type:交换机类型
         *     DIRECT("direct"), 定向,直连,routing
         *     FANOUT("fanout"),扇形(⼴播), 每个队列都能收到消息
         *     TOPIC("topic"),通配符
         *     HEADERS("headers") 参数匹配(⼯作⽤的较少)
         * 3. durable: 是否持久化,true-持久化,false⾮持久化.持久化可以将交换器存盘,在服务器重启的时候不会丢失相关信息
         * 4. autoDelete: ⾃动删除.
         * ⾃动删除的前提是⾄少有⼀个队列或者交换器与这个交换器绑定, 之后所有与这个交换器绑定的
         * 队列或者交换器都与此解绑.
         * ⽽不是这种理解: 当与此交换器连接的客⼾端都断开时,RabbitMQ会⾃动删除本交换器.
         * 5. internal: 内部使⽤, ⼀般falase.
         * 如果设置为true, 表⽰内部使⽤.
         * 客⼾端程序⽆法直接发送消息到这个交换器中,只能通过交换器路由到交换器这种⽅式
         * 6. arguments: 参数
         */
        channel.exchangeDeclare("FANOUT_EXCHANGE", BuiltinExchangeType.FANOUT, false);

        //4.声明队列
        /**
         * queueDeclare方法的参数
         * queue:队列名
         * durable:可持久化
         * exclusive:是否独占(是否只能由一个消费者进行消费)
         * autoDelete:当没有人消费时,是否自动删除
         * arguments:参数
         */
        channel.queueDeclare("Queue1", true, false, false, null);
        channel.queueDeclare("Queue2", true, false, false, null);

        //5.交换机和队列进行绑定
        /**
         * 参数1:queue,队列名
         * 参数2:exchange,交换机名
         * 参数3:routingkey:路由key
         * 如果交换机类型为fanout,routingkey设置为""表示每个消费者都可以接受到全部信息
         */
        channel.queueBind("Queue1", "FANOUT_EXCHANGE", "");
        channel.queueBind("Queue2", "FANOUT_EXCHANGE", "");


        String msg = "HelloFANOUT";//消息的内容
        //发送消息
        /**
         * basicPublish方法的参数
         * exchange:交换机名称
         * routingKey:routingKey和队列名保持一致
         * props:属性配置
         * body:消息的内容
         */
        channel.basicPublish("FANOUT_EXCHANGE", "", null, msg.getBytes());
        System.out.println("消息发送成功");
        //6.释放资源
        channel.close();
        connection.close();

    }
}

运行,观察控制台

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

编写消费者代码

两个消费者代码一致,只需要把队列名改一下即可

public class Consumer1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 建立连接(Connection)
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //设置连接参数:ip,端口号,用户名,密码,虚拟机
        connectionFactory.setHost("");//ip
        connectionFactory.setPort(5672);//端口号,默认是5672
        connectionFactory.setUsername("Test");//用户名
        connectionFactory.setPassword("123");//密码
        connectionFactory.setVirtualHost("TestVirtualHost");//设置操作的虚拟机
        Connection connection = connectionFactory.newConnection();//从连接工厂中获取到连接
        //2. 开启通道(Channel)
        Channel channel = connection.createChannel();//通过connection开启通道
        //3.声明队列,如果已经声明过则不创建
        channel.queueDeclare("Queue1", true, false, false, null);
        //4.消费消息
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("接收到消息" + new String(body));
            }
        };
        /**
         * 方法参数
         * queue:队列名
         * autoAck:是否自动确认
         * Consumer:接收到消息后执行的逻辑
         * **/
        channel.basicConsume("Queue1", true, consumer);

    }
}

运行两个消费者程序

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

4. 路由模式

用代码实现如图的结构
在这里插入图片描述

public class Producer {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 建立连接(Connection)
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //设置连接参数:ip,端口号,用户名,密码,虚拟机
        connectionFactory.setHost("");//ip
        connectionFactory.setPort(5672);//端口号,默认是5672
        connectionFactory.setUsername("Test");//用户名
        connectionFactory.setPassword("123");//密码
        connectionFactory.setVirtualHost("TestVirtualHost");//设置操作的虚拟机
        Connection connection = connectionFactory.newConnection();//从连接工厂中获取到连接

        //2. 开启通道(Channel)
        Channel channel = connection.createChannel();//通过connection开启通道

        //3. 声明交换机
        channel.exchangeDeclare("DIRECT_EXCHANGE", BuiltinExchangeType.DIRECT, true);

        //4.声明队列
        channel.queueDeclare("DIRQueue1", true, false, false, null);
        channel.queueDeclare("DIRQueue2", true, false, false, null);

        //5.交换机和队列进行绑定
        channel.queueBind("DIRQueue1", "DIRECT_EXCHANGE", "a");
        channel.queueBind("DIRQueue2", "DIRECT_EXCHANGE", "a");
        channel.queueBind("DIRQueue2", "DIRECT_EXCHANGE", "b");
        channel.queueBind("DIRQueue2", "DIRECT_EXCHANGE", "c");

        String msgA = "HelloDirA";//消息的内容
        String msgB = "HelloDirB";//消息的内容
        String msgC = "HelloDirC";//消息的内容
        //发送消息
        channel.basicPublish("DIRECT_EXCHANGE", "a", null, msgA.getBytes());
        channel.basicPublish("DIRECT_EXCHANGE", "b", null, msgB.getBytes());
        channel.basicPublish("DIRECT_EXCHANGE", "c", null, msgC.getBytes());

        System.out.println("消息发送成功");
        //6.释放资源
        channel.close();
        connection.close();

    }
}

运行代码,观察控制台

在这里插入图片描述

可以看到,队列1收到了一条消息,队列2收到了3条消息,接着编写消费者代码,两个消费者代码类似,只需要把队列名改改

public class Consumer1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 建立连接(Connection)
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //设置连接参数:ip,端口号,用户名,密码,虚拟机
        connectionFactory.setHost("");//ip
        connectionFactory.setPort(5672);//端口号,默认是5672
        connectionFactory.setUsername("Test");//用户名
        connectionFactory.setPassword("123");//密码
        connectionFactory.setVirtualHost("TestVirtualHost");//设置操作的虚拟机
        Connection connection = connectionFactory.newConnection();//从连接工厂中获取到连接

        //2. 开启通道(Channel)
        Channel channel = connection.createChannel();//通过connection开启通道

        //3.声明队列,如果已经声明过则不创建
        channel.queueDeclare("DIRQueue1", true, false, false, null);

        //4.消费消息

        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("接收到消息" + new String(body));
            }
        };
        /**
         * 方法参数
         * queue:队列名
         * autoAck:是否自动确认
         * Consumer:接收到消息后执行的逻辑
         * **/
        channel.basicConsume("DIRQueue1", true, consumer);

    }
}

运行两个消费者代码

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

5. 通配符模式

用代码实现如图结构
在这里插入图片描述

编写生产者代码:

public class Producer {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 建立连接(Connection)
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //设置连接参数:ip,端口号,用户名,密码,虚拟机
        connectionFactory.setHost("");//ip
        connectionFactory.setPort(5672);//端口号,默认是5672
        connectionFactory.setUsername("Test");//用户名
        connectionFactory.setPassword("123");//密码
        connectionFactory.setVirtualHost("TestVirtualHost");//设置操作的虚拟机
        Connection connection = connectionFactory.newConnection();//从连接工厂中获取到连接

        //2. 开启通道(Channel)
        Channel channel = connection.createChannel();//通过connection开启通道

        //3. 声明交换机
        channel.exchangeDeclare("TOPIC_EXCHANGE", BuiltinExchangeType.TOPIC, true);

        //4.声明队列
        channel.queueDeclare("TOPICQueue1", true, false, false, null);
        channel.queueDeclare("TOPICQueue2", true, false, false, null);

        //5.交换机和队列进行绑定
        channel.queueBind("TOPICQueue1", "TOPIC_EXCHANGE", "*.a.*");
        channel.queueBind("TOPICQueue2", "TOPIC_EXCHANGE", "*.*.b");
        channel.queueBind("TOPICQueue2", "TOPIC_EXCHANGE", "c.#");

        String msgA = "HelloTopic_ae.a.f";//消息的内容
        String msgB = "HelloTopic_ef.a.b";//消息的内容
        String msgC = "HelloTopic_c.ef.d";//消息的内容
        //发送消息
        channel.basicPublish("TOPIC_EXCHANGE", "ae.a.f", null, msgA.getBytes());//转发到Queue1
        channel.basicPublish("TOPIC_EXCHANGE", "ef.a.b", null, msgB.getBytes());//转发到Queue1和Queue2
        channel.basicPublish("TOPIC_EXCHANGE", "c.ef.d", null, msgC.getBytes());//转发到Queue2

        System.out.println("消息发送成功");
        //6.释放资源
        channel.close();
        connection.close();

    }
}

观察控制台

在这里插入图片描述

两个队列都有两条消息

接下来编写消费者代码,两个消费者代码一致,只需要修改队列名

public class Consumer {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 建立连接(Connection)
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //设置连接参数:ip,端口号,用户名,密码,虚拟机
        connectionFactory.setHost("");//ip
        connectionFactory.setPort(5672);//端口号,默认是5672
        connectionFactory.setUsername("Test");//用户名
        connectionFactory.setPassword("123");//密码
        connectionFactory.setVirtualHost("TestVirtualHost");//设置操作的虚拟机
        Connection connection = connectionFactory.newConnection();//从连接工厂中获取到连接

        //2. 开启通道(Channel)
        Channel channel = connection.createChannel();//通过connection开启通道

        //3.声明队列,如果已经声明过则不创建
        channel.queueDeclare("TOPICQueue2", true, false, false, null);

        //4.消费消息

        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("接收到消息" + new String(body));
            }
        };
        /**
         * 方法参数
         * queue:队列名
         * autoAck:是否自动确认
         * Consumer:接收到消息后执行的逻辑
         * **/
        channel.basicConsume("TOPICQueue2", true, consumer);

    }
}

6. RPC模式

在这里插入图片描述

客户端代码

public class Client {
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //1. 建立连接(Connection)
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //设置连接参数:ip,端口号,用户名,密码,虚拟机
        connectionFactory.setHost("");//ip
        connectionFactory.setPort(5672);//端口号,默认是5672
        connectionFactory.setUsername("Test");//用户名
        connectionFactory.setPassword("123");//密码
        connectionFactory.setVirtualHost("TestVirtualHost");//设置操作的虚拟机
        Connection connection = connectionFactory.newConnection();//从连接工厂中获取到连接

        //2. 开启通道(Channel)
        Channel channel = connection.createChannel();//通过connection开启通道

        String msg = "HelloRPC";
        String correlationId = UUID.randomUUID().toString();
        //
        channel.queueDeclare("RPC_RESPONSE_QUEUE",true,false,false,null);
        channel.queueDeclare("RPC_REQUEST_QUEUE",true,false,false,null);

        //发送请求
        AMQP.BasicProperties properties = new AMQP.BasicProperties().builder()
                .correlationId(correlationId)//设置correlationId
                .replyTo("RPC_RESPONSE_QUEUE")//设置响应的队列
                .build();
        channel.basicPublish("", "RPC_REQUEST_QUEUE", properties, msg.getBytes());

        //使用阻塞队列,来存储响应信息
        final BlockingQueue<String> response = new LinkedBlockingQueue<>(1);
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String responseMsg = new String(body);
                System.out.println("接收到回调消息" + responseMsg);
                if (correlationId.equals(properties.getCorrelationId())) {
                    //如果correlationId相等,把消息放进阻塞队列中
                    response.offer(responseMsg);
                }
            }
        };
        channel.basicConsume("RPC_RESPONSE_QUEUE", true, consumer);

        //从阻塞队列中
        System.out.println("响应结果"+response.take());


        //6.释放资源
        channel.close();
        connection.close();

    }
}

服务端代码:

public class Server {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 建立连接(Connection)
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //设置连接参数:ip,端口号,用户名,密码,虚拟机
        connectionFactory.setHost("");//ip
        connectionFactory.setPort(5672);//端口号,默认是5672
        connectionFactory.setUsername("Test");//用户名
        connectionFactory.setPassword("123");//密码
        connectionFactory.setVirtualHost("TestVirtualHost");//设置操作的虚拟机
        Connection connection = connectionFactory.newConnection();//从连接工厂中获取到连接

        //2. 开启通道(Channel)
        Channel channel = connection.createChannel();//通过connection开启通道

        channel.basicQos(1);
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String request = new String(body);

                String response = "响应成功!!!!!!!!!" + request;
                System.out.println(response);
                AMQP.BasicProperties basicProperties = new AMQP.BasicProperties()
                        .builder()
                        .correlationId(properties.getCorrelationId())
                        .build();

                channel.basicPublish("", "RPC_RESPONSE_QUEUE",basicProperties, response.getBytes());
                channel.basicAck(envelope.getDeliveryTag(),false);//手动确认
            }
        };

        //3.接受请求
        channel.basicConsume("RPC_REQUEST_QUEUE", false, consumer);

        //6.释放资源
        channel.close();
        connection.close();
    }
}

先运行客户端代码,观察控制台

在这里插入图片描述

接着运行服务端代码

在这里插入图片描述

在这里插入图片描述

7. 发布确认

有三种策略

1、单独确认:每发送一条消息,就等待确认,确认完毕再发送下一条消息

2、批量确认:发送消息的数量达到一定数量的时候再确认

3、异步确认:

代码案例:

public class PublisherConfirms {
    private static final Integer MESSAGE_COUNT = 10000;

    public static Connection createConnection() throws IOException, TimeoutException {
        //1. 建立连接(Connection)
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //设置连接参数:ip,端口号,用户名,密码,虚拟机
        connectionFactory.setHost("116.205.165.92");//ip
        connectionFactory.setPort(5672);//端口号,默认是5672
        connectionFactory.setUsername("Test");//用户名
        connectionFactory.setPassword("123");//密码
        connectionFactory.setVirtualHost("TestVirtualHost");//设置操作的虚拟机
        //从连接工厂中获取到连接
        return connectionFactory.newConnection();
    }

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //1.单独确认
//        publishMessagesIndividually();
        //2.批量确认
        publishMessagesInBatches();
        //3.异步确认
        handlingPublisherConfirmsAsynchronously();

    }

    //1.单独确认,每发送一条消息,就等待确认,确认完毕再发送下一条消息
    private static void publishMessagesIndividually() throws IOException, TimeoutException, InterruptedException {
        try (Connection connection = createConnection()) {
            //1.开启信道
            Channel channel = connection.createChannel();
            //2.设置信道为confirm模式,开启之后,基于该信道发送的消息都会生成一个id,用于消息的确认
            channel.confirmSelect();
            //3.声明队列
            channel.queueDeclare("PUBLISHER_CONFIRM_QUEUE1", true, false, false, null);
            //4.发送消息并等待确认
            long start = System.currentTimeMillis();
            for (int i = 0; i < MESSAGE_COUNT; i++) {
                String msg = "HelloMessage" + i;
                channel.basicPublish("", "PUBLISHER_CONFIRM_QUEUE1", null, msg.getBytes());
                //等待确认
                channel.waitForConfirmsOrDie(5000);//如果超时了会抛出异常
            }
            long end = System.currentTimeMillis();
            System.out.println("批量确认策略,消息条数" + MESSAGE_COUNT + "耗时" + (end - start) + "ms");

        }
    }


    //2.批量确认
    private static void publishMessagesInBatches() throws IOException, TimeoutException, InterruptedException {
        try (Connection connection = createConnection()) {
            //1.开启信道
            Channel channel = connection.createChannel();
            //2.设置信道为confirm模式,开启之后,基于该信道发送的消息都会生成一个id,用于消息的确认
            channel.confirmSelect();
            //3.声明队列
            channel.queueDeclare("PUBLISHER_CONFIRM_QUEUE2", true, false, false, null);
            //4.发送消息并等待确认
            int count = 0;
            int batchSize = 50;//批量处理的消息数量
            long start = System.currentTimeMillis();
            for (int i = 0; i < MESSAGE_COUNT; i++) {
                String msg = "HelloMessage" + i;
                channel.basicPublish("", "PUBLISHER_CONFIRM_QUEUE2", null, msg.getBytes());
                count++;
//                等待确认
                if (count == batchSize) {//如果发送的消息达到50条,就批量确认
                    channel.waitForConfirmsOrDie(5000);//如果超时了会抛出异常
                    count = 0;
                }
            }
            //如果count>0再整体进行确认
            if (count > 0) {
                channel.waitForConfirmsOrDie(5000);//如果超时了会抛出异常
            }
            long end = System.currentTimeMillis();
            System.out.println("批量确认策略,消息条数" + MESSAGE_COUNT + "耗时" + (end - start) + "ms");

        }

    }

    //3.异步确认
    private static void handlingPublisherConfirmsAsynchronously() throws IOException, TimeoutException, InterruptedException {
        try (Connection connection = createConnection()) {
            //1.开启信道
            Channel channel = connection.createChannel();
            //2.设置信道为confirm模式,开启之后,基于该信道发送的消息都会生成一个id,用于消息的确认
            channel.confirmSelect();
            //3.声明队列
            channel.queueDeclare("PUBLISHER_CONFIRM_QUEUE3", true, false, false, null);

            //
            long start = System.currentTimeMillis();
            SortedSet<Long> confirmSeqNo = Collections.synchronizedSortedSet(new TreeSet<>());//存储未确认的消息的id
            //4.异步监听
            channel.addConfirmListener(new ConfirmListener() {
                @Override
                public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                    //处理确认
                    if (multiple) {
                        //如果是批量处理,清除掉当前id之前的消息
                        confirmSeqNo.headSet(deliveryTag + 1).clear();
                    } else {
                        //如果不是批量处理,清除当前的这个id就可以了
                        confirmSeqNo.remove(deliveryTag);
                    }
                }

                @Override
                public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                    //处理
                    if (multiple) {
                        //如果是批量处理,清除掉当前id之前的消息
                        confirmSeqNo.headSet(deliveryTag + 1).clear();
                    } else {
                        //如果不是批量处理,清除当前的这个id就可以了
                        confirmSeqNo.remove(deliveryTag);
                    }
                    //重发消息
                }
            });
            //5.发送消息
            for (int i = 0; i < MESSAGE_COUNT; i++) {
                String msg = "HelloMessage" + i;
                long id = channel.getNextPublishSeqNo();
                channel.basicPublish("", "PUBLISHER_CONFIRM_QUEUE3", null, msg.getBytes());
                confirmSeqNo.add(id);//id加入集合中,表示消息发送完了,但是还没确认
            }
            while (!confirmSeqNo.isEmpty()) {//如果存储id的集合消息不为空,说明还有消息没有确认
                Thread.sleep(10);
            }
            long end = System.currentTimeMillis();
            System.out.println("异步确认策略,消息条数" + MESSAGE_COUNT + "耗时" + (end - start) + "ms");


        }
    }

}

在这里插入图片描述

使用Spring Boot完成RabbitMQ的通信

工作队列模式

创建SpringBoot项目,添加RabbitMQ相关的依赖(为了方便学习,把SpringWeb也加上~)

在这里插入图片描述

添加相关配置

spring.rabbitmq.host=ip地址
spring.rabbitmq.password=密码
spring.rabbitmq.port=端口号
spring.rabbitmq.username=用户名

1、声明队列

/**
 * 声明队列
 * @return
 */
@Bean("workQueue")
public Queue workQueue() {
    return QueueBuilder.durable(Constants.WORK_QUEUE).build();
}

注意,这里导入的不是Java的Queue而是org.springframework.amqp.core的Queue
在这里插入图片描述

2、编写生产者代码

@RestController
@RequestMapping("/producer")
public class ProducerController {

    //使用RabbitTemplate,进行消息的发送
    @Autowired
    private RabbitTemplate rabbitTemplate;


    @RequestMapping("/work")
    public String work() {
        rabbitTemplate.convertAndSend("", "WORK_QUEUE", "Hello Spring AMQP");
        return "发送成功";
    }
}

在浏览器中访问这个接口即可完成消息的发送

3、编写消费者代码

@Component
public class WorkListener {

    @RabbitListener(queues = Constants.WORK_QUEUE)//要从哪个队列中获取消息
    public void queueListener(Message message) {
        System.out.println("listener1[" + Constants.WORK_QUEUE + "] 接收到消息: " + message);
    }
    //一个RabbitListener就是一个消费者,如果只有一个RabbitListener,那就是简单模式了
    @RabbitListener(queues = Constants.WORK_QUEUE)//要从哪个队列中获取消息
    public void queueListener2(Message message) {
        System.out.println("listener2[" + Constants.WORK_QUEUE + "] 接收到消息: " + message);
    }
}

@RabbitListener是Spring框架中用于监听RabbitMQ队列的注解,通过这个注解可以定义一个方法,以便从RabbitMQ中接收消息,参数类型

程序一启动就会监听队列中有没有消息,如果有就消费

发布订阅模式

生产者代码

1、声明队列

/**
 * 声明队列-发布订阅模式
 *
 * @return
 */
@Bean("fanoutQueue1")
public Queue fanoutQueue1() {
    return QueueBuilder.durable(Constants.FANOUT_QUEUE1).build();
}

@Bean("fanoutQueue2")
public Queue fanoutQueue2() {
    return QueueBuilder.durable(Constants.FANOUT_QUEUE2).build();
}

2、声明交换机

/**
 * 声明交换机-发布订阅模式
 * @return
 */
@Bean("fanoutExchange")
public FanoutExchange fanoutExchange() {
    return ExchangeBuilder.fanoutExchange(Constants.FANOUT_EXCHANGE).durable(true).build();
}

3、交换机和队列的绑定

/**
 * 交换机和队列的绑定
 * @param exchange
 * @param queue
 * @return
 */
@Bean("fanoutQueueBinding1")
public Binding fanoutQueueBinding1(@Qualifier("fanoutExchange") FanoutExchange exchange,@Qualifier("fanoutQueue1") Queue queue){
    return BindingBuilder.bind(queue).to(exchange);
}
/**
 * 交换机和队列的绑定
 * @param exchange
 * @param queue
 * @return
 */
@Bean("fanoutQueueBinding1")
public Binding fanoutQueueBinding2(@Qualifier("fanoutExchange") FanoutExchange exchange,@Qualifier("fanoutQueue2") Queue queue){
    return BindingBuilder.bind(queue).to(exchange);
}

4、发送消息

@RequestMapping("/fanout")
public String fanout() {
    rabbitTemplate.convertAndSend(Constants.FANOUT_EXCHANGE,"","Hello Spring qmqp:fanout");
    return "发送成功";
}

编写消费者代码

@Component
public class FanoutListener {

    @RabbitListener(queues = Constants.FANOUT_QUEUE1)//要从哪个队列中获取消息
    public void queueListener1(Message message) {
        System.out.println("[" + Constants.FANOUT_QUEUE1 + "] 接收到消息: " + message);
    }

    @RabbitListener(queues = Constants.FANOUT_QUEUE2)//要从哪个队列中获取消息
    public void queueListener2(Message message) {
        System.out.println("[" + Constants.FANOUT_QUEUE2 + "] 接收到消息: " + message);
    }
}

路由模式

1、声明队列

@Bean("directQueue1")
public Queue directQueue1() {
    return QueueBuilder.durable(Constants.DIRECT_QUEUE1).build();
}
@Bean("directQueue2")
public Queue directQueue2() {
    return QueueBuilder.durable(Constants.DIRECT_QUEUE2).build();
}

2、声明交换机

@Bean("directExchange")
public DirectExchange directExchange() {
    return ExchangeBuilder.directExchange(Constants.DIRECT_EXCHANGE).durable(true).build();
}

3、绑定

@Bean("directQueueBinding1")
public Binding directQueueBinding1(@Qualifier("directExchange") DirectExchange directExchange, @Qualifier("directQueue1") Queue queue) {
    return BindingBuilder.bind(queue).to(directExchange).with("orange");
}
@Bean("directQueueBinding2")
public Binding directQueueBinding2(@Qualifier("directExchange") DirectExchange directExchange, @Qualifier("directQueue2") Queue queue) {
    return BindingBuilder.bind(queue).to(directExchange).with("black");
}
@Bean("directQueueBinding3")
public Binding directQueueBinding3(@Qualifier("directExchange") DirectExchange directExchange, @Qualifier("directQueue2") Queue queue) {
    return BindingBuilder.bind(queue).to(directExchange).with("orange");
}

4、发送消息

@RequestMapping("/direct/{routingKey}")
public String direct(@PathVariable("routingKey") String routingKey) {
    rabbitTemplate.convertAndSend(Constants.DIRECT_EXCHANGE, routingKey, "Hello Spring qmqp:direct" + "routingKey is " + routingKey);
    return "发送成功";
}

5、消费者代码

@Component
public class DirectListener {

    @RabbitListener(queues = Constants.DIRECT_QUEUE1)//要从哪个队列中获取消息
    public void queueListener1(Message message) {
        System.out.println("[" + Constants.DIRECT_QUEUE1 + "] 接收到消息: " + message);
    }

    @RabbitListener(queues = Constants.DIRECT_QUEUE2)//要从哪个队列中获取消息
    public void queueListener2(Message message) {
        System.out.println("[" + Constants.DIRECT_QUEUE2 + "] 接收到消息: " + message);
    }
}

通配符模式

1、声明队列

//通配符模式
//声明队列
@Bean("topicQueue1")
public Queue topicQueue1() {
    return QueueBuilder.durable(Constants.TOPIC_QUEUE1).build();
}

@Bean("topicQueue2")
public Queue topicQueue2() {
    return QueueBuilder.durable(Constants.TOPIC_QUEUE2).build();
}

2、声明交换机

//声明交换机
@Bean("topicExchange")
public TopicExchange topicExchange() {
    return ExchangeBuilder.topicExchange(Constants.TOPIC_EXCHANGE).durable(true).build();
}

3、绑定

//绑定
@Bean("topicQueueBinding1")
public Binding topicQueueBinding1(@Qualifier("topicExchange") TopicExchange topicExchange, @Qualifier("topicQueue1") Queue queue) {
    return BindingBuilder.bind(queue).to(topicExchange).with("*.orange.*");
}

//绑定
@Bean("topicQueueBinding2")
public Binding topicQueueBinding2(@Qualifier("topicExchange") TopicExchange topicExchange, @Qualifier("topicQueue2") Queue queue) {
    return BindingBuilder.bind(queue).to(topicExchange).with("*.*.rabbit");
}

//绑定
@Bean("topicQueueBinding3")
public Binding topicQueueBinding3(@Qualifier("topicExchange") TopicExchange topicExchange, @Qualifier("topicQueue2") Queue queue) {
    return BindingBuilder.bind(queue).to(topicExchange).with("lazy.#");
}

4、发送消息

@RequestMapping("/topic/{routingKey}")
public String topic(@PathVariable("routingKey") String routingKey) {
    rabbitTemplate.convertAndSend(Constants.TOPIC_EXCHANGE, routingKey, "Hello Spring qmqp:topic" + "routingKey is " + routingKey);
    return "发送成功";
}

5、消费消息

@Component
public class TopicListener {

    @RabbitListener(queues = Constants.TOPIC_QUEUE1)//要从哪个队列中获取消息
    public void queueListener1(Message message) {
        System.out.println("[" + Constants.TOPIC_QUEUE1 + "] 接收到消息: " + message);
    }

    @RabbitListener(queues = Constants.TOPIC_QUEUE2)//要从哪个队列中获取消息
    public void queueListener2(Message message) {
        System.out.println("[" + Constants.TOPIC_QUEUE2 + "] 接收到消息: " + message);
    }
}

RabbitListener注解

RabbitListener是类注解,也是方法注解,如果用在类上,需要搭配RabbitHandler注解

@Component
@RabbitListener(queues = "queue")
public class Test {
    @RabbitHandler
    public void handMessage(String message) {
        System.out.println("接受到消息" + message);
    }
}

RabbitListener会根据消息的内容选择不同的方法,例如

@Component
@RabbitListener(queues = "queue")
public class TestListener {
    @RabbitHandler
    public void handMessage(String message) {
        System.out.println("接受到消息" + message);
    }
    @RabbitHandler
    public void handMessage(Integer message) {
        System.out.println("接受到消息" + message);
    }
}

如果消息是String类型的,就会调用第一个方法,如果是Integer类型的,则调用第二个方法

发送对象

定义OrderInfo对象

@Data
public class OrderInfo implements Serializable {
    private String id;
    private String name;
}
@RestController
@RequestMapping("/order")
public class TestController {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @RequestMapping("/create2")
    public String create2() {
        OrderInfo info = new OrderInfo();
        info.setId(UUID.randomUUID().toString());
        info.setName("Order1");
        rabbitTemplate.convertAndSend("", "order.create", info);
        return "成功";
    }
}

将消息转换成json格式,需要调用setMessageConverter方法:

@RequestMapping("/create2")
public String create2() {
    OrderInfo info = new OrderInfo();
    info.setId(UUID.randomUUID().toString());
    info.setName("Order1");
    rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
    rabbitTemplate.convertAndSend("", "order.create", info);
    return "下单成功";
}

这样做不太美观,我们可以自己配置RabbitTemplate,设置转换格式,同时交给Spring管理,这样注入进来的就是我们自己的RabbitTemplate了

@Configuration
public class RabbitMQConfig {
    @Bean
    public Jackson2JsonMessageConverter jackson2JsonMessageConverter() {
        return new Jackson2JsonMessageConverter();
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory factory, Jackson2JsonMessageConverter jackson2JsonMessageConverter) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(factory);
        rabbitTemplate.setMessageConverter(jackson2JsonMessageConverter);
        return rabbitTemplate;
    }
}

消费对象

消费对象只需要监听即可,可以在类上加RabbitListener注解搭配RabbitHandler一起使用,也可以只把RabbitListener加在方法上,而方法参数则是需要消费的对象,例如

@Component
@RabbitListener(queues = "order.create")//queues是对应的队列
public class OrderListener {
    @RabbitHandler
    public void handMessage(String orderInfo) {
        System.out.println("接收到消息" + orderInfo);
    }
    @RabbitHandler
    public void handMessage(OrderInfo orderInfo) {
        System.out.println("接收到消息" + orderInfo);
    }
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值