RabbitMQ学习笔记

1. MQ 的基本概念

1.1 MQ概述

        MQ全称 M essage Q ueue(消息队列),是在消息的传输过程中保存消息的容器。多用于分布式系统之间进行通信。

小结

  • MQ,消息队列,存储消息的中间件
  • 分布式系统通信两种方式:直接远程调用 和 借助第三方 完成间接通信
  •  发送方称为生产者,接收方称为消费者

1.2 MQ 的优势和劣势

 1.3 MQ 的优势

1. 应用解耦

远程调用产生的问题:耦合

1.容错性:如果产生异常一条链路挂了,导致订单系统也可能出现问题,用户就会得到下单失败

2.可维护性:增删X系统都要修改订单系统

 

 mq解决的问题

1.容错性提高,隔离,链路挂了可能过几分钟就会修复,不会影响到订单系统

2.如果需要增加x系统,在mq里面拿出信息消费就行,订单系统一行都不需要修改

2.异步提速 

 

 3.削峰填谷

MQ 的优势 小结

  • 应用解耦:提高系统容错性和可维护性
  • 异步提速:提升用户体验和系统吞吐量
  • 削峰填谷:提高系统稳定性

1.4 MQ 的劣势

 1.系统的可用性

        系统引入的外部依赖越多,系统的稳定性就越差,我们一开始只需要维护系统A和B,现在还有维护mq中间件。

2.系统的复杂度

        MQ 的加入大大增加了系统的复杂度,以前系统间是同步的远程调用,现在是通过 MQ 进行异步调用。如何 保证消息没有被重复消费?怎么处理消息丢失情况?那么保证消息传递的顺序

3.系统的一致性

        A 系统处理完业务,通过 MQ 给B、C、D三个系统发消息数据,如果 B 系统、C 系统处理成功,D 系统处理失败。如何保证消息数据处理的一致性?

总结

既然 MQ 有优势也有劣势,那么使用 MQ 需要满足什么条件呢?
        ① 生产者不需要从消费者处获得反馈。引入消息队列之前的直接调用,其接口的返回值应该为空,这才让明 明下层的动作还没做,上层却当成动作做完了继续往后走,即所谓异步成为了可能。
        ② 容许短暂的不一致性。
        ③ 确实是用了有效果。即解耦、提速、削峰这些方面的收益,超过加入MQ,管理MQ这些成本

 1.5 常见的 MQ 产品

 1.6 RabbitMQ 简介

AMQP Advanced Message Queuing Protocol (高级消息队列协议),是一个网络协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中 间件不同产品,不同的开发语言等条件的限制。2006年,AMQP 规范发布。 类比HTTP

 小结

1. RabbitMQ 是基于 AMQP 协议使用 Erlang 语言开发的一款消息队列产品。
2. RabbitMQ提供了6种工作模式,我们学习5种。这是今天的重点。
3. AMQP 是协议,类比HTTP。
4. JMS 是 API 规范接口,类比 JDBC。

2. RabbitMQ 的安装和配置

下载最新版本的镜像:

docker pull rabbitmq

2、创建并运行 RabbitMQ 容器 

docker run -d -p 15672:15672 -p 5672:5672 \
	-e RABBITMQ_DEFAULT_VHOST=my_vhost  \
	-e RABBITMQ_DEFAULT_USER=admin \
	-e RABBITMQ_DEFAULT_PASS=admin \
	--hostname myRabbit \
	--name rabbitmq \
	rabbitmq

设置 docker 启动的时候自动启动(可选):

docker update rabbitmq --restart=always

3、启动 rabbitmq_management

方法一:

docker exec -it rabbitmq /bin/bash
---------------------------------
user@7b295c46c99d /: rabbitmq-plugins enable rabbitmq_management

方法二:

docker exec -it rabbitmq rabbitmq-plugins enable rabbitmq_management

4、访问 RabbitMQ 后台管理

    浏览器输入地址:http://ip:15672 即可访问后台管理页面,这里的 ip 为运行 RabbitMQ 所在的服务器的 IP 地址;
    默认的用户名和密码都是 guest(如果没有在容器创建的时候指定用户名密码);
 

3. RabbitMQ 快速入门

1.简单模式

需求:使用简单模式完成消息传递
步骤:
  • 创建工程(生成者、消费者)
  • 分别添加依赖
  • 编写生产者发送消息
  • 编写消费者接收消息

1.创建工程(生成者、消费者)

 2.分别添加依赖

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


    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

 3.编写生产者发送消息

  •  创建连接工厂
  • 设置参数
  • 创建连接connection
  • 创建channel
  • 创建队列queue
  • 发送消息
