• RabbitMQ消息发送与接收


    1.简介

      所有MQ产品从模型抽象上来说都是一样的过程。消费者订阅某个队列。生产者创建消息,然后发布到队列,最后将消息发送到监听的消费者。

       AMQP(Advanced message queuing protocol)是一个提供统一消息服务的应用层标准协议,基于此协议的客户端与消息中间件可传递消息,并不受客户端、中间件等不同产品,不同开发语言等条件的限制。

      ActiveMQ是基于JMS(Java Message Service)协议的消息中间件。区别如下:

     Rabbit模型如下:

     1.Message。消息,是不具体的。由消息头和消息体组成。消息体是不透明的,而消息头是一系列可选属性组成,这些属性包括routing-key(路由键)、priority(优先级)、delivery-mode(是否持久存储)等

    2.Publisher。消息的生产者,也是一个向交换机发布消息的客户端应用程序。

    3.Exchanger。交换机,用来接收生产者发布的消息并将这些消息路由给服务器中的队列。

    4.Binging。绑定,用于消息队列和交换器之间的管理。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则。所以可以将交换器理解成一个由绑定构成的路由表。

    5.Queue。消息队列,用来保存消息知道发送给消费者。一个消息可投入一个或对个队列。

    6.Connection。网络连接,比如一个TCP连接。

    7.Channel。信道,多路复用连接中的一条独立的双向数据流通道,可读可写。一个Connection包括多个channel。因为对于操作系统来说建立和销毁TCP是非常昂贵的开销,所以引入信道的概念,以复用一条TCP连接。

    8.Consumer。消费者,从消息队列取得消息的客户端应用程序。

    9.VirtualHost。虚拟主机。表示一批交换机、消息队列和相关对象。vhost本质上是一个mini版的RabbitMQ服务器,拥有自己的队列、绑定、交换器和权限控制;vhost通过在各个实例间提供逻辑上分离,允许你为不同应用程序安全保密地运行数据;vhost是AMQP概念的基础,必须在连接时进行指定,RabbitMQ包含了默认vhost:“/”。

    10.Borker。表示消息队列服务器实体。表示启动一个rabbitmq所包含的进程。

    2.使用

    1.简单队列模式

    不涉及交换机的模型如下:

    pom文件引入如下依赖:

            <!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->
            <dependency>
                <groupId>com.rabbitmq</groupId>
                <artifactId>amqp-client</artifactId>
                <version>5.4.3</version>
            </dependency>

    1.消息生产者 

    package rabbitmq;
    
    import java.io.IOException;
    import java.util.concurrent.TimeoutException;
    
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    import com.rabbitmq.client.ConnectionFactory;
    
    public class Producer {
    
        public static ConnectionFactory getConnectionFactory() {
            // 创建连接工程,下面给出的是默认的case
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("192.168.99.100");
            factory.setPort(5672);
            factory.setUsername("guest");
            factory.setPassword("guest");
            factory.setVirtualHost("/");
            return factory;
        }
    
        public static void main(String[] args) throws IOException, TimeoutException  {
            ConnectionFactory connectionFactory = getConnectionFactory();
            Connection newConnection = null;
            Channel createChannel = null;
            try {
                newConnection = connectionFactory.newConnection();
                createChannel = newConnection.createChannel();
                /**
                 * 声明一个队列。
                 * 参数一:队列名称
                 * 参数二:是否持久化
                 * 参数三:是否排外  如果排外则这个队列只允许有一个消费者
                 * 参数四:是否自动删除队列,如果为true表示没有消息也没有消费者连接自动删除队列
                 * 参数五:队列的附加属性
                 * 注意:
                 * 1.声明队列时,如果已经存在则放弃声明,如果不存在则会声明一个新队列;
                 * 2.队列名可以任意取值,但需要与消息接收者一致。
                 * 3.下面的代码可有可无,一定在发送消息前确认队列名称已经存在RabbitMQ中,否则消息会发送失败。
                 */
                createChannel.queueDeclare("myQueue", true, false, false,null);
                
                String message = "测试消息";
                /**
                 * 发送消息到MQ
                 * 参数一:交换机名称,为""表示不用交换机
                 * 参数二:为队列名称或者routingKey.当指定了交换机就是routingKey
                 * 参数三:消息的属性信息
                 * 参数四:消息内容的字节数组
                 */
                createChannel.basicPublish("", "myQueue", null, message.getBytes());
                
                System.out.println("消息发送成功");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (createChannel != null) {
                    createChannel.close();
                }
                if (newConnection != null) {
                    newConnection.close();
                }
            }
            
        }
    }

      注意:5672是rabbitmq暴露的端口,15672是management插件的端口。

    发送成功之后可以从15672端口查看,也可以从15672进行消费,如下:

     2.消息接收

    package rabbitmq;
    
    import java.io.IOException;
    import java.util.concurrent.TimeoutException;
    
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    import com.rabbitmq.client.ConnectionFactory;
    import com.rabbitmq.client.DefaultConsumer;
    import com.rabbitmq.client.Envelope;
    import com.rabbitmq.client.AMQP.BasicProperties;
    
    public class Consumer {
    
        public static ConnectionFactory getConnectionFactory() {
            // 创建连接工程,下面给出的是默认的case
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("192.168.99.100");
            factory.setPort(5672);
            factory.setUsername("guest");
            factory.setPassword("guest");
            factory.setVirtualHost("/");
            return factory;
        }
    
        public static void main(String[] args) throws IOException, TimeoutException  {
            ConnectionFactory connectionFactory = getConnectionFactory();
            Connection newConnection = null;
            Channel createChannel = null;
            try {
                newConnection = connectionFactory.newConnection();
                createChannel = newConnection.createChannel();
                /**
                 * 声明一个队列。
                 * 参数一:队列名称
                 * 参数二:是否持久化
                 * 参数三:是否排外  如果排外则这个队列只允许有一个消费者
                 * 参数四:是否自动删除队列,如果为true表示没有消息也没有消费者连接自动删除队列
                 * 参数五:队列的附加属性
                 * 注意:
                 * 1.声明队列时,如果已经存在则放弃声明,如果不存在则会声明一个新队列;
                 * 2.队列名可以任意取值,但需要与消息接收者一致。
                 * 3.下面的代码可有可无,一定在发送消息前确认队列名称已经存在RabbitMQ中,否则消息会发送失败。
                 */
                createChannel.queueDeclare("myQueue", true, false, false,null);
                /**
                 * 接收消息。会持续坚挺,不能关闭channel和Connection
                 * 参数一:队列名称
                 * 参数二:消息是否自动确认,true表示自动确认接收完消息以后会自动将消息从队列移除。否则需要手动ack消息
                 * 参数三:消息接收者的标签,用于多个消费者同时监听一个队列时用于确认不同消费者。
                 * 参数四:消息接收者
                 */
                createChannel.basicConsume("myQueue", true, "", new DefaultConsumer(createChannel) {
                    @Override
                    public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
                            byte[] body) throws IOException {
                        String string = new String(body, "UTF-8");
                        System.out.println("接收到d消息: -》 " + string);
                    }
                });
                
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
            }
            
        }
    }

      注意:消息的确认模式可以为自动也可以为手动,自动确认读取完会自动从队列删除;手动需要自己ack,如果设为手动也没ack可能会造成消息重复消费。

      如果是多个消费者,会从队列以轮询的方式处理消息,这种称为工作队列模式。

    补充:这种实际也是用了rabbitmq的一个默认交换机,routing_key为队列名称。也可以理解为是Rabbitmq类型为System的交换机。

    测试:修改消费者代码

                createChannel.basicConsume("myQueue", true, "", new DefaultConsumer(createChannel) {
                    @Override
                    public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
                            byte[] body) throws IOException {
                        String string = new String(body, "UTF-8");
                        System.out.println(envelope);
                        System.out.println(properties);
                        System.out.println("接收到d消息: -》 " + string);
                    }
                });

    结果:(可以看出是有路由key的,值为队列名称)

    Envelope(deliveryTag=1, redeliver=false, exchange=, routingKey=myQueue)
    #contentHeader<basic>(content-type=null, content-encoding=null, headers=null, delivery-mode=null, priority=null, correlation-id=null, reply-to=null, expiration=null, message-id=null, timestamp=null, type=null, user-id=null, app-id=null, cluster-id=null)
    接收到d消息: -》 测试消息

    2.涉及交换机的发送和接收

      Exchange类型根据分发策略分为四种。direct、fanout、topic、headers。headers匹配AMQP消息的header而不是路由键,此外headers交换机和direct交换机完全一致,目前几乎不用。Exchange只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,消息会丢失。所以只能收到监听之后生产者发送的消息。

    抽取工具类:

    package rabbitmq;
    
    import com.rabbitmq.client.Connection;
    import com.rabbitmq.client.ConnectionFactory;
    
    public class ConnectionUtils {
        
        public static Connection getConnection() throws Exception {
            // 创建连接工程,下面给出的是默认的case
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("192.168.99.100");
            factory.setPort(5672);
            factory.setUsername("guest");
            factory.setPassword("guest");
            factory.setVirtualHost("/");
            return factory.newConnection();
        }
    
    }

    1.Direct类型交换-单播模式,也成为路由模式(Routing模式)

      精准绑定,消息中的路由键(RoutingKey)和Binding的bindingKey一致。

    生产者:

    package rabbitmq;
    
    import com.rabbitmq.client.BuiltinExchangeType;
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    
    public class DirectProducer {
    
        public static void main(String[] args) throws Exception{
            Connection connection = ConnectionUtils.getConnection();
            //8、创建频道-channel = connection.createChannel()
            Channel channel = connection.createChannel();
    
            //声明交换机- channel.exchangeDeclare(交换机名字,交换机类型)
            channel.exchangeDeclare("routing_exchange", BuiltinExchangeType.DIRECT);
            //连续发3条消息
            for (int i = 0; i < 3; i++) {
                String routingKey = "";
                //发送消息的时候根据相关逻辑指定相应的routing key。
                switch (i){
                    case 0:  //假设i=0,为error消息
                        routingKey = "log.error";
                        break;
                    case 1: //假设i=1,为info消息
                        routingKey = "log.info";
                        break;
                    case 2: //假设i=2,为warning消息
                        routingKey = "log.warning";
                        break;
                }
                //10、创建消息-String m = xxx
                String message = "hello,message!" + i;
                //11、消息发送-channel.basicPublish(交换机[默认Default Exchage],路由key[简单模式可以传递队列名称],消息其它属性,消息内容)
                channel.basicPublish("routing_exchange",routingKey,null,message.getBytes("utf-8"));
            }
            //12、关闭资源-channel.close();connection.close()
            channel.close();
            connection.close();
        }
    }

    消费者一:

    package rabbitmq;
    
    import java.io.IOException;
    
    import com.rabbitmq.client.AMQP;
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    import com.rabbitmq.client.Consumer;
    import com.rabbitmq.client.DefaultConsumer;
    import com.rabbitmq.client.Envelope;
    
    public class DirectConsumerOne {
    
        public static void main(String[] args) throws Exception{
            Connection connection = ConnectionUtils.getConnection();
            //8、创建频道-channel = connection.createChannel()
            Channel channel = connection.createChannel();
            //9、声明队列-channel.queueDeclare(名称,是否持久化,是否独占本连接,是否自动删除,附加参数)
            channel.queueDeclare("routing_queue1",true,false,false,null);
    
            //队列绑定交换机-channel.queueBind(队列名, 交换机名, 路由key[广播消息设置为空串])
            channel.queueBind("routing_queue1", "routing_exchange", "log.error");
            //创建消费者
            Consumer callback = new DefaultConsumer(channel){
                /**
                 * @param consumerTag 消费者标签,在channel.basicConsume时候可以指定
                 * @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送)
                 * @param properties  属性信息(生产者的发送时指定)
                 * @param body 消息内容
                 * @throws IOException
                 */
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    //路由的key
                    String routingKey = envelope.getRoutingKey();
                    //获取交换机信息
                    String exchange = envelope.getExchange();
                    //获取消息ID
                    long deliveryTag = envelope.getDeliveryTag();
                    //获取消息信息
                    String message = new String(body,"utf-8");
                    System.out.println(
                            "routingKey:" + routingKey +
                            ",exchange:" + exchange +
                            ",deliveryTag:" + deliveryTag +
                            ",message:" + message);
                }
            };
            /**
             * 消息消费
             * 参数1:队列名称
             * 参数2:是否自动应答,true为自动应答[mq接收到回复会删除消息],设置为false则需要手动应答
             * 参数3:消息接收到后回调
             */
            channel.basicConsume("routing_queue1",true,callback);
    
            //注意,此处不建议关闭资源,让程序一直处于读取消息
        }
    }

    消费者二:

    package rabbitmq;
    
    import java.io.IOException;
    
    import com.rabbitmq.client.AMQP;
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    import com.rabbitmq.client.Consumer;
    import com.rabbitmq.client.DefaultConsumer;
    import com.rabbitmq.client.Envelope;
    
    public class DirectConsumerTwo {
    
        public static void main(String[] args) throws Exception{
            Connection connection = ConnectionUtils.getConnection();
            //8、创建频道-channel = connection.createChannel()
            Channel channel = connection.createChannel();
            //9、声明队列-channel.queueDeclare(名称,是否持久化,是否独占本连接,是否自动删除,附加参数)
            channel.queueDeclare("routing_queue2",true,false,false,null);
    
            //队列绑定交换机-channel.queueBind(队列名, 交换机名, 路由key[广播消息设置为空串])
            channel.queueBind("routing_queue2", "routing_exchange", "log.error");
            //队列绑定交换机-channel.queueBind(队列名, 交换机名, 路由key[广播消息设置为空串])
            channel.queueBind("routing_queue2", "routing_exchange", "log.info");
            //队列绑定交换机-channel.queueBind(队列名, 交换机名, 路由key[广播消息设置为空串])
            channel.queueBind("routing_queue2", "routing_exchange", "log.warning");
            //创建消费者
            Consumer callback = new DefaultConsumer(channel){
                /**
                 * @param consumerTag 消费者标签,在channel.basicConsume时候可以指定
                 * @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送)
                 * @param properties  属性信息(生产者的发送时指定)
                 * @param body 消息内容
                 * @throws IOException
                 */
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    //路由的key
                    String routingKey = envelope.getRoutingKey();
                    //获取交换机信息
                    String exchange = envelope.getExchange();
                    //获取消息ID
                    long deliveryTag = envelope.getDeliveryTag();
                    //获取消息信息
                    String message = new String(body,"utf-8");
                    System.out.println(
                            "routingKey:" + routingKey +
                            ",exchange:" + exchange +
                            ",deliveryTag:" + deliveryTag +
                            ",message:" + message);
                }
            };
            /**
             * 消息消费
             * 参数1:队列名称
             * 参数2:是否自动应答,true为自动应答[mq接收到回复会删除消息],设置为false则需要手动应答
             * 参数3:消息接收到后回调
             */
            channel.basicConsume("routing_queue2",true,callback);
    
            //注意,此处不建议关闭资源,让程序一直处于读取消息
        }
    }

    结果:

    (1)消费者一

    (2)消费者二

    2.fanout多播模式,也称为Publish/Scribe模式

      每个发到fanout类型交换器的消息会被分发到所有的队列中。fanout不处理路由键,发消息最快。

    两个消费者:

    消费者1:

    package rabbitmq;
    
    import java.io.IOException;
    
    import com.rabbitmq.client.AMQP;
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    import com.rabbitmq.client.Consumer;
    import com.rabbitmq.client.DefaultConsumer;
    import com.rabbitmq.client.Envelope;
    
    public class FanoutConsumerOne {
    
         public static void main(String[] args) throws Exception{
                Connection connection = ConnectionUtils.getConnection();
                //8、创建频道-channel = connection.createChannel()
                Channel channel = connection.createChannel();
                //9、声明队列-channel.queueDeclare(名称,是否持久化,是否独占本连接,是否自动删除,附加参数)
                channel.queueDeclare("fanout_queue1",true,false,false,null);
                //队列绑定交换机-channel.queueBind(队列名, 交换机名, 路由key[广播消息设置为空串])
                channel.queueBind("fanout_queue1", "fanout_exchange", "");
    
                //创建消费者
                Consumer callback = new DefaultConsumer(channel){
                    /**
                     * @param consumerTag 消费者标签,在channel.basicConsume时候可以指定
                     * @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送)
                     * @param properties  属性信息(生产者的发送时指定)
                     * @param body 消息内容
                     * @throws IOException
                     */
                    @Override
                    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                        //路由的key
                        String routingKey = envelope.getRoutingKey();
                        //获取交换机信息
                        String exchange = envelope.getExchange();
                        //获取消息ID
                        long deliveryTag = envelope.getDeliveryTag();
                        //获取消息信息
                        String message = new String(body,"utf-8");
                        System.out.println(
                                "routingKey:" + routingKey +
                                ",exchange:" + exchange +
                                ",deliveryTag:" + deliveryTag +
                                ",message:" + message);
                    }
                };
                /**
                 * 消息消费
                 * 参数1:队列名称
                 * 参数2:是否自动应答,true为自动应答[mq接收到回复会删除消息],设置为false则需要手动应答
                 * 参数3:消息接收到后回调
                 */
                channel.basicConsume("fanout_queue1",true,callback);
    
                //注意,此处不建议关闭资源,让程序一直处于读取消息
            }
    }

    消费者二:

    package rabbitmq;
    
    import java.io.IOException;
    
    import com.rabbitmq.client.AMQP;
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    import com.rabbitmq.client.Consumer;
    import com.rabbitmq.client.DefaultConsumer;
    import com.rabbitmq.client.Envelope;
    
    public class FanoutConsumerTwo {
    
         public static void main(String[] args) throws Exception{
                Connection connection = ConnectionUtils.getConnection();
                //8、创建频道-channel = connection.createChannel()
                Channel channel = connection.createChannel();
                //9、声明队列-channel.queueDeclare(名称,是否持久化,是否独占本连接,是否自动删除,附加参数)
                channel.queueDeclare("fanout_queue2",true,false,false,null);
                //队列绑定交换机-channel.queueBind(队列名, 交换机名, 路由key[广播消息设置为空串])
                channel.queueBind("fanout_queue2", "fanout_exchange", "");
    
                //创建消费者
                Consumer callback = new DefaultConsumer(channel){
                    /**
                     * @param consumerTag 消费者标签,在channel.basicConsume时候可以指定
                     * @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送)
                     * @param properties  属性信息(生产者的发送时指定)
                     * @param body 消息内容
                     * @throws IOException
                     */
                    @Override
                    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                        //路由的key
                        String routingKey = envelope.getRoutingKey();
                        //获取交换机信息
                        String exchange = envelope.getExchange();
                        //获取消息ID
                        long deliveryTag = envelope.getDeliveryTag();
                        //获取消息信息
                        String message = new String(body,"utf-8");
                        System.out.println(
                                "routingKey:" + routingKey +
                                ",exchange:" + exchange +
                                ",deliveryTag:" + deliveryTag +
                                ",message:" + message);
                    }
                };
                /**
                 * 消息消费
                 * 参数1:队列名称
                 * 参数2:是否自动应答,true为自动应答[mq接收到回复会删除消息],设置为false则需要手动应答
                 * 参数3:消息接收到后回调
                 */
                channel.basicConsume("fanout_queue2",true,callback);
    
                //注意,此处不建议关闭资源,让程序一直处于读取消息
            }
    }

    生产者:

    package rabbitmq;
    
    import com.rabbitmq.client.BuiltinExchangeType;
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    
    public class FanoutProducer {
    
        public static void main(String[] args) throws Exception{
            Connection connection = ConnectionUtils.getConnection();
            //8、创建频道-channel = connection.createChannel()
            Channel channel = connection.createChannel();
            //声明交换机- channel.exchangeDeclare(交换机名字,交换机类型)
            channel.exchangeDeclare("fanout_exchange", BuiltinExchangeType.FANOUT);
            //连续发10条消息
            for (int i = 0; i < 10; i++) {
                //10、创建消息-String m = xxx
                String message = "hello, message!" + i;
                //11、消息发送-channel.basicPublish(交换机[默认Default Exchage],路由key[简单模式可以传递队列名称],消息其它属性,消息内容)
                channel.basicPublish("fanout_exchange","",null,message.getBytes("utf-8"));
                System.out.println("发送消息成功: " + message);
            }
            //12、关闭资源-channel.close();connection.close()
            channel.close();
            connection.close();
        }
    }

    启动两个生产者,后启动消费者后消费消息。

    3.Topic类型

      处理routingKey和bindingKey,支持通配符。# 匹配0或多个单词,* 匹配单个单词。 Topic主题模式可以实现 Publish/Subscribe发布订阅模式 和 Routing路由模式 的双重功能

    生产者:

    package rabbitmq;
    
    import com.rabbitmq.client.BuiltinExchangeType;
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    
    public class TopicProducer {
    
          public static void main(String[] args) throws Exception{
                Connection connection = ConnectionUtils.getConnection();
                //8、创建频道-channel = connection.createChannel()
                Channel channel = connection.createChannel();
    
                //声明交换机- channel.exchangeDeclare(交换机名字,交换机类型)
                channel.exchangeDeclare("topic_exchange", BuiltinExchangeType.TOPIC);
                //连续发3条消息
                for (int i = 0; i < 5; i++) {
                    String routingKey = "";
                    //发送消息的时候根据相关逻辑指定相应的routing key。
                    switch (i){
                        case 0:  //假设i=0,为error消息
                            routingKey = "log.error";
                            break;
                        case 1: //假设i=1,为info消息
                            routingKey = "log.info";
                            break;
                        case 2: //假设i=2,为warning消息
                            routingKey = "log.warning";
                            break;
                        case 3: //假设i=3,为log.info.add消息
                            routingKey = "log.info.add";
                            break;
                        case 4: //假设i=4,为log.info.update消息
                            routingKey = "log.info.update";
                            break;
                    }
                    //10、创建消息-String m = xxx
                    String message = "hello,message!" + i;
                    //11、消息发送-channel.basicPublish(交换机[默认Default Exchage],路由key[简单模式可以传递队列名称],消息其它属性,消息内容)
                    channel.basicPublish("topic_exchange",routingKey,null,message.getBytes("utf-8"));
                }
                //12、关闭资源-channel.close();connection.close()
                channel.close();
                connection.close();
            }
    }

    消费者一:

    package rabbitmq;
    
    import java.io.IOException;
    
    import com.rabbitmq.client.AMQP;
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    import com.rabbitmq.client.Consumer;
    import com.rabbitmq.client.DefaultConsumer;
    import com.rabbitmq.client.Envelope;
    
    public class TopicConsumerOne {
    
        public static void main(String[] args) throws Exception{
            Connection connection = ConnectionUtils.getConnection();
            //8、创建频道-channel = connection.createChannel()
            Channel channel = connection.createChannel();
            //9、声明队列-channel.queueDeclare(名称,是否持久化,是否独占本连接,是否自动删除,附加参数)
            channel.queueDeclare("topic_queue1",true,false,false,null);
            //队列绑定交换机与路由key
            channel.queueBind("topic_queue1", "topic_exchange", "log.*");
            //创建消费者
            Consumer callback = new DefaultConsumer(channel){
                /**
                 * @param consumerTag 消费者标签,在channel.basicConsume时候可以指定
                 * @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送)
                 * @param properties  属性信息(生产者的发送时指定)
                 * @param body 消息内容
                 * @throws IOException
                 */
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    //路由的key
                    String routingKey = envelope.getRoutingKey();
                    //获取交换机信息
                    String exchange = envelope.getExchange();
                    //获取消息ID
                    long deliveryTag = envelope.getDeliveryTag();
                    //获取消息信息
                    String message = new String(body,"utf-8");
                    System.out.println(
                            "routingKey:" + routingKey +
                            ",exchange:" + exchange +
                            ",deliveryTag:" + deliveryTag +
                            ",message:" + message);
                }
            };
            /**
             * 消息消费
             * 参数1:队列名称
             * 参数2:是否自动应答,true为自动应答[mq接收到回复会删除消息],设置为false则需要手动应答
             * 参数3:消息接收到后回调
             */
            channel.basicConsume("topic_queue1",true,callback);
    
            //注意,此处不建议关闭资源,让程序一直处于读取消息
        }
    }

    消费者二:

    package rabbitmq;
    
    import java.io.IOException;
    
    import com.rabbitmq.client.AMQP;
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    import com.rabbitmq.client.Consumer;
    import com.rabbitmq.client.DefaultConsumer;
    import com.rabbitmq.client.Envelope;
    
    public class TopicConsumerTwo {
    
         public static void main(String[] args) throws Exception{
                Connection connection = ConnectionUtils.getConnection();
                //8、创建频道-channel = connection.createChannel()
                Channel channel = connection.createChannel();
                //9、声明队列-channel.queueDeclare(名称,是否持久化,是否独占本连接,是否自动删除,附加参数)
                channel.queueDeclare("topic_queue2",true,false,false,null);
    
                //队列绑定路由key
                channel.queueBind("topic_queue2", "topic_exchange", "log.#");
                //创建消费者
                Consumer callback = new DefaultConsumer(channel){
                    /**
                     * @param consumerTag 消费者标签,在channel.basicConsume时候可以指定
                     * @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送)
                     * @param properties  属性信息(生产者的发送时指定)
                     * @param body 消息内容
                     * @throws IOException
                     */
                    @Override
                    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                        //路由的key
                        String routingKey = envelope.getRoutingKey();
                        //获取交换机信息
                        String exchange = envelope.getExchange();
                        //获取消息ID
                        long deliveryTag = envelope.getDeliveryTag();
                        //获取消息信息
                        String message = new String(body,"utf-8");
                        System.out.println(
                                "routingKey:" + routingKey +
                                ",exchange:" + exchange +
                                ",deliveryTag:" + deliveryTag +
                                ",message:" + message);
                    }
                };
                
                /**
                 * 消息消费
                 * 参数1:队列名称
                 * 参数2:是否自动应答,true为自动应答[mq接收到回复会删除消息],设置为false则需要手动应答
                 * 参数3:消息接收到后回调
                 */
                channel.basicConsume("topic_queue2",true,callback);
    
                //注意,此处不建议关闭资源,让程序一直处于读取消息
            }
    }

    启动两个消费者后启动生产者,最终入下:

    (1)消费者一

     (2)消费者二

     总结:

    1、简单模式

    一个生产者、一个消费者,不需要设置交换机(使用默认的交换机,一个direct类型的交换机,routing_key为queue名称)

    2、工作队列模式 Work Queue

    一个生产者、多个消费者(竞争关系),不需要设置交换机(使用默认的交换机,一个direct类型的交换机,routing_key为queue名称)

    3、发布订阅模式 Publish/subscribe

    需要设置类型为fanout的交换机,并且交换机和队列进行绑定,当发送消息到交换机后,交换机会将消息发送到绑定的队列。多播模式,不进行RoutingKey的判断。

    4、路由模式 Routing

    需要设置类型为direct的交换机,交换机和队列进行绑定,并且指定routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列

    5、通配符模式 Topic

    需要设置类型为topic的交换机,交换机和队列进行绑定,并且指定通配符方式的routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列

      补充一下,无论是fanout多播模式还是direct路由模式还是topic通配符模式,Exchanger收到消息是会发送到后面的queue列中。如果一个应用以多实例部署,多个实例监听一个Exchanger下面相同的队列,不会造成一个消息被相同的应用多实例重复消费,因为queue本质是不可重复消费。

      开发中可以一个应用一个交换机,不同的消息类型放到不同的队列中。如果涉及死信队列,可以对每个应用再建立一个死信交换机,队列名称相同,便于处理死信消息。

    补充:消息的属性可以通过BasicProperties进行设置

    BasicProperties源码如下:

        public static class BasicProperties extends com.rabbitmq.client.impl.AMQBasicProperties {
            private String contentType;
            private String contentEncoding;
            private Map<String,Object> headers;
            private Integer deliveryMode;
            private Integer priority;
            private String correlationId;
            private String replyTo;
            private String expiration;
            private String messageId;
            private Date timestamp;
            private String type;
            private String userId;
            private String appId;
            private String clusterId;

    测试:

    (1)生产者发送消息时生成一些属性

    package rabbitmq;
    
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.TimeoutException;
    
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    import com.rabbitmq.client.ConnectionFactory;
    import com.rabbitmq.client.AMQP.BasicProperties;
    
    public class Producer {
    
        public static ConnectionFactory getConnectionFactory() {
            // 创建连接工程,下面给出的是默认的case
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("192.168.99.100");
            factory.setPort(5672);
            factory.setUsername("guest");
            factory.setPassword("guest");
            factory.setVirtualHost("/");
            return factory;
        }
    
        public static void main(String[] args) throws IOException, TimeoutException  {
            ConnectionFactory connectionFactory = getConnectionFactory();
            Connection newConnection = null;
            Channel createChannel = null;
            try {
                newConnection = connectionFactory.newConnection();
                createChannel = newConnection.createChannel();
                
                /**
                 * 声明一个队列。
                 * 参数一:队列名称
                 * 参数二:是否持久化
                 * 参数三:是否排外  如果排外则这个队列只允许有一个消费者
                 * 参数四:是否自动删除队列,如果为true表示没有消息也没有消费者连接自动删除队列
                 * 参数五:队列的附加属性
                 * 注意:
                 * 1.声明队列时,如果已经存在则放弃声明,如果不存在则会声明一个新队列;
                 * 2.队列名可以任意取值,但需要与消息接收者一致。
                 * 3.下面的代码可有可无,一定在发送消息前确认队列名称已经存在RabbitMQ中,否则消息会发送失败。
                 */
                createChannel.queueDeclare("myQueue", true, false, false,null);
                
                String message = "测试消息";
                // 设置消息属性以及headers
                Map<String, Object> headers = new HashMap<>();
                headers.put("creator", "张三");
                BasicProperties build = new BasicProperties().builder().appId("test001").messageId("001").headers(headers).build();
                /**
                 * 发送消息到MQ
                 * 参数一:交换机名称,为""表示不用交换机
                 * 参数二:为队列名称或者routingKey.当指定了交换机就是routingKey
                 * 参数三:消息的属性信息
                 * 参数四:消息内容的字节数组
                 */
                createChannel.basicPublish("", "myQueue", build, message.getBytes());
                
                System.out.println("消息发送成功");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (createChannel != null) {
                    createChannel.close();
                }
                if (newConnection != null) {
                    newConnection.close();
                }
            }
            
        }
    }

    (2)消息接收者

    package rabbitmq;
    
    import java.io.IOException;
    import java.util.concurrent.TimeoutException;
    
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    import com.rabbitmq.client.ConnectionFactory;
    import com.rabbitmq.client.DefaultConsumer;
    import com.rabbitmq.client.Envelope;
    import com.rabbitmq.client.AMQP.BasicProperties;
    
    public class Consumer {
    
        public static ConnectionFactory getConnectionFactory() {
            // 创建连接工程,下面给出的是默认的case
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("192.168.99.100");
            factory.setPort(5672);
            factory.setUsername("guest");
            factory.setPassword("guest");
            factory.setVirtualHost("/");
            return factory;
        }
    
        public static void main(String[] args) throws IOException, TimeoutException  {
            ConnectionFactory connectionFactory = getConnectionFactory();
            Connection newConnection = null;
            Channel createChannel = null;
            try {
                newConnection = connectionFactory.newConnection();
                createChannel = newConnection.createChannel();
                /**
                 * 声明一个队列。
                 * 参数一:队列名称
                 * 参数二:是否持久化
                 * 参数三:是否排外  如果排外则这个队列只允许有一个消费者
                 * 参数四:是否自动删除队列,如果为true表示没有消息也没有消费者连接自动删除队列
                 * 参数五:队列的附加属性
                 * 注意:
                 * 1.声明队列时,如果已经存在则放弃声明,如果不存在则会声明一个新队列;
                 * 2.队列名可以任意取值,但需要与消息接收者一致。
                 * 3.下面的代码可有可无,一定在发送消息前确认队列名称已经存在RabbitMQ中,否则消息会发送失败。
                 */
                createChannel.queueDeclare("myQueue", true, false, false,null);
                /**
                 * 接收消息。会持续坚挺,不能关闭channel和Connection
                 * 参数一:队列名称
                 * 参数二:消息是否自动确认,true表示自动确认接收完消息以后会自动将消息从队列移除。否则需要手动ack消息
                 * 参数三:消息接收者的标签,用于多个消费者同时监听一个队列时用于确认不同消费者。
                 * 参数四:消息接收者
                 */
                createChannel.basicConsume("myQueue", true, "", new DefaultConsumer(createChannel) {
                    @Override
                    public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
                            byte[] body) throws IOException {
                        
                        System.out.println(properties);
                        
                        System.out.println(envelope);
                        
                        String string = new String(body, "UTF-8");
                        System.out.println("接收到d消息: -》 " + string);
                    }
                });
                
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
            }
            
        }
    }

    结果:

    #contentHeader<basic>(content-type=null, content-encoding=null, headers={creator=张三}, delivery-mode=null, priority=null, correlation-id=null, reply-to=null, expiration=null, message-id=001, timestamp=null, type=null, user-id=null, app-id=test001, cluster-id=null)
    Envelope(deliveryTag=1, redeliver=false, exchange=, routingKey=myQueue)
    接收到d消息: -》 测试消息

    补充: RabbitMQheaders消息类型的交换机使用方法如下:

    x-match 为all是匹配所有的请求头和值,必须所有相等才会发送;any是满足任意一个即可。

    package rabbitmq;
    
    import java.util.HashMap;
    import java.util.Hashtable;
    import java.util.Map;
    
    import com.rabbitmq.client.AMQP.BasicProperties.Builder;
    import com.rabbitmq.client.BuiltinExchangeType;
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    
    public class HeadersProducer {
        
        public static void main(String[] args) {
            Connection connection = null;
            Channel channel = null;
            try {
                connection = ConnectionUtils.getConnection();
                channel = connection.createChannel();
    
                
                //声明交换机
                channel.exchangeDeclare("header_exchange", BuiltinExchangeType.HEADERS);
                // 声明queue
                channel.queueDeclare("header_queue", true, false, false, null);
                // 声明bind-x-match any  匹配任意一个头,x-match all 匹配所有key
                Map<String, Object> bindingArgs = new HashMap<String, Object>();
                bindingArgs.put("x-match", "all"); //any or all
                bindingArgs.put("headName1", "val1");
                bindingArgs.put("headName2", "val2");
                channel.queueBind("header_queue", "header_exchange", "", bindingArgs);
                
                //设置消息头键值对信息
                Map<String, Object> headers = new Hashtable<String, Object>();
                headers.put("headName1", "val1");
                headers.put("headName2", "val2");
                Builder builder = new Builder();
                builder.headers(headers);
                String message = "这是headers测试消息234";
                channel.basicPublish("header_exchange", "", builder.build(), message.getBytes());
                System.out.println("发送消息: " + message);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    if (channel != null) {
                        // 回滚。如果未异常会提交事务,此时回滚无影响
                        channel.txRollback();
                        channel.close();
                    }
                    if (connection != null) {
                        connection.close();
                    }
                } catch (Exception ignore) {
                    // ignore
                }
            }
        }
    }
    【当你用心写完每一篇博客之后,你会发现它比你用代码实现功能更有成就感!】
  • 相关阅读:
    vue.js小结
    前端js
    前端HTML页面签入微信和APP小结
    angualr引入bootstrap部分效果失效。
    .net面试问到的问题
    C#网页爬虫抓取行政区划
    mysql 更新一个字段(在他的后面添加字符串)
    关于asp.net C# 导出Excel文件 打开Excel文件格式与扩展名指定格式不一致的解决办法
    动态生成GridView列
    MSDN_FieldInfo.SetValue
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/13923237.html
Copyright © 2020-2023  润新知