RabbitMQ_3_RabbitMQ应用

RabbitMQ官方文档:RabbitMQ Tutorials | RabbitMQ

这是官方文档中提及的7种工作模式:

7种工作模式介绍

Simple(简单模式)

P:生产者

C:消费者

Queue:队列

一个生产者,一个消费者,点到点模式。

我们上篇文章介绍的入门程序就属于点到点模式。

Work Queue(工作队列)

一个生产者,多个消费者。假如队列种有10条消息,C1和C2是共同消费这10条消息的,消息不会重复消费。

Publish/Subscribe(发布/订阅)

这里和工作模式不同此时,两个消费者都会收到同一条消息,相当于每条消息都会复制多份,C1和C2都能收到这条消息,就像广播一样。

这里的X表示交换机,在订阅模型种多了一个Exchange角色,过程略有变化。

Exchange介绍:

作用:生产者将消息发送到Exchange,由交换机将消息按一定规则路由到一个或者多个队列种(上图中(工作模式和简单模式)生产者直接将消息投递到队列中,实际上这个在RabbitMQ中不会发生,只是上面使用默认交换机,对交换机进行省略)。

RabbitMQ交换机有四种类型:fanout,direct,topic,headers,不同类型有着不同的路由策略。AMQP协议里还有另外两种类型,Systen和自定义,此处不做介绍。

  1. Fanout:广播,将消息交给所有绑定到交换机的队列(Publish/Subscribe模式)
  2. Direct:定向,把消息交给符合指定rounting key的队列(Routing模式)
  3. Topic:通配符,把消息交给符合rounting pattern(路由模式)的队列(Topics模式)
  4. headers类型的交换机不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容的headers属性进行匹配。headers类型的交换机性能会很差,而且也不实用,基本不会看到它的存在。

Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息就会丢失。

**RountingKey:**路由键。生产者将消息发给交换机时,指定的一个字符串,用来告诉交换机应该如何处理这个消息。

BindingKey: 绑定,RabbitMQ中通过Binding将交换机和队列关联起来,在绑定的时候一般会指定一个Binding Key,这样RabbitMQ就知道如何正确地将消息路由到队列了。

Rounting(路由模式)

相当于发布订阅模式的变种,增加了RountingKey。

注意:这里的BindingKey实际上也是一个RountingKey,只是它被用来绑定交换机和队列的关系。

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

Topics(通配符模式)

路由模式的升级版,这里*表示匹配一个单词,#表示匹配多个单词。

比如:

  • apple.a.orange会被匹配到Q1
  • apple.orange.b会被匹配到Q2
  • c.apple.orange会被匹配到Q2
  • 而apple.a.b则时会被匹配到Q1、Q2
  • 同理,c.a.banana也会被匹配到Q1、Q2

适合场景:需要灵活匹配哦和过滤消息的场景。

RPC模式

在RPC通信的过程中,没有生产者和消费者,比较像咱们的RPC远程调用,大概就是通过两个队列实现了一个可回调的过程。

  1. 客户端发送消息到一个指定的队列,并在消息属性中设置replyTo字段,这个指定了一个回调队列
  2. 服务端接收到请求后,处理请求并发送响应消息到replyTo指定的回调队列
  3. 客户端在回调队列上等待响应消息。一旦收到响应,客户端会检查消息的correlationId属性,以确保它时所期望的响应。

Publisher Confirms(发布确认)

Publisher Confirms模式是RabbitMQ提供的一种确保消息可靠发送到RabbitMQ服务器的机制。在这种模式下,生产者可以等待RabbitMQ服务器的确认,以确保消息已经被服务器接收并处理。

1、生产者将Channel设置为confirm模式(通过调用channel.confirmSelect()完成)后,发布的每一条消息都会获得一个唯一的ID,生产者可以将这些序列号和消息关联起来,以便跟踪消息的状态。

2、当消息被RabbitMQ服务器接收并处理后,服务器会异步地向生产者发送一个确认(ACK)给生产者(包含消息的唯一ID),表明消息已经送达。

通过Publisher Confirms模式,生产者可以确保消息被RabbitMQ服务器成功接收,从而避免消息丢失的问题。

适用场景:对数据安全性要求较高的场景,比如金融交易,订单处理。

工作模式使用

简单模式

上篇文章的入门程序已经讲过,此处粘贴代码。

生产者代码:

java 复制代码
public class ProducerDemo {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1、建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("106.52.188.165");
        connectionFactory.setPort(5672);//需要提前开放端口号
        connectionFactory.setUsername("admin");//账号
        connectionFactory.setPassword("admin");//密码
        connectionFactory.setVirtualHost("xmy");//虚拟主机
        Connection connection = connectionFactory.newConnection();
        //2、开启信道
        Channel channel = connection.createChannel();
        //3、声明交换机       使用内置的交换机
        //4、声明队列
        /**
         * queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments) throws IOException;
         * 参数介绍:
         * queue:队列名称
         *durable:可持久化
         * exclusive:是否独占
         * autoDelete:是否自动删除
         * argument:参数
         */
      channel.queueDeclare("hello",true,false,false,null);
        //5、发送消息
        /**
         * basicPublish(String exchange,String routingKey,BasicProperties prop,byte[] body)
         * 参数说明:
         * exchange:交换机名称
         * routingKey:内置交换机,routingKey和队列名称保持一致
         * props:属性配置
         * body:消息体
         */
//        for (int i = 0; i < 10; i++) {
//            String msg = "hello,xmy!"+i;
//            channel.basicPublish("","hello",null,msg.getBytes());
//        }
        String msg = "hello,xmy!";
        channel.basicPublish("","hello",null,msg.getBytes());
        System.out.println("消息发送成功!!!");
        //6、资源释放
        channel.close();
        connection.close();

消费者代码:

java 复制代码
public class ComsumerDemo {
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //1、创建连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("106.52.188.165");
        connectionFactory.setPort(5672);//需要提前开放端口号
        connectionFactory.setUsername("admin");//账号
        connectionFactory.setPassword("admin");//密码
        connectionFactory.setVirtualHost("xmy");//虚拟主机
        Connection connection = connectionFactory.newConnection();
        //2、创建channel
        Channel channel = connection.createChannel();
        //3、声明队列(可以省略)
        channel.queueDeclare("hello",true,false,false,null);
        //4、消费消息
        /**
         * basicConsume(String queue,boolean autoAck,Consumer1 callback)
         * 参数说明:
         * queue:队列名称
         * autoAck:是否自动确认
         * callback:接收消息后执行的逻辑是啥
         */
        DefaultConsumer consumer = new DefaultConsumer(channel){
            //从队列种收到消息的方法
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //TODO
                System.out.println("接收到消息:"+ new String(body));
            }
        };
        channel.basicConsume("hello",true,consumer);
        //等待程序执行完成
        Thread.sleep(2000);
        //5、释放资源
        channel.close();
        connection.close();
    }
}