public class Producer_helloWorld {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2.设置参数
        factory.setHost("8.130.108.134");//ip
        factory.setPort(5672);//端口
        factory.setVirtualHost("/itcast");//虚拟机
        factory.setUsername("heima");//用户名
        factory.setPassword("heima");//密码
        //3.创建连接connection
        Connection connection = factory.newConnection();
        //4.创建channel
        Channel channel = connection.createChannel();
        //5.创建队列queue
        /**
         * queueDeclare(String queue, boolean durable, boolean exclusive,
         * boolean autoDelete, Map<String, Object> arguments)
         * 参数:
         *       1.queue:队列名称
         *       2.durable:是否持久化
         *       3.exclusive
         *          *是否独占。只能有一个消费者监听这个队列
         *          *当connection关闭时,是否删除队列
         *       4.autoDelete:是否自动删除,当没有consumer时,自动删除掉
         *       5.arguments:参数信息
         */
        //如果没有一个叫hello_world的队列,则会创建改队列,如果有则不会创建
        channel.queueDeclare("hello_world",true,false,false,null);

        /**
         * basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
         * 1.exchange:交换机名称。简单模式下交换机会使用默认的“ ”
         * 2.routingKey:路由名称
         * 3.props:配置信息
         * 4.body:发送的信息
         */
        //6.发送消息
        channel.basicPublish("","hello_world",null,"hello rabbitmq".getBytes());

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

    }

 4.编写消费者消费消息

  •  创建连接工厂
  • 设置参数
  • 创建连接connection
  • 创建channel
  • 创建队列queue
  • 发送消息
    public static void main(String[] args) throws IOException, TimeoutException {
        //1.创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2.设置参数
        factory.setHost("8.130.108.134");//ip
        factory.setPort(5672);//端口
        factory.setVirtualHost("/itcast");//虚拟机
        factory.setUsername("heima");//用户名
        factory.setPassword("heima");//密码
        //3.创建连接
        Connection connection = factory.newConnection();
        //4.创建channel
        Channel channel = connection.createChannel();
        //5.创建队列queue
        /**
         * queueDeclare(String queue, boolean durable, boolean exclusive,
         * boolean autoDelete, Map<String, Object> arguments)
         * 参数:
         *       1.queue:队列名称
         *       2.durable:是否持久化
         *       3.exclusive
         *          *是否独占。只能有一个消费者监听这个队列
         *          *当connection关闭时,是否删除队列
         *       4.autoDelete:是否自动删除,当没有consumer时,自动删除掉
         *       5.arguments:参数信息
         */
        //如果没有一个叫hello_world的队列,则会创建改队列,如果有则不会创建
        channel.queueDeclare("hello_world",true,false,false,null);
        /**
         *
         * public String basicConsume(String queue, boolean autoAck, Consumer callback)
         * 1.queue:队列名称
         * 2.autoAck:是否自动确认
         * 3.callback:回调对象
         */
        //6.发送消息
        Consumer consumer = new DefaultConsumer(channel){
            /**
             *这是一个回调方法,收到消息后,自动执行该方法
             *1.consumerTag:标记
             * 2.envelope:获取一些信息,交换机,路由
             * 3.properties:配置信息
             * 4.body:数据
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("consumerTag:"+consumerTag);
                System.out.println("Envelope:"+envelope.getExchange());
                System.out.println("RoutingKey:"+envelope.getRoutingKey());
                System.out.println("properties:"+properties);
                System.out.println("body:"+new String(body));
            }
        };
        channel.basicConsume("hello_world",true,consumer);

        //7.消费者不用关闭资源
    }

小结

 4. RabbitMQ 的工作模式

4.1 Work queues 工作队列模式

1. 模式说明

 

  • Work Queues:与入门程序的简单模式相比,多了一个或一些消费端,多个消费端共同消费同一个队列中的消息。
  • 应用场景:对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。

1.生产者

Producer_WorkQueues

public class Producer_WorkQueues {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2.设置参数
        factory.setHost("8.130.108.134");//ip
        factory.setPort(5672);//端口
        factory.setVirtualHost("/itcast");//虚拟机
        factory.setUsername("heima");//用户名
        factory.setPassword("heima");//密码
        //3.创建连接connection
        Connection connection = factory.newConnection();
        //4.创建channel
        Channel channel = connection.createChannel();
        //5.创建队列queue
        /**
         * queueDeclare(String queue, boolean durable, boolean exclusive,
         * boolean autoDelete, Map<String, Object> arguments)
         * 参数:
         *       1.queue:队列名称
         *       2.durable:是否持久化
         *       3.exclusive
         *          *是否独占。只能有一个消费者监听这个队列
         *          *当connection关闭时,是否删除队列
         *       4.autoDelete:是否自动删除,当没有consumer时,自动删除掉
         *       5.arguments:参数信息
         */
        //如果没有一个叫hello_world的队列,则会创建改队列,如果有则不会创建
        channel.queueDeclare("work_queues",true,false,false,null);

        /**
         * basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
         * 1.exchange:交换机名称。简单模式下交换机会使用默认的“ ”
         * 2.routingKey:路由名称
         * 3.props:配置信息
         * 4.body:发送的信息
         */
        for (int i = 0; i < 10; i++) {
            //6.发送消息
            channel.basicPublish("","work_queues",null,("hello work_queues"+i).getBytes());

        }

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

    }
}