Work Queues(工作队列模式)

工作模式是简单模式的增强版,和简单模式的区别就是:简单模式有一个消费者,工作队列模式支持多个消费者,消费者之间是竞争关系,每个消息只能被一个消费者接收。

常量定义:

java 复制代码
 public static final String HOST = 主机IP;

    public static final Integer PORT = 5672;

    public static final String USER_NAME = "admin";

    public static final String PASSWORD = "admin";

    public static final String VIRTUAL_HOST = "xmy";

    //工作队列模式
    public static final String WORK_QUEUE = "work.queue";

生产者代码:

java 复制代码
public class Producer {
    public static void main(String[] args) throws IOException, TimeoutException {
    //1、建立连接
    ConnectionFactory connectionFactory = new ConnectionFactory();
    connectionFactory.setHost(Constants.HOST);
    connectionFactory.setPort(Constants.PORT);//需要提前开放端口号
    connectionFactory.setUsername(Constants.USER_NAME);//账号
    connectionFactory.setPassword(Constants.PASSWORD);//密码
    connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST);//虚拟主机
    Connection connection = connectionFactory.newConnection();
    //2、开启信道
    Channel channel = connection.createChannel();
    //3、声明交换机       使用内置的交换机
    //4、声明队列
    /**
     * queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments) throws IOException;
     * 参数介绍:
     * queue:队列名称
     *durable:可持久化
     * exclusive:是否独占
     * autoDelete:是否自动删除
     * argument:参数
     */
    channel.queueDeclare(Constants.WORK_QUEUE,true,false,false,null);
    //5、发送消息
    /**
     * basicPublish(String exchange,String routingKey,BasicProperties prop,byte[] body)
     * 参数说明:
     * exchange:交换机名称
     * routingKey:内置交换机,routingKey和队列名称保持一致
     * props:属性配置
     * body:消息体
     */
        for (int i = 0; i < 10; i++) {
            String msg = "hello,xmyWorker!"+i;
            channel.basicPublish("",Constants.WORK_QUEUE,null,msg.getBytes());
        }
//    String msg = "hello,xmy!";
//    channel.basicPublish("","hello",null,msg.getBytes());
    System.out.println("消息发送成功!!!");
    //6、资源释放
    channel.close();
    connection.close();
}
}

消费者代码:

ps:这里因为有两个消费者因此需要复制出两份

java 复制代码
public class Consumer1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1、建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT);//需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);//密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST);//虚拟主机
        Connection connection = connectionFactory.newConnection();
        //2、开启信道
        Channel channel = connection.createChannel();
        //3、声明交换机       使用内置的交换机
        //4、声明队列    如果队列不存在,则创建,如果队列存在则不创建
        /**
         * queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments) throws IOException;
         * 参数介绍:
         * queue:队列名称
         *durable:可持久化
         * exclusive:是否独占
         * autoDelete:是否自动删除
         * argument:参数
         */

        channel.queueDeclare(Constants.WORK_QUEUE,true,false,false,null);
        //5、消费消息
        /**
         * basicPublish(String exchange,String routingKey,BasicProperties prop,byte[] body)
         * 参数说明:
         * exchange:交换机名称
         * routingKey:内置交换机,routingKey和队列名称保持一致
         * props:属性配置
         * body:消息体
         */
        DefaultConsumer consumer = new DefaultConsumer(channel){
            //从队列种收到消息的方法
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //TODO
                System.out.println("接收到消息:"+ new String(body));
            }
        };
        channel.basicConsume(Constants.WORK_QUEUE,true,consumer);

//    String msg = "hello,xmy!";
//    channel.basicPublish("","hello",null,msg.getBytes());
//        System.out.println("消息发送成功!!!");
        //6、资源释放
//        channel.close();
//        connection.close();

    }
}

先运行两个消费者:

可以看到,此时就生成了work.queue,并且显示有两个消费者。

再启动生产者.

消费者1:

消费者2:

Publish/Subscribe(发布/订阅)

此处就与上面两个工作模式不同了需要场景交换机并绑定交换机和队列。

常量:

java 复制代码
   //发布订阅模式
    public static final String FANOUT_EXCHANGE = "fanout.exchange";
    public static final String FANOUT_QUEUE1 = "fanout.queue1";
    public static final String FANOUT_QUEUE2 = "fanout.queue2";

生产者:

java 复制代码
public class Producer {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1、建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT);//需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);//密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST);//虚拟主机
        Connection connection = connectionFactory.newConnection();
        //2、开启信道
        Channel channel = connection.createChannel();
        //3、声明交换机
        /**
         * exchangeDeclare(String var1, BuiltinExchangeType var2, boolean var3, boolean var4, boolean var5, Map<String, Object> var6) throws IOException;
         * 参数详解:
         * exchange:交换机名称
         * type:交换机类型
         * durable:持久化
         * autoDelete:是否自动删除
         * internal:是否内部使用
         * argument:参数设置
         */
        channel.exchangeDeclare(Constants.FANOUT_EXCHANGE, BuiltinExchangeType.FANOUT,true);
        //4、声明队列
        channel.queueDeclare(Constants.FANOUT_QUEUE1,true,false,false,null);
        channel.queueDeclare(Constants.FANOUT_QUEUE2,true,false,false,null);
        //5、交换机和队列绑定        rountingKey为空表示:无论收到任何消息都直接发送到队列上
        channel.queueBind(Constants.FANOUT_QUEUE1,Constants.FANOUT_EXCHANGE,"");
        channel.queueBind(Constants.FANOUT_QUEUE2,Constants.FANOUT_EXCHANGE,"");
        //6、发布消息
        String msg = "hello,xmy!";
        channel.basicPublish(Constants.FANOUT_EXCHANGE,"",null,msg.getBytes());
        System.out.println("消息发送成功!!!");
        //7、释放资源
        channel.close();
        connection.close();
    }
}