2.消费者

Consumer_WorkQueues1

public class Consumer_WorkQueues1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1.创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2.设置参数
        factory.setHost("8.130.108.134");//ip
        factory.setPort(5672);//端口
        factory.setVirtualHost("/itcast");//虚拟机
        factory.setUsername("heima");//用户名
        factory.setPassword("heima");//密码
        //3.创建连接
        Connection connection = factory.newConnection();
        //4.创建channel
        Channel channel = connection.createChannel();
        //5.创建队列queue
        /**
         * queueDeclare(String queue, boolean durable, boolean exclusive,
         * boolean autoDelete, Map<String, Object> arguments)
         * 参数:
         *       1.queue:队列名称
         *       2.durable:是否持久化
         *       3.exclusive
         *          *是否独占。只能有一个消费者监听这个队列
         *          *当connection关闭时,是否删除队列
         *       4.autoDelete:是否自动删除,当没有consumer时,自动删除掉
         *       5.arguments:参数信息
         */
        //如果没有一个叫hello_world的队列,则会创建改队列,如果有则不会创建
        channel.queueDeclare("work_queues",true,false,false,null);
        /**
         *
         * public String basicConsume(String queue, boolean autoAck, Consumer callback)
         * 1.queue:队列名称
         * 2.autoAck:是否自动确认
         * 3.callback:回调对象
         */
        //6.发送消息
        Consumer consumer = new DefaultConsumer(channel){
            /**
             *这是一个回调方法,收到消息后,自动执行该方法
             *1.consumerTag:标记
             * 2.envelope:获取一些信息,交换机,路由
             * 3.properties:配置信息
             * 4.body:数据
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("consumerTag:"+consumerTag);
                System.out.println("Envelope:"+envelope.getExchange());
                System.out.println("RoutingKey:"+envelope.getRoutingKey());
                System.out.println("properties:"+properties);
                System.out.println("body:"+new String(body));
            }
        };
        channel.basicConsume("work_queues",true,consumer);

        //7.消费者不用关闭资源
    }

Consumer_WorkQueues2

public class Consumer_WorkQueues1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1.创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2.设置参数
        factory.setHost("8.130.108.134");//ip
        factory.setPort(5672);//端口
        factory.setVirtualHost("/itcast");//虚拟机
        factory.setUsername("heima");//用户名
        factory.setPassword("heima");//密码
        //3.创建连接
        Connection connection = factory.newConnection();
        //4.创建channel
        Channel channel = connection.createChannel();
        //5.创建队列queue
        /**
         * queueDeclare(String queue, boolean durable, boolean exclusive,
         * boolean autoDelete, Map<String, Object> arguments)
         * 参数:
         *       1.queue:队列名称
         *       2.durable:是否持久化
         *       3.exclusive
         *          *是否独占。只能有一个消费者监听这个队列
         *          *当connection关闭时,是否删除队列
         *       4.autoDelete:是否自动删除,当没有consumer时,自动删除掉
         *       5.arguments:参数信息
         */
        //如果没有一个叫hello_world的队列,则会创建改队列,如果有则不会创建
        channel.queueDeclare("work_queues",true,false,false,null);
        /**
         *
         * public String basicConsume(String queue, boolean autoAck, Consumer callback)
         * 1.queue:队列名称
         * 2.autoAck:是否自动确认
         * 3.callback:回调对象
         */
        //6.发送消息
        Consumer consumer = new DefaultConsumer(channel){
            /**
             *这是一个回调方法,收到消息后,自动执行该方法
             *1.consumerTag:标记
             * 2.envelope:获取一些信息,交换机,路由
             * 3.properties:配置信息
             * 4.body:数据
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("consumerTag:"+consumerTag);
                System.out.println("Envelope:"+envelope.getExchange());
                System.out.println("RoutingKey:"+envelope.getRoutingKey());
                System.out.println("properties:"+properties);
                System.out.println("body:"+new String(body));
            }
        };
        channel.basicConsume("work_queues",true,consumer);

        //7.消费者不用关闭资源
    }

小结

1. 在一个队列中如果有多个消费者,那么消费者之间对于同一个消息的关系是 竞争 的关系。
2. Work Queues 对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。例如:短信服务部署多个,只需要有一个节点成功发送即可。

4.2 Pub/Sub 订阅模式

 

在订阅模型中,多了一个 Exchange 角色,而且过程略有变化:
  • P:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)
  • C:消费者,消息的接收者,会一直等待消息到来
  • Queue:消息队列,接收消息、缓存消息
  • Exchange:交换机(X)。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、 递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。Exchange有常见以下3种类型:
    • Fanout:广播,将消息交给所有绑定到交换机的队列
    •  Direct:定向,把消息交给符合指定routing key 的队列
    •  Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列
Exchange (交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与 Exchange 绑定,或者没有符合路由规则的队列,那么消息会丢失
1.发布者
Producer_PubSub
public class Consumer_PubSub1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1.创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2.设置参数
        factory.setHost("8.130.108.134");//ip
        factory.setPort(5672);//端口
        factory.setVirtualHost("/itcast");//虚拟机
        factory.setUsername("heima");//用户名
        factory.setPassword("heima");//密码
        //3.创建连接
        Connection connection = factory.newConnection();
        //4.创建channel
        Channel channel = connection.createChannel();

        /**
         *
         * public String basicConsume(String queue, boolean autoAck, Consumer callback)
         * 1.queue:队列名称
         * 2.autoAck:是否自动确认
         * 3.callback:回调对象
         */
        //6.发送消息
        Consumer consumer = new DefaultConsumer(channel){
            /**
             *这是一个回调方法,收到消息后,自动执行该方法
             *1.consumerTag:标记
             * 2.envelope:获取一些信息,交换机,路由
             * 3.properties:配置信息
             * 4.body:数据
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("body:"+new String(body));
                System.out.println("将日志信息打印到控制台");
            }
        };
        channel.basicConsume("test_fanout_queue1",true,consumer);

        //7.消费者不用关闭资源
    }

2.消费者

Consumer_PubSub1

public class Consumer_PubSub1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1.创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2.设置参数
        factory.setHost("8.130.108.134");//ip
        factory.setPort(5672);//端口
        factory.setVirtualHost("/itcast");//虚拟机
        factory.setUsername("heima");//用户名
        factory.setPassword("heima");//密码
        //3.创建连接
        Connection connection = factory.newConnection();
        //4.创建channel
        Channel channel = connection.createChannel();

        /**
         *
         * public String basicConsume(String queue, boolean autoAck, Consumer callback)
         * 1.queue:队列名称
         * 2.autoAck:是否自动确认
         * 3.callback:回调对象
         */
        //6.发送消息
        Consumer consumer = new DefaultConsumer(channel){
            /**
             *这是一个回调方法,收到消息后,自动执行该方法
             *1.consumerTag:标记
             * 2.envelope:获取一些信息,交换机,路由
             * 3.properties:配置信息
             * 4.body:数据
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("body:"+new String(body));
                System.out.println("将日志信息打印到控制台");
            }
        };
        channel.basicConsume("test_fanout_queue1",true,consumer);

        //7.消费者不用关闭资源
    }
}

Consumer_PubSub2


public class Consumer_PubSub1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1.创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2.设置参数
        factory.setHost("8.130.108.134");//ip
        factory.setPort(5672);//端口
        factory.setVirtualHost("/itcast");//虚拟机
        factory.setUsername("heima");//用户名
        factory.setPassword("heima");//密码
        //3.创建连接
        Connection connection = factory.newConnection();
        //4.创建channel
        Channel channel = connection.createChannel();

        /**
         *
         * public String basicConsume(String queue, boolean autoAck, Consumer callback)
         * 1.queue:队列名称
         * 2.autoAck:是否自动确认
         * 3.callback:回调对象
         */
        //6.发送消息
        Consumer consumer = new DefaultConsumer(channel){
            /**
             *这是一个回调方法,收到消息后,自动执行该方法
             *1.consumerTag:标记
             * 2.envelope:获取一些信息,交换机,路由
             * 3.properties:配置信息
             * 4.body:数据
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("body:"+new String(body));
                System.out.println("将日志信息打印到控制台");
            }
        };
        channel.basicConsume("test_fanout_queue1",true,consumer);

        //7.消费者不用关闭资源
    }

4.3 Routing 路由模式

模式说明

 
  • 队列与交换机的绑定,不能是任意绑定了,而是要指定一个 RoutingKey(路由key)
  • 消息的发送方在向 Exchange 发送消息时,也必须指定消息的 RoutingKey
  • Exchange 不再把消息交给每一个绑定的队列,而是根据消息的 Routing Key 进行判断,只有队列的 Routingkey 与消息的 Routing key 完全一致,才会接收到消息

 

  • P:生产者,向 Exchange 发送消息,发送消息时,会指定一个routing key
  • X:Exchange(交换机),接收生产者的消息,然后把消息递交给与 routing key 完全匹配的队列
  • C1:消费者,其所在队列指定了需要 routing key 为 error 的消息 
  • C2:消费者,其所在队列指定了需要 routing key 为 info、error、warning 的消息

1.发布者

Producer_Routing1

public class Producer_Routing {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2.设置参数
        factory.setHost("8.130.108.134");//ip
        factory.setPort(5672);//端口
        factory.setVirtualHost("/itcast");//虚拟机
        factory.setUsername("heima");//用户名
        factory.setPassword("heima");//密码
        //3.创建连接connection
        Connection connection = factory.newConnection();
        //4.创建channel
        Channel channel = connection.createChannel();
        //5.创建交换机
        /**
         * exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable,
         * boolean autoDelete,boolean Map<String, Object> arguments)
         * 1.exchange:交换机名称
         * 2.type:交换机类型
         *      DIRECT("direct"),:定向
         *     FANOUT("fanout"),:广播(扇形)
         *     TOPIC("topic"),:通配符
         *     HEADERS("headers");:参数匹配
         * 3.durable:是否持久化
         * 4.autoDelete:是否自动删除
         * 5.internal:内部使用。一般false
         * 6.arguments:参数
         */
        channel.exchangeDeclare("test_direct", BuiltinExchangeType.DIRECT,true,false,false,null);

        //6.创建队列
        channel.queueDeclare("test_direct_queue1",true,false,false,null);
        channel.queueDeclare("test_direct_queue2",true,false,false,null);

        //7.绑定队列和交换机
        /**
         * queueBind(String queue, String exchange, String routingKey)
         * 1.queue:队列名称
         * 2.exchange:交换机名称
         * 3.routingKey:路由器
         * 如果交换机的类型为fanout,routingKey为null
         */
        //队列1的绑定
        channel.queueBind("test_direct_queue1","test_fanout","error");
        //队列2的绑定
        channel.queueBind("test_direct_queue2","test_fanout","info");
        channel.queueBind("test_direct_queue2","test_fanout","error");
        channel.queueBind("test_direct_queue2","test_fanout","warning");

        /**
         * basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
         * 1.exchange:交换机名称。简单模式下交换机会使用默认的“ ”
         * 2.routingKey:路由名称
         * 3.props:配置信息
         * 4.body:发送的信息
         */
        String body = "日志信息:张三调用了findAll方法...日志级别:info...";
        //8.发送消息
        channel.basicPublish("test_direct","info",null,body.getBytes());

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


    }
}

2.消费者

Consumer_Routing2

public class Consumer_Routing1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1.创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2.设置参数
        factory.setHost("8.130.108.134");//ip
        factory.setPort(5672);//端口
        factory.setVirtualHost("/itcast");//虚拟机
        factory.setUsername("heima");//用户名
        factory.setPassword("heima");//密码
        //3.创建连接
        Connection connection = factory.newConnection();
        //4.创建channel
        Channel channel = connection.createChannel();

        /**
         *
         * public String basicConsume(String queue, boolean autoAck, Consumer callback)
         * 1.queue:队列名称
         * 2.autoAck:是否自动确认
         * 3.callback:回调对象
         */
        //6.发送消息
        Consumer consumer = new DefaultConsumer(channel){
            /**
             *这是一个回调方法,收到消息后,自动执行该方法
             *1.consumerTag:标记
             * 2.envelope:获取一些信息,交换机,路由
             * 3.properties:配置信息
             * 4.body:数据
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("body:"+new String(body));
                System.out.println("将日志信息打印到控制台");
            }
        };
        channel.basicConsume("test_direct_queue1",true,consumer);

        //7.消费者不用关闭资源
    }
}

Consumer_Routing2


public class Consumer_Routing2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1.创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2.设置参数
        factory.setHost("8.130.108.134");//ip
        factory.setPort(5672);//端口
        factory.setVirtualHost("/itcast");//虚拟机
        factory.setUsername("heima");//用户名
        factory.setPassword("heima");//密码
        //3.创建连接
        Connection connection = factory.newConnection();
        //4.创建channel
        Channel channel = connection.createChannel();

        /**
         *
         * public String basicConsume(String queue, boolean autoAck, Consumer callback)
         * 1.queue:队列名称
         * 2.autoAck:是否自动确认
         * 3.callback:回调对象
         */
        //6.发送消息
        Consumer consumer = new DefaultConsumer(channel){
            /**
             *这是一个回调方法,收到消息后,自动执行该方法
             *1.consumerTag:标记
             * 2.envelope:获取一些信息,交换机,路由
             * 3.properties:配置信息
             * 4.body:数据
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("body:"+new String(body));
                System.out.println("将日志信息打印到控制台");
            }
        };
        channel.basicConsume("test_direct_queue2",true,consumer);

        //7.消费者不用关闭资源
    }
}

3. 小结

Routing 模式要求队列在绑定交换机时要指定 routing key ,消息会转发到符合 routing key 的队列。

4.4 Topics 通配符模式

  • Topic 类型与 Direct 相比,都是可以根据 RoutingKey 把消息路由到不同的队列。只不过 Topic 类型Exchange 可以让队列在绑定 Routing key 的时候使用通配符
  • Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert
  •  通配符规则:# 匹配一个或多个词,* 匹配不多不少恰好1个词,例如:item.# 能够匹配 item.insert.abc 或者 item.insert,item.* 只能匹配 item.insert

//需求:queue1Name所有error级别的日志存入数据库,所有order系统日志存入数据库
//需求:queue2Name所有*.*的数据都打印到控制台

1.发布者

Producer_Topics


public class Producer_Topics {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2.设置参数
        factory.setHost("8.130.108.134");//ip
        factory.setPort(5672);//端口
        factory.setVirtualHost("/itcast");//虚拟机
        factory.setUsername("heima");//用户名
        factory.setPassword("heima");//密码
        //3.创建连接connection
        Connection connection = factory.newConnection();
        //4.创建channel
        Channel channel = connection.createChannel();
        //5.创建交换机
        /**
         * exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable,
         * boolean autoDelete,boolean Map<String, Object> arguments)
         * 1.exchange:交换机名称
         * 2.type:交换机类型
         *      DIRECT("direct"),:定向
         *     FANOUT("fanout"),:广播(扇形)
         *     TOPIC("topic"),:通配符
         *     HEADERS("headers");:参数匹配
         * 3.durable:是否持久化
         * 4.autoDelete:是否自动删除
         * 5.internal:内部使用。一般false
         * 6.arguments:参数
         */
        String exchangeName="test_topic";
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.TOPIC,true,false,false,null);

        //6.创建队列
        String queue1Name="test_topic_queue1";
        String queue2Name="test_topic_queue2";
        channel.queueDeclare(queue1Name,true,false,false,null);
        channel.queueDeclare(queue2Name,true,false,false,null);

        //7.绑定队列和交换机
        /**
         * queueBind(String queue, String exchange, String routingKey)
         * 1.queue:队列名称
         * 2.exchange:交换机名称
         * 3.routingKey:路由器
         * 如果交换机的类型为fanout,routingKey为null
         */
        //需求:queue1Name所有error级别的日志存入数据库,所有order系统日志存入数据库
        //需求:queue2Name所有*.*的数据都打印到控制台
        channel.queueBind(queue1Name,exchangeName,"#.error");
        channel.queueBind(queue1Name,exchangeName,"order.*");
        channel.queueBind(queue2Name,exchangeName,"*.*");

        /**
         * basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
         * 1.exchange:交换机名称。简单模式下交换机会使用默认的“ ”
         * 2.routingKey:路由名称
         * 3.props:配置信息
         * 4.body:发送的信息
         */
        String body = "日志信息:张三调用了findAll方法...日志级别:info...";
        //8.发送消息
        channel.basicPublish(exchangeName,"good.info",null,body.getBytes());

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


    }
}

Consumer_Topic1

public class Consumer_Topic1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1.创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2.设置参数
        factory.setHost("8.130.108.134");//ip
        factory.setPort(5672);//端口
        factory.setVirtualHost("/itcast");//虚拟机
        factory.setUsername("heima");//用户名
        factory.setPassword("heima");//密码
        //3.创建连接
        Connection connection = factory.newConnection();
        //4.创建channel
        Channel channel = connection.createChannel();

        String queue1Name = "test_topic_queue1";
        String queue2Name = "test_topic_queue2";

        /**
         *
         * public String basicConsume(String queue, boolean autoAck, Consumer callback)
         * 1.queue:队列名称
         * 2.autoAck:是否自动确认
         * 3.callback:回调对象
         */
        //6.发送消息
        Consumer consumer = new DefaultConsumer(channel){
            /**
             *这是一个回调方法,收到消息后,自动执行该方法
             *1.consumerTag:标记
             * 2.envelope:获取一些信息,交换机,路由
             * 3.properties:配置信息
             * 4.body:数据
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("body:"+new String(body));
                System.out.println("将日志存到数据库");
            }
        };
        channel.basicConsume(queue1Name,true,consumer);

        //7.消费者不用关闭资源
    }

Consumer_Topic2

public class Consumer_Topic2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1.创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2.设置参数
        factory.setHost("8.130.108.134");//ip
        factory.setPort(5672);//端口
        factory.setVirtualHost("/itcast");//虚拟机
        factory.setUsername("heima");//用户名
        factory.setPassword("heima");//密码
        //3.创建连接
        Connection connection = factory.newConnection();
        //4.创建channel
        Channel channel = connection.createChannel();

        String queue1Name = "test_topic_queue1";
        String queue2Name = "test_topic_queue2";

        /**
         *
         * public String basicConsume(String queue, boolean autoAck, Consumer callback)
         * 1.queue:队列名称
         * 2.autoAck:是否自动确认
         * 3.callback:回调对象
         */
        //6.发送消息
        Consumer consumer = new DefaultConsumer(channel){
            /**
             *这是一个回调方法,收到消息后,自动执行该方法
             *1.consumerTag:标记
             * 2.envelope:获取一些信息,交换机,路由
             * 3.properties:配置信息
             * 4.body:数据
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("body:"+new String(body));
                System.out.println("将日志存到数据库");
            }
        };
        channel.basicConsume(queue2Name,true,consumer);

        //7.消费者不用关闭资源
    }

总结

1. 简单模式 HelloWorld
        一个生产者、一个消费者,不需要设置交换机(使用默认的交换机)。
2. 工作队列模式 Work Queue
        一个生产者、多个消费者(竞争关系),不需要设置交换机(使用默认的交换机)。
3. 发布订阅模式 Publish/subscribe
        需要设置类型为 fanout 的交换机,并且交换机和队列进行绑定,当发送消息到交换机后,交换机会将消 息发送到绑定的队列。
4. 路由模式 Routing
        需要设置类型为 direct 的交换机,交换机和队列进行绑定,并且指定 routing key,当发送消息到交换机 后,交换机会根据 routing key 将消息发送到对应的队列。
5. 通配符模式 Topic
        需要设置类型为 topic 的交换机,交换机和队列进行绑定,并且指定通配符方式的 routing key,当发送 消息到交换机后,交换机会根据 routing key 将消息发送到对应的队列。

4. SpringBoot 整合RabbitMQ 

生产端
1. 创建生产者SpringBoot工程
2. 引入start,依赖坐标
        <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
3. 编写yml配置,基本信息配置
4. 定义交换机,队列以及绑定关系的配置类
5. 注入RabbitTemplate,调用方法,完成消息发送
 
1.pom.xml
    <dependencies>
<!--        rabbitmq的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>

2.application.yml

spring:
  rabbitmq:
    host: 8.130.108.134
    username: itcast
    password: 123321
    virtual-host: /
    port: 5672

3.RabbitMQConfig

@Configuration
public class RabbitMQConfig {

    public static final String EXCHANGE_NAME = "boot_topic_exchange";
    public static final String QUEUE_NAME = "boot_queue";

    //1.交换机
    @Bean("bootExchange")
    public Exchange bootExchange(){
        //名字和持久化
        return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
    }


    //2.Queue 队列
    @Bean("bootQueue")
    public Queue bootQueue(){
        return QueueBuilder.durable(QUEUE_NAME).build();
    }

    //3. 队列和交互机绑定关系 Binding
    /*
        1. 知道哪个队列
        2. 知道哪个交换机
        3. routing key
     */
    @Bean
    public Binding bindQueueExchange(@Qualifier("bootQueue") Queue queue, @Qualifier("bootExchange") Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("boot.#").noargs();
    }


}

4.ProducerTest


@SpringBootTest
@RunWith(SpringRunner.class)
public class ProducerTest {

    //1.注入RabbitTemplate
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSend(){

        rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"boot.haha","boot mq hello~~~");
    }
消费端
1. 创建消费者SpringBoot工程
2. 引入start,依赖坐标
        <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
3. 编写yml配置,基本信息配置
4. 定义监听类,使用@RabbitListener注解完成队列监听。
1.pom.xml
    <dependencies>
<!--        rabbitmq的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>

2.application.yml

spring:
  rabbitmq:
    host: 8.130.108.134
    username: itcast
    password: 123321
    virtual-host: /
    port: 5672

3.RabbitMQListener

@Component
public class RabbitMQListener {
    @RabbitListener(queues ="boot_queue")
//    自动监听队列的信息到方法参数
    public void ListenerQueue(Message message){
        System.out.println(new String(message.getBody()));
    }
}

小结

  • SpringBoot提供了快速整合RabbitMQ的方式
  • 基本信息再yml中配置,队列交互机以及绑定关系在配置类中使用Bean的方式配置
  • 生产端直接注入RabbitTemplate完成消息发送
  • 消费端直接使用@RabbitListener完成消息接收

1. RabbitMQ 高级特性

RabbitMQ高级特性
消息可靠性投递
Consumer ACK
消费端限流
TTL
死信队列
延迟队列
日志与监控
消息可靠性分析与追踪
管理
RabbitMQ应用问题
消息可靠性保障
消息幂等性处理
RabbitMQ集群搭建
RabbitMQ高可用集群

1.1 消息的可靠投递

在使用 RabbitMQ 的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败场景。RabbitMQ 为我们提
供了两种方式用来控制消息的投递可靠性模式。
  • confirm 确认模式
  • return 退回模式
abbitmq 整个消息投递的路径为:
producer--->rabbitmq broker--->exchange--->queue--->consumer
  • 消息从 producer 到 exchange 则会返回一个 confirmCallback 。
  • 消息从 exchange-->queue 投递失败则会返回一个 returnCallback 。
我们将利用这两个 callback 控制消息的可靠性投递

步骤

设置 ConnectionFactory publisher-confirms="true" 开启 确认模式。
使用 rabbitTemplate.setConfirmCallback 设置回调函数。当消息发送到 exchange 后回
confirm 方法。在方法中判断 ack ,如果为 true ,则发送成功,如果为 false ,则发
送失败,需要处理。
设置 ConnectionFactory publisher-returns="true" 开启 退回模式。
使用 rabbitTemplate.setReturnCallback 设置退回函数,当消息从 exchange 路由到
queue 失败后,如果设置了 rabbitTemplate.setMandatory(true) 参数,则会将消息退
回给 producer 。并执行回调函数 returnedMessage
RabbitMQ 中也提供了事务机制,但是性能较差,此处不做讲解。
使用 channel 下列方法,完成事务控制:
        txSelect(), 用于将当前 channel 设置成 transaction 模式
        txCommit(),用于提交事务
        txRollback(),用于回滚事务

1.2 Consumer Ack

ack指Acknowledge,确认。 表示消费端收到消息后的确认方式。
有三种确认方式:
  • 自动确认:acknowledge="none"
  • 手动确认:acknowledge="manual"
  • 根据异常情况确认:acknowledge="auto",(这种方式使用麻烦,不作讲解)
其中自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应 message 从 RabbitMQ 的消息缓存中移除。但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失。如 果设置了手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则 调用channel.basicNack()方法,让其自动重新发送消息。

1.3 消费端限流

 

  • 在<rabbit:listener-container> 中配置 prefetch属性设置消费端一次拉取多少消息
  • 消费端的确认模式一定为手动确认。acknowledge="manual

1.4 TTL

  • TTL 全称 Time To Live(存活时间/过期时间)。
  • 当消息到达存活时间后,还没有被消费,会被自动清除。
  • RabbitMQ可以对消息设置过期时间,也可以对整个队列(Queue)设置过期时间。

 

 

  • 设置队列过期时间使用参数:x-message-ttl,单位:ms(毫秒),会对整个队列消息统一过期。
  • 设置消息过期时间使用参数:expiration。单位:ms(毫秒),当该消息在队列头部时(消费时),会单独判断 这一消息是否过期。
  • 如果两者都进行了设置,以时间短的为准。

1.5 死信队列

死信队列,英文缩写:DLX 。Dead Letter Exchange(死信交换机),当消息成为Dead message后,可以被重新发送到另一个交换机,这个交换机就是DLX。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LI JS@你猜啊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值