注意:此处声明的交换机是fanout类型的,并且设置rountingKey为空表示将消息给每个队列都发一份。

消费者1:

java 复制代码
public class Comsumer1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1、建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT);//需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);//密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST);//虚拟主机
        Connection connection = connectionFactory.newConnection();
        //2、开启信道
        Channel channel = connection.createChannel();
        //3、声明队列
        channel.queueDeclare(Constants.FANOUT_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 {
                //TODO
                System.out.println("接收到消息:"+ new String(body));
            }
        };
        channel.basicConsume(Constants.FANOUT_QUEUE1,true,consumer);
    }
}

消费者2:(修改队列名称)

java 复制代码
public class Comsumer2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1、建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT);//需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);//密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST);//虚拟主机
        Connection connection = connectionFactory.newConnection();
        //2、开启信道
        Channel channel = connection.createChannel();
        //3、声明队列
        channel.queueDeclare(Constants.FANOUT_QUEUE2,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 {
                //TODO
                System.out.println("接收到消息:"+ new String(body));
            }
        };
        channel.basicConsume(Constants.FANOUT_QUEUE2,true,consumer);
    }
}

先运行生产者:

可以看到此时多出了两个队列,并且队列中各有一条消息。

我们可以通过exchange查看队列的绑定关系:

运行消费者:

消费者1:

消费者2:

Routing(路由模式)

此时队列和交换机的绑定就不能是任意的绑定了,而是需要指定一个BindingKey(RoutingKey的一种)

消息在向交换机发送时也需要指定一个RountingKey,交换机也不再把消息交给每一个绑定的Queue,而是根据消息的RountingKey进行判断,只有队列绑定的BindingKey和发送消息的RoutingKey一致时才会收到消息。

常量:

java 复制代码
 //路由模式
    public static final String DIRECT_EXCHANGE = "direct.exchange";
    public static final String DIRECT_QUEUE1 = "direct.queue1";
    public static final String DIRECT_QUEUE2 = "direct.queue2";

生产者代码:

与广播模式相比,这里的交换机类型需要更换,并且在绑定队列和交换机关系的时候需要指定BindingKey。

java 复制代码
public class Producer {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1、建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT);//需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);//密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST);//虚拟主机
        Connection connection = connectionFactory.newConnection();
        //2、开启信道
        Channel channel = connection.createChannel();
        //3、声明交换机
        channel.exchangeDeclare(Constants.DIRECT_EXCHANGE, BuiltinExchangeType.DIRECT,true);
        //4、声明队列
        channel.queueDeclare(Constants.DIRECT_QUEUE1,true,false,false,null);
        channel.queueDeclare(Constants.DIRECT_QUEUE2,true,false,false,null);
        //5、绑定交换机和队列
        channel.queueBind(Constants.DIRECT_QUEUE1,Constants.DIRECT_EXCHANGE,"a");
        channel.queueBind(Constants.DIRECT_QUEUE2,Constants.DIRECT_EXCHANGE,"a");
        channel.queueBind(Constants.DIRECT_QUEUE2,Constants.DIRECT_EXCHANGE,"b");
        channel.queueBind(Constants.DIRECT_QUEUE2,Constants.DIRECT_EXCHANGE,"c");
        //6、发送消息
        String msga = "hello,xmy!my routingkey is a...";
        channel.basicPublish(Constants.DIRECT_EXCHANGE,"a",null,msga.getBytes());
        String msgb = "hello,xmy!my routingkey is b...";
        channel.basicPublish(Constants.DIRECT_EXCHANGE,"b",null,msgb.getBytes());
        String msgc = "hello,xmy!my routingkey is c...";
        channel.basicPublish(Constants.DIRECT_EXCHANGE,"c",null,msgc.getBytes());
        System.out.println("消息发送成功!!!");
        //7、释放资源
        channel.close();
        connection.close();
    }
}

消费者1:

java 复制代码
public class Comsumer1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1、建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT);//需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);//密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST);//虚拟主机
        Connection connection = connectionFactory.newConnection();
        //2、开启信道
        Channel channel = connection.createChannel();
        //3、声明队列
        channel.queueDeclare(Constants.DIRECT_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 {
                //TODO
                System.out.println("接收到消息:"+ new String(body));
            }
        };
        channel.basicConsume(Constants.DIRECT_QUEUE1,true,consumer);
    }
}

消费者2:

java 复制代码
public class Comsumer2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1、建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT);//需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);//密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST);//虚拟主机
        Connection connection = connectionFactory.newConnection();
        //2、开启信道
        Channel channel = connection.createChannel();
        //3、声明队列
        channel.queueDeclare(Constants.DIRECT_QUEUE2,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 {
                //TODO
                System.out.println("接收到消息:"+ new String(body));
            }
        };
        channel.basicConsume(Constants.DIRECT_QUEUE2,true,consumer);
    }
}

运行生产者:

可以看到此时就往queue1中路由了1条消息,往queue2中路由了3条消息:

观察exchange下队列和RoutingKey的绑定关系:

运行消费者:

消费者1:

消费者2:

总结:

Topics(通配符模式)

Topics和Routing模式的区别是:

1、topics模式使用的交换机类型是topic(Routing模式使用的交换机类型为direct)

2、topic类型的交换机在匹配规则上进行扩展,BindingKey支持通配符匹配(direct类型的交换机路由规则是BindingKey和RountingKey完全匹配),而topic模式的匹配规则刚才已经讲过此处也就不再过多赘述。

常量:

java 复制代码
  //通配符模式
    public static final String TOPIC_EXCHANGE = "topic.exchange";
    public static final String TOPIC_QUEUE1 = "topic.queue1";
    public static final String TOPIC_QUEUE2 = "topic.queue2";

生产者:

java 复制代码
public class Producer {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1、建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT);//需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);//密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST);//虚拟主机
        Connection connection = connectionFactory.newConnection();
        //2、开启信道
        Channel channel = connection.createChannel();
        //3、声明交换机
        channel.exchangeDeclare(Constants.TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC,true);
        //4、声明队列
        channel.queueDeclare(Constants.TOPIC_QUEUE1,true,false,false,null);
        channel.queueDeclare(Constants.TOPIC_QUEUE2,true,false,false,null);
        //5、绑定交换机和队列
        channel.queueBind(Constants.TOPIC_QUEUE1,Constants.TOPIC_EXCHANGE,"*.a.*");
        channel.queueBind(Constants.TOPIC_QUEUE2,Constants.TOPIC_EXCHANGE,"*.*.b");
        channel.queueBind(Constants.TOPIC_QUEUE2,Constants.TOPIC_EXCHANGE,"c.#");
        //6、发送消息
        String msga = "hello,xmy!my routingkey is ab.a.c...";
        channel.basicPublish(Constants.TOPIC_EXCHANGE,"ab.a.c",null,msga.getBytes());//转发到queue1
        String msgb = "hello,xmy!my routingkey is ce.a.b...";
        channel.basicPublish(Constants.TOPIC_EXCHANGE,"ce.a.b",null,msgb.getBytes());//转发到queue1和queue2
        String msgc = "hello,xmy!my routingkey is c.ab.c...";
        channel.basicPublish(Constants.TOPIC_EXCHANGE,"c.ab.c",null,msgc.getBytes());//转发到queue2
        System.out.println("消息发送成功!!!");
        //7、释放资源
        channel.close();
        connection.close();
    }
}

消费者1:

java 复制代码
public class Comsumer1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1、建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT);//需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);//密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST);//虚拟主机
        Connection connection = connectionFactory.newConnection();
        //2、开启信道
        Channel channel = connection.createChannel();
        //3、声明队列
        channel.queueDeclare(Constants.TOPIC_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 {
                //TODO
                System.out.println("接收到消息:"+ new String(body));
            }
        };
        channel.basicConsume(Constants.TOPIC_QUEUE1,true,consumer);
    }
}

消费者2:

java 复制代码
public class Comsumer2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1、建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT);//需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);//密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST);//虚拟主机
        Connection connection = connectionFactory.newConnection();
        //2、开启信道
        Channel channel = connection.createChannel();
        //3、声明队列
        channel.queueDeclare(Constants.TOPIC_QUEUE2,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 {
                //TODO
                System.out.println("接收到消息:"+ new String(body));
            }
        };
        channel.basicConsume(Constants.TOPIC_QUEUE2,true,consumer);
    }
}

运行生产者,可以看到队列的消息数:

查看绑定关系:

消费者1:

消费者2:

总结:

RPC通信

声明常量请求队列和回调队列:

java 复制代码
 //rpc模式
    public static final String RPC_REQUEST_QUEUE = "rpc.request.queue";
    public static final String RPC_RESPONSE_QUEUE = "rpc.response.queue";

客户端代码主要流程如下:

1、声明两个队列,包含回调队列replyQueueName,声明本次请求的唯一标识correlation_id。

2、将replyQueueName和correlation_id配置到要发送的消息队列中。

3、使用阻塞队列来阻塞当前进程,监听回调队列中的消息,把请求放到阻塞队列中。

4、阻塞队列有消息后,主线程被唤醒,打印返回内容。

客户端代码:

java 复制代码
public class RpcClient {
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //1、建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT);//需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);//密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST);//虚拟主机
        Connection connection = connectionFactory.newConnection();
        //2、开启信道
        Channel channel = connection.createChannel();
        //3、声明队列
        channel.queueDeclare(Constants.RPC_REQUEST_QUEUE,true,false,false,null);
        channel.queueDeclare(Constants.RPC_RESPONSE_QUEUE,true,false,false,null);
        //4、发送请求
        String msg = "hello,rpc...";
        //设置请求唯一标识      使用UUID进行创建
        String correlationID = UUID.randomUUID().toString();
        //设置请求相关属性
        AMQP.BasicProperties props = new AMQP.BasicProperties().builder()
                .correlationId(correlationID)
                .replyTo(Constants.RPC_RESPONSE_QUEUE)
                .build();
        channel.basicPublish("",Constants.RPC_REQUEST_QUEUE,props,msg.getBytes());
        //4、接收响应
        //使用阻塞队列来存储响应信息
        final BlockingQueue<String> response = new ArrayBlockingQueue<>(1);
        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String respMsg = new String(body);
                System.out.println("接收到回调消息:"+respMsg);
                if(correlationID.equals(properties.getCorrelationId())){
                    //如果correlationID校验一致
                    response.offer(respMsg);
                }
            }
        };
        channel.basicConsume(Constants.RPC_RESPONSE_QUEUE,true,consumer);
        String result = response.take();
        System.out.println("[RPC CLIENT]响应结果:"+result);
    }
}

ps:此处使用阻塞队列是因为客户端发送完消息之后还需要等待服务器响应,如果不使用阻塞队列,那么进程就会立刻结束也就收不到服务器发出的响应了。

服务器代码:

java 复制代码
/**
 * rpc服务器
 * 1、接收请求
 * 2、发送响应
 */
public class RpcServer {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1、建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT);//需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);//密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST);//虚拟主机
        Connection connection = connectionFactory.newConnection();
        //2、开启信道
        Channel channel = connection.createChannel();
        //3、接收请求
        channel.basicQos(1);
        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String request = new String(body,"UTF-8");
                System.out.println("接收到请求:"+request);
                String response = "针对请求request"+request+",响应成功";
                AMQP.BasicProperties basicProperties = new AMQP.BasicProperties().builder()
                                .correlationId(properties.getCorrelationId())
                                        .build();
                channel.basicPublish("",Constants.RPC_RESPONSE_QUEUE,basicProperties,response.getBytes());
                //手动确认
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };
        channel.basicConsume(Constants.RPC_REQUEST_QUEUE,false,consumer);
    }
}

运行客户端:

运行服务器,控制台打印结果:

RpcServer:

RpcClient:

代码对应关系:

Publisher Confirms(发布确认)

作为消息中间件,都会面临消息丢失的问题。

消息丢失大概分为三种情况:

1、生产者问题。因为应用程序故障,网络抖动等各种原因,生产者没有成功向broker发送消息。

2、消息中间件自身的问题。生产者成功发给了Broker,但是Broker没有把消息保存号,导致消息丢失。

3、消费者问题。Broker发送消息到消费者,消费者在消费消息时,因为没有处理好,导致broker将消费失败的消息从队列中删除了。

RabbitMQ也对上述问题给出了相应的解决方案。问题2可以通过持久化机制。问题3可以采用消息应答机制(后面详细讲)。

针对问题1,可以采用发布确认(Publisher Confirms)机制实现。

发布确认属于RabbitMQ的七大工作模式之一。

生产者将信道设置程confirm(确认)模式,一旦信道进入confirm模式,所有在该信道上面发布的消息都会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,RabbitMQ就会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会在将消息写入磁盘后发出。broker回传给生产者的确认消息中。

deliveryTag包含了确认消息的序号,此外broker也可以设置channel.basicAck方法最终的mutiple参数,表示到这个序号之前的所有消息都已经得到了处理。

还可以使用发送方确认机制来进行发布确认,它的最大的好处在于它是异步的,生产者可以同时发布消息和等待信道返回确认消息。

1、当消息最终得到确认之后,生产者可以通过回调方法来处理该确认消息。

2、如果RabbitMQ因为自身内部错误导致消息丢失,就会发送一条nack命令,生产者同样可以在回调方法中处理该nack命令

使用发送确认机制,必须要将信道设置为confirm模式。

封装可复用代码:
java 复制代码
    private static final Integer MESSAGE_COUNT = 200;
    //建立连接
    static Connection createConnection() throws Exception {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT);//需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);//密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST);//虚拟主机
         return connectionFactory.newConnection();
    }
单独确认:
java 复制代码
/**
     * 单独确认
     * @throws Exception
     */
    private static void publishingMessagesIndividually() throws Exception {
        try (Connection connection = createConnection()){
            //1、开启信道
            Channel channel = connection.createChannel();
            //2、设置信道为confirm模式
            channel.confirmSelect();
            //3、声明队列
            channel.queueDeclare(Constants.PUBLISHER_CONFIRMS_QUEUE1,true,false,false,null);
            //4、发送消息并等待确认
            long start = System.currentTimeMillis();
            for (int i = 0; i < MESSAGE_COUNT; i++) {
                String msg = "hello publisher confirms"+i;
                channel.basicPublish("",Constants.PUBLISHER_CONFIRMS_QUEUE1,null,msg.getBytes());
                //等待确认
                channel.waitForConfirms(5000);
            }
            long end = System.currentTimeMillis();
            System.out.printf("单独确认策略,消息条数:%d,耗时:%d ms\n",MESSAGE_COUNT,end-start);
        }
    }
批量确认:
java 复制代码
/**
     * 批量确认
     */
    private static void publishingMessagesInBatches() throws Exception {
        try (Connection connection = createConnection()){
            //1、开启信道
            Channel channel = connection.createChannel();
            //2、设置信道为confirm模式
            channel.confirmSelect();
            //3、声明队列
            channel.queueDeclare(Constants.PUBLISHER_CONFIRMS_QUEUE2,true,false,false,null);
            //4、发送消息并等待确认
            int batchSize = 100;
            int outstandingMessageCount = 0;
            long start = System.currentTimeMillis();
            for (int i = 0; i < MESSAGE_COUNT; i++) {
                String msg = "hello publisher confirms"+i;
                channel.basicPublish("",Constants.PUBLISHER_CONFIRMS_QUEUE2,null,msg.getBytes());
                outstandingMessageCount++;
                if(outstandingMessageCount >= batchSize){
                    channel.waitForConfirmsOrDie(5000);
                    outstandingMessageCount = 0;
                }
            }
            if(outstandingMessageCount > 0){
                channel.waitForConfirms(5000);
            }
            long end = System.currentTimeMillis();
            System.out.printf("批量确认策略,消息条数:%d,耗时:%d ms\n",MESSAGE_COUNT,end-start);
        }
    }
异步确认
java 复制代码
/**
     * 异步确认
     */
    private static void handlingPublisherConfirmsAsynchronously() throws Exception {
        try(Connection connection = createConnection()){
            //1、开启信道
            Channel channel = connection.createChannel();
            //2、设置信道为confirm模式
            channel.confirmSelect();
            //3、声明队列
            channel.queueDeclare(Constants.PUBLISHER_CONFIRMS_QUEUE3,true,false,false,null);
            long start = System.currentTimeMillis();

            //4、监听confirm
            //集合中存储的是未确认的消息Id
            SortedSet<Long> confirmSeqNo = Collections.synchronizedSortedSet(new TreeSet<>());
            channel.addConfirmListener(new ConfirmListener() {
                @Override
                public void handleAck(long l, boolean b) throws IOException {
                if(b){
                    confirmSeqNo.headSet(l+1).clear();
                    }else{
                    confirmSeqNo.remove(l);
                    }
                }

                @Override
                public void handleNack(long l, boolean b) throws IOException {
                    if(b){
                        confirmSeqNo.headSet(l+1).clear();
                    }else{
                        confirmSeqNo.remove(l);
                    }
                    //需要根据实际场景进行处理,比如重发消息,此处代码省略
                }
            });
            //5、发送消息
            for (int i = 0; i < MESSAGE_COUNT; i++) {
                String msg = "hello publisher confirms"+i;
                long seqNo = channel.getNextPublishSeqNo();
                channel.basicPublish("",Constants.PUBLISHER_CONFIRMS_QUEUE3,null,msg.getBytes());
                confirmSeqNo.add(seqNo);
            }
            while(!confirmSeqNo.isEmpty()){
                Thread.sleep(10);
            }
            long end = System.currentTimeMillis();
            System.out.printf("异步确认策略,消息条数:%d,耗时:%d ms\n",MESSAGE_COUNT,end-start);
        }
    }
main函数:
java 复制代码
    public static void main(String[] args) throws Exception {
        //Strategy #1: Publishing Messages Individually
        //单独确认
        publishingMessagesIndividually();
        //Strategy #2: Publishing Messages in Batches
        //批量确认
        publishingMessagesInBatches();
        //Strategy #3: Handling Publisher Confirms Asynchronously
        //异步确认
        handlingPublisherConfirmsAsynchronously();
    }

可以看到,单独确认策略耗时最长,而批量确认和异步确认时间上差不多。

完整代码:
java 复制代码
public class PublisherConfirms {

    private static final Integer MESSAGE_COUNT = 200;
    //建立连接
    static Connection createConnection() throws Exception {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT);//需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);//密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST);//虚拟主机
         return connectionFactory.newConnection();
    }
    public static void main(String[] args) throws Exception {
        //Strategy #1: Publishing Messages Individually
        //单独确认
        publishingMessagesIndividually();
        //Strategy #2: Publishing Messages in Batches
        //批量确认
        publishingMessagesInBatches();
        //Strategy #3: Handling Publisher Confirms Asynchronously
        //异步确认
        handlingPublisherConfirmsAsynchronously();
    }

    /**
     * 异步确认
     */
    private static void handlingPublisherConfirmsAsynchronously() throws Exception {
        try(Connection connection = createConnection()){
            //1、开启信道
            Channel channel = connection.createChannel();
            //2、设置信道为confirm模式
            channel.confirmSelect();
            //3、声明队列
            channel.queueDeclare(Constants.PUBLISHER_CONFIRMS_QUEUE3,true,false,false,null);
            long start = System.currentTimeMillis();

            //4、监听confirm
            //集合中存储的是未确认的消息Id
            SortedSet<Long> confirmSeqNo = Collections.synchronizedSortedSet(new TreeSet<>());
            channel.addConfirmListener(new ConfirmListener() {
                @Override
                public void handleAck(long l, boolean b) throws IOException {
                if(b){
                    confirmSeqNo.headSet(l+1).clear();
                    }else{
                    confirmSeqNo.remove(l);
                    }
                }

                @Override
                public void handleNack(long l, boolean b) throws IOException {
                    if(b){
                        confirmSeqNo.headSet(l+1).clear();
                    }else{
                        confirmSeqNo.remove(l);
                    }
                    //需要根据实际场景进行处理,比如重发消息,此处代码省略
                }
            });
            //5、发送消息
            for (int i = 0; i < MESSAGE_COUNT; i++) {
                String msg = "hello publisher confirms"+i;
                long seqNo = channel.getNextPublishSeqNo();
                channel.basicPublish("",Constants.PUBLISHER_CONFIRMS_QUEUE3,null,msg.getBytes());
                confirmSeqNo.add(seqNo);
            }
            while(!confirmSeqNo.isEmpty()){
                Thread.sleep(10);
            }
            long end = System.currentTimeMillis();
            System.out.printf("异步确认策略,消息条数:%d,耗时:%d ms\n",MESSAGE_COUNT,end-start);
        }
    }

    /**
     * 批量确认
     */
    private static void publishingMessagesInBatches() throws Exception {
        try (Connection connection = createConnection()){
            //1、开启信道
            Channel channel = connection.createChannel();
            //2、设置信道为confirm模式
            channel.confirmSelect();
            //3、声明队列
            channel.queueDeclare(Constants.PUBLISHER_CONFIRMS_QUEUE2,true,false,false,null);
            //4、发送消息并等待确认
            int batchSize = 100;
            int outstandingMessageCount = 0;
            long start = System.currentTimeMillis();
            for (int i = 0; i < MESSAGE_COUNT; i++) {
                String msg = "hello publisher confirms"+i;
                channel.basicPublish("",Constants.PUBLISHER_CONFIRMS_QUEUE2,null,msg.getBytes());
                outstandingMessageCount++;
                if(outstandingMessageCount >= batchSize){
                    channel.waitForConfirmsOrDie(5000);
                    outstandingMessageCount = 0;
                }
            }
            if(outstandingMessageCount > 0){
                channel.waitForConfirms(5000);
            }
            long end = System.currentTimeMillis();
            System.out.printf("批量确认策略,消息条数:%d,耗时:%d ms\n",MESSAGE_COUNT,end-start);
        }
    }

    /**
     * 单独确认
     * @throws Exception
     */
    private static void publishingMessagesIndividually() throws Exception {
        try (Connection connection = createConnection()){
            //1、开启信道
            Channel channel = connection.createChannel();
            //2、设置信道为confirm模式
            channel.confirmSelect();
            //3、声明队列
            channel.queueDeclare(Constants.PUBLISHER_CONFIRMS_QUEUE1,true,false,false,null);
            //4、发送消息并等待确认
            long start = System.currentTimeMillis();
            for (int i = 0; i < MESSAGE_COUNT; i++) {
                String msg = "hello publisher confirms"+i;
                channel.basicPublish("",Constants.PUBLISHER_CONFIRMS_QUEUE1,null,msg.getBytes());
                //等待确认
                channel.waitForConfirms(5000);
            }
            long end = System.currentTimeMillis();
            System.out.printf("单独确认策略,消息条数:%d,耗时:%d ms\n",MESSAGE_COUNT,end-start);
        }
    }
}

现在我们将消息改为20000条再次观察(ps:把单独确认注释掉,对比批量确认和异步确认即可。)

可以看到异步确认此时明显比批量确认更快的!!!

SpringBoot整合RabbitMQ

Spring官方文档:https://spring.io/projects/spring-amqp/

RabbitMQ官方文档:RabbitMQ tutorial - "Hello World!" | RabbitMQ

PS:此处我们只粘贴工作模式、广播模式、路由模式、通配符模式的代码。为了使实验结果更加明显,我们可以在管理页面中把刚才的交换机和队列删除或者切换到另一台虚拟机上进行操作!!!

引入依赖

XML 复制代码
<!--Spring MVC相关依赖-->
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-test</artifactId>
 <scope>test</scope>
</dependency>
<!--RabbitMQ相关依赖-->
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
1

工作队列模式

常量:

java 复制代码
 public static final String WORK_QUEUE = "work.queue";

声明队列(在RabbitMQConfig中进行声明):

java 复制代码
//工作模式
    @Bean("workQueue")
    public Queue workQueue(){
            return QueueBuilder.durable(Constants.WORK_QUEUE).build();
    }

Controller:(生产者,访问当前URL就会创建队列,如果需要创建交换机,则创建交换机,发送消息)

java 复制代码
@Autowired
    private RabbitTemplate rabbitTemplate;

    @RequestMapping("/work")
    public String work(){
        //使用内置交换机,RountingKey和队列名称一致即可
        for (int i = 0; i < 10; i++) {
            rabbitTemplate.convertAndSend("", Constants.WORK_QUEUE,"hello spring amqp:work..."+i);
        }
        return "发送成功";
    }

Listener:(消费者,在spring启动的时候就会自动进行消费)

java 复制代码
@Component
public class WorkListener {
    @RabbitListener(queues = Constants.WORK_QUEUE)
    public void queueListener1(Message message, Channel channel){
        System.out.println("listener1:["+Constants.WORK_QUEUE+"],接收到消息:"+message+"channel:"+channel);
    }

    @RabbitListener(queues = Constants.WORK_QUEUE)
    public void queueListener2(String message){
        System.out.println("listener2:["+Constants.WORK_QUEUE+"],接收到消息:"+message);
    }
}

ps:@RabbitListener是Spring框架中用于监听RabbitMQ队列的注解,通过使用这个注解,可以定义一个方法,以便从RabbitMQ队列中接收消息。该注解支持多种参数类型,这些参数类型代表了从RabbitMQ接收到的消息和相关消息。

一下是一些常用的参数类型:

1、String:返回消息的内容

2、Message:Spring AMQP的Message类,返回原始的消息体以及消息的属性,如消息ID,内容,队列信息等。

3、Channel:RabbitMQ的通道对象,可以用于进行更高级的操作,如手动确认消息

Publish/Subscribe

常量:

java 复制代码
 //发布订阅模式
    public static final String FANOUT_QUEUE1 = "fanout.queue1";
    public static final String FANOUT_QUEUE2 = "fanout.queue2";
    public static final String FANOUT_EXCHANGE = "fanout.exchange";

声明队列和交换机:

java 复制代码
//发布订阅模式
    //1、声明queue
    @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、声明交换机
    @Bean("fanoutExchange")
    public FanoutExchange fanoutExchange(){
        return ExchangeBuilder.fanoutExchange(Constants.FANOUT_EXCHANGE).durable(true).build();
    }

声明绑定关系:

java 复制代码
//3、声明绑定关系
    @Bean("fanoutQueueBinding1")
    public Binding fanoutQueueBinding1(@Qualifier("fanoutExchange") FanoutExchange fanoutExchange,@Qualifier("fanoutQueue1") Queue queue){
        return BindingBuilder.bind(queue).to(fanoutExchange);
    }

    @Bean("fanoutQueueBinding2")
    public Binding fanoutQueueBinding2(@Qualifier("fanoutExchange") FanoutExchange fanoutExchange,@Qualifier("fanoutQueue2") Queue queue){
        return BindingBuilder.bind(queue).to(fanoutExchange);
    }

controller:

java 复制代码
    @RequestMapping("/fanout")
    public String fanout(){
        rabbitTemplate.convertAndSend(Constants.FANOUT_EXCHANGE,"","hello spring amqp:fanout...");
        return "发送成功";
    }

listener:

java 复制代码
@Component
public class FanoutListener {
    @RabbitListener(queues = Constants.FANOUT_QUEUE1)
    public void queueListener1(String message){
        System.out.println("listener1:["+Constants.FANOUT_QUEUE1+"],接收到消息:"+message);
    }

    @RabbitListener(queues = Constants.FANOUT_QUEUE2)
    public void queueListener2(String message){
        System.out.println("listener2:["+Constants.FANOUT_QUEUE2+"],接收到消息:"+message);
    }
}

Rounting

常量:

java 复制代码
 //路由模式
    public static final String DIRECT_QUEUE1 = "direct.queue1";
    public static final String DIRECT_QUEUE2 = "direct.queue2";
    public static final String DIRECT_EXCHANGE = "direct.exchange";

声明队列和交换机:

java 复制代码
//路由模式
    //1、声明queue
    @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();
    }

声明绑定关系:

java 复制代码
 //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("orange");
    }
    @Bean("directQueueBinding3")
    public Binding directQueueBinding3(@Qualifier("directExchange") DirectExchange directExchange,@Qualifier("directQueue2") Queue queue){
        return BindingBuilder.bind(queue).to(directExchange).with("black");
    }

controller:

java 复制代码
@RequestMapping("/direct/{routingKey}")
    public String direct(@PathVariable("routingKey") String routingKey){
        rabbitTemplate.convertAndSend(Constants.DIRECT_EXCHANGE,routingKey,"hello spring amqp:direct...,routingKey:"+routingKey);
        return "发送成功";
    }

listener:

java 复制代码
@Component
public class DirectListener {
    @RabbitListener(queues = Constants.DIRECT_QUEUE1)
    public void queueListener1(String message){
        System.out.println("listener1:["+Constants.DIRECT_QUEUE1+"],接收到消息:"+message);
    }

    @RabbitListener(queues = Constants.DIRECT_QUEUE2)
    public void queueListener2(String message){
        System.out.println("listener2:["+Constants.DIRECT_QUEUE2+"],接收到消息:"+message);
    }
}

Topics

常量:

java 复制代码
  //通配符模式
    public static final String TOPIC_QUEUE1 = "topic.queue1";
    public static final String TOPIC_QUEUE2 = "topic.queue2";
    public static final String TOPIC_EXCHANGE = "topic.exchange";

声明队列和交换机:

java 复制代码
//通配符模式
    //1、声明queue
    @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();
    }

声明绑定关系:

java 复制代码
    //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.#");
    }

controller:

java 复制代码
  @RequestMapping("/topic/{routingKey}")
    public String topic(@PathVariable("routingKey") String routingKey){
        rabbitTemplate.convertAndSend(Constants.TOPIC_EXCHANGE,routingKey,"hello spring amqp:topic...,routingKey:"+routingKey);
        return "发送成功";
    }

listener:

java 复制代码
@Component
public class TopicListener {
    @RabbitListener(queues = Constants.TOPIC_QUEUE1)
    public void queueListener1(String message){
        System.out.println("listener1:["+Constants.TOPIC_QUEUE1+"],接收到消息:"+message);
    }

    @RabbitListener(queues = Constants.TOPIC_QUEUE2)
    public void queueListener2(String message){
        System.out.println("listener2:["+Constants.TOPIC_QUEUE2+"],接收到消息:"+message);
    }
}

常见问题

队列绑定交换机的类型不匹配
声明的交换机类型不匹配
不删除队列然后直接修改队列为不持久化
没有为Bean指定名称

基于SpringBoot+RabbitMQ完成应用通信

作为一个消息队列,RabbitMQ也可以用作应用程序之间的通信。上述代码生产者和消费者代码放在不同的应用中即可完成不同应用程序的通信。

需求描述:

此处我们将生产者和消费者分在不同的模块中,代表不同的机器:

生产者代码(订单系统):

完善配置:

bash 复制代码
spring.application.name=order-service
spring.rabbitmq.addresses=amqp://admin:admin@主机ip:5672/order

声明队列:

java 复制代码
 @Bean("orderQueue")
    public Queue orderQueue(){
        return QueueBuilder.durable("order.create").build();
    }

controller:

java 复制代码
@Autowired
    private RabbitTemplate rabbitTemplate;
    @RequestMapping("/create")
    public String create(){
        //参数校验,落库等,业务代码省略
        //发送消息
        String uuid = UUID.randomUUID().toString();
        rabbitTemplate.convertAndSend("","order.create","订单信息,订单ID:"+uuid);
        return "下单成功";
    }

消费者代码(物流系统):

声明队列:

java 复制代码
@Bean("orderQueue")
    public Queue orderQueue(){
        return QueueBuilder.durable("order.create").build();
    }

listener:

java 复制代码
@Component
@RabbitListener(queues = "order.create")
public class OrderListener {
    //@RabbitHandler这个注解会根据不同的类型调用不同的消费者

    @RabbitHandler
    public void handMessage(String orderInfo){
        System.out.println("接收到消息"+orderInfo);
        //接收到消息之后,相应的业务处理
        //业务代码省略
    }
}

ps:这里的RabbitListener一般都是加在类上的,只是我们之前为了代码方便没有这么写。使用@RabbitHandler就可以针对不同类型的消息进行消费了~~

发送消息为对象

yml配置文件:

java 复制代码
spring.application.name=logistics-service
server.port=8081
spring.rabbitmq.addresses=amqp://admin:admin@主机IP/order

这里的端口要设置为不同的,代表两台机器。

model类:

java 复制代码
@Data
public class OrderInfo implements Serializable {
    private String orderId;
    private String name;
}

ps:这里需要对对象进行一个序列化,不然发送的消息会是一堆乱码。

这里更推荐使用JSON进行序列化,SpringAMQP提供了Jackson2JsonMessageConverter转换器,我们可以在RabbitTemplate中进行配置(生产者和消费者都需要进行配置):

java 复制代码
@Configuration
public class RabbitMQConfig {
    @Bean("orderQueue")
    public Queue orderQueue(){
        return QueueBuilder.durable("order.create").build();
    }

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

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

生产者:

java 复制代码
 @RequestMapping("/create2")
    public String create2(){
        //参数校验,落库等,业务代码省略
        //发送消息
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setOrderId(UUID.randomUUID().toString());
        orderInfo.setName("商品"+new Random().nextInt(0,99));
        rabbitTemplate.convertAndSend("","order.create","订单信息:"+orderInfo);
        return "下单成功";
    }

消费者:

java 复制代码
 @RabbitHandler
    public void handMessage(OrderInfo orderInfo){
        System.out.println("接收到消息对象"+orderInfo);
        //接收到消息之后,相应的业务处理
        //业务代码省略
    }

此时,我们的系统就可以根据String和对象,自动切换到对应消费的方法了。

相关推荐
gugugu.1 小时前
从单机到微服务:分布式架构演进全景解析
分布式·微服务·架构
JSON_L1 小时前
Fastadmin中通过RabbitMq实现即时和延迟队列
rabbitmq·php·fastadmin
小股虫1 小时前
Kafka深度解析:从日志处理到流处理的“数据管道扛把子
分布式·kafka
白露与泡影1 小时前
Kafka:消费者重试与死信队列的对应模式分析
分布式·kafka·linq
勇敢打工人3 小时前
rabbitmq数据恢复
分布式·rabbitmq
ZStack开发者社区9 小时前
替代VMware vSAN | 五大角度解析ZStack分布式存储替代优势
分布式·云计算
R-sz10 小时前
使用Redisson实现同一业务类型串行执行的分布式锁方案,解决并发问题
分布式
哈哈哈笑什么14 小时前
蜜雪冰城1分钱奶茶秒杀活动下,使用分片锁替代分布式锁去做秒杀系统
redis·分布式·后端
哈哈哈笑什么15 小时前
高并发分布式Springcloud系统下,使用RabbitMQ实现订单支付完整闭环的实现方案(反向撤销+重试+补偿)
分布式·spring cloud·rabbitmq