RabbitMQ-个人笔记

文章目录

  • 1. 什么是消息队列
  • 2. MQ的优势和劣势
  • 3. MQ的对比
  • 4.RabbitMQ 简介
  • 5. RabbitMQ模式及其使用
    • 5.1 Hello World
    • 5.2 Work queues
    • 5.3 Publish/Subscribe
    • 5.4 Routing
    • 5.5 Topics
    • 5.6 总结

1. 什么是消息队列

在学习Java基础的过程中,使用线程池,其中一个参数就是阻塞队列,因为有核心线程数和最大线程数的限制,当超过了最大最大线程数时,当有新的线程进来,就需要有一个阻塞队列,这里的队列就相当于缓存的作用.
依次类比,消息中间件就是对消息的缓存,在生成消息消费消息中间创建一个缓冲区,一个只管放,一个只管取。用专业的名称来讲,一个叫做生产者,一个叫做消费者
RabbitMQ-个人笔记_第1张图片

2. MQ的优势和劣势

优势

  • 应用解耦:如下图所示,订单系统是整个消息的入口,库存系统出现问题,会导致订单系统也出现问题,那么整个系统可能也会随之崩溃,这就是耦合度太高
    RabbitMQ-个人笔记_第2张图片
    使用MQ,库存系统在MQ中取数据,出错也不会影响订单系统以及其他系统。
    RabbitMQ-个人笔记_第3张图片

  • 异步提速:典型的下订单的例子,如图所示:如果等后面的所以系统都完成,需要920ms
    RabbitMQ-个人笔记_第4张图片
    而使用MQ,下完订单就返回,剩下的慢慢处理,用户体验不就上来了
    RabbitMQ-个人笔记_第5张图片

  • 削峰填谷:如果同时有大量的请求过来,服务器可能无法撑住,但是如果有MQ作为中间件,所以的流量全部打到MQ上,服务器再慢慢的从MQ中取数据,这样就避免了高峰。
    RabbitMQ-个人笔记_第6张图片
    RabbitMQ-个人笔记_第7张图片
    RabbitMQ-个人笔记_第8张图片

劣势

  • 系统的可用性降低
  • 系统的复杂度提高
  • 数据的一致性问题
    RabbitMQ-个人笔记_第9张图片

既然MQ有这些缺点,那应该在什么样的情况下选择MQ呢?

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

3. MQ的对比

RabbitMQ-个人笔记_第10张图片

4.RabbitMQ 简介

RabbitMQ是由erlang语言开发,基于AMQP(Advanced Message Queue 高级消息队列协议)协议实现的消息队列,它是一种应用程序之间的通信方法,消息队列在分布式系统开发中应用非常广泛。RabbitMQ官方地址:https://www.rabbitmq.com
基础架构如图所示:
RabbitMQ-个人笔记_第11张图片

基本概念:

  • Broker:接收和分发消息的应用,RabbitMQ Server就是 Message Broker
  • Virtual host:出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网
    络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出多
    个vhost,每个用户在自己的 vhost 创建 exchange/queue 等
  • Connection:publisher/consumer 和 broker 之间的 TCP 连接
  • Channel:如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP Connection的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的 channel 进行通讯,AMQP method 包含了channel id 帮助客户端和message broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的 Connection 极大减少了操作系统建立 TCP connection 的开销
  • Exchange:message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到queue 中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout (multicast)
  • Queue:消息最终被送到这里等待 consumer 取走
  • Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key。Binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据

5. RabbitMQ模式及其使用

现在的RabbitMQ共有七种模式,分别为:
Hello World模式
Work queues 工作队列模式
Publish/Subscribe 发布订阅模式
Routing 路由模式
Topics 主题模式
RPC 远程调用模式
Publisher Confirms 发布确认模式

在Maven中,导入一下坐标

	<dependencies>
        <dependency>
            <groupId>com.rabbitmqgroupId>
            <artifactId>amqp-clientartifactId>
            <version>5.6.0version>
        dependency>
    dependencies>

从最简单的模式开始,永远的Hello World

// 公共配置
		// 1. 创建连接工厂
		ConnectionFactory factory = new ConnectionFactory();

        // 2. 设置参数
        factory.setHost("xxx.xx.xxx.xxx"); //主机地址
        factory.setPort(5672); // 端口号
        factory.setVirtualHost("/shang"); //设置虚拟机,类似于在linux系统下 / 后面有多个子目录一样
        factory.setUsername("lcp"); // 用户名
        factory.setPassword("lcp"); // 用户名密码

        // 3. 创建连接 Connection
        Connection connection = factory.newConnection();

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

5.1 Hello World

RabbitMQ-个人笔记_第12张图片

我们看图说话,一个生产者将消息放入队列中,消费者从队列中取得消息,可以看到是一对一的关系。

注意点:

  • connection :于RabbitMQ服务器建立连接
  • channel:和服务器建立通道
  • queue:队列,队列需要自己创建,或者进行指定,队列使用名字进行区分。如果创建队列时,该队列已存在,则直接返回。
// 生产者
        // 5. 创建队列Queue
        /*
        queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,
                                 Map arguments)
               1. queue 队列名称
               2. durable 是否持久化,当mq重启后,是否还在
               3. exclusive
                    - 是否独占 只能有一个消费者监听这个端口
                    - 当connection关闭时,是否删除队列
               4. autoDelete 是否自动删除,当没有消费者时
               5. arguments 参数
         */
        channel.queueDeclare("hello", true, false, false, null);

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

        // 7. 释放队列
        channel.close();
        connection.close();
// 消费者
		// 4. 创建Channel
        Channel channel = connection.createChannel();

        // 5. 创建队列Queue
        channel.queueDeclare("hello", true, false, false, null);

        // 6. 接收消息,异步方法
        /*
        String queue, boolean autoAck, Consumer callback
            callback 回调对象
         */
        Consumer consumer = new DefaultConsumer(channel) {
            // 回调方法
            /*
                String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body
                    1. consumerTag 消息标识
                    2. envelope 获取一些信息 :路由, 交换机
                    3. properties 配置信息
                    4. 真实数据
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("consumerTag:" + consumerTag);
                System.out.println("Exchange:" + envelope.getExchange());
                System.out.println("RoutingKey:" + envelope.getRoutingKey());
                System.out.println("properties:" + properties);
                System.out.println("body: " + new String(body));
            }
        };
        channel.basicConsume("hello", true, consumer);

总体步骤而言:

  1. 双方于服务器建立连接
  2. 获取channel
  3. 生产者和消费者 使用或者创建队列,对队列的参数进行定义
  4. 生产者生产消息
  5. 消费者通过异步回调的方式来处理消息

5.2 Work queues

RabbitMQ-个人笔记_第13张图片
与hello不同的是 一个生产者 -> 多个消费者,在消息处理过慢时,可以使用多个消费者进行消费,且这些消费者之间是竞争关系

关注点:

  • connection :于RabbitMQ服务器建立连接
  • channel:和服务器建立通道
  • queue:队列,队列需要自己创建,或者进行指定,队列使用名字进行区分。如果创建队列时,该队列已存在,则直接返回。
  • 多个消费者绑定同一个队列
// 生产者
// 5. 创建队列Queue
        /*
        queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,
                                 Map arguments)
               1. queue 队列名称
               2. durable 是否持久化,当mq重启后,是否还在
               3. exclusive
                    - 是否独占 只能有一个消费者监听这个端口
                    - 当connection关闭时,是否删除队列
               4. autoDelete 是否自动删除,当没有消费者时
               5. arguments 参数
         */
        channel.queueDeclare("workQueues", true, false, false, null);

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


        // 7. 释放队列
//        channel.close();
//        connection.close();
    }
// 消费者 只需绑定一个队列即可
// 5. 创建队列Queue
        channel.queueDeclare("workQueues", true, false, false, null);

        // 6. 接收消息
        /*
        String queue, boolean autoAck, Consumer callback
            callback 回调对象
         */
        Consumer consumer = new DefaultConsumer(channel) {
            // 回调方法
            /*
                String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body
                    1. consumerTag 消息标识
                    2. envelope 获取一些信息 :路由, 交换机
                    3. properties 配置信息
                    4. 真实数据
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//                System.out.println("consumerTag:" + consumerTag);
//                System.out.println("Exchange:" + envelope.getExchange());
//                System.out.println("RoutingKey:" + envelope.getRoutingKey());
//                System.out.println("properties:" + properties);
                System.out.println("body: " + new String(body));
            }
        };
        channel.basicConsume("workQueues", true, consumer);

总体步骤而言:

  1. 双方于服务器建立连接
  2. 获取channel
  3. 生产者和消费者 使用或者创建队列,对队列的参数进行定义
  4. 生产者生产消息
  5. 多个消费者通过异步回调的方式来处理消息

5.3 Publish/Subscribe

RabbitMQ-个人笔记_第14张图片
在这个模式中,出现了交换机的概念,生产者将消费发送到交换机,交换机再分别将消息转发到与之绑定的队列

关注点:

  • connection :于RabbitMQ服务器建立连接
  • channel:和服务器建立通道,并且绑定交换机
  • queue:队列,队列需要自己创建,或者进行指定,队列使用名字进行区分。如果创建队列时,该队列已存在,则直接返回。
  • exchange:创建交换机,让交换机和队列进行绑定,模式为fanout
// 生产者
		// 5. 创建交换机
        /*
	        String exchange, 路由名称
	        BuiltinExchangeType type, 路由类型
	            DIRECT("direct"), 定向
	            FANOUT("fanout"), 扇形(广播)
	            TOPIC("topic"), 通配符
	            HEADERS("headers"); 参数
	        boolean durable, 是否持久化
	        boolean autoDelete, 是否删除
	        boolean internal, 内部使用,一般用false
	        Map arguments 参数
         */
        String exchangeName = "testFanout";
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT,
                false, false, null);

        // 6. 创建队列
        String queueFanout1 = "testQueueFanout1";
        String queueFanout2 = "testQueueFanout2";
        channel.queueDeclare(queueFanout1, true, false, false, null);
        channel.queueDeclare(queueFanout2, true, false, false, null);

        /*
            String queue, String exchange, String routingKey
                routingKey 路由键,绑定规则
                如果路由规则为Fanout,则为空字符串
         */
        // 7. 绑定队列和交换机
        channel.queueBind(queueFanout1, exchangeName, "");
        channel.queueBind(queueFanout2, exchangeName, "");

        // 8. 发送消息
        String body = "这是最好的时代";
        channel.basicPublish(exchangeName, "", null, body.getBytes());

        // 9. 关闭
//        channel.close();
//        connection.close();
// 消费者 只关心队列
	// 6. 接收消息
        /*
        String queue, boolean autoAck, Consumer callback
            callback 回调对象
         */
        Consumer consumer = new DefaultConsumer(channel) {
            // 回调方法
            /*
                String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body
                    1. consumerTag 消息标识
                    2. envelope 获取一些信息 :路由, 交换机
                    3. properties 配置信息
                    4. 真实数据
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("consumerTag:" + consumerTag);
                System.out.println("Exchange:" + envelope.getExchange());
                System.out.println("RoutingKey:" + envelope.getRoutingKey());
                System.out.println("properties:" + properties);
                System.out.println("body: " + new String(body));
            }
        };
        channel.basicConsume("testQueueFanout1", true, consumer);

总体步骤:

  1. 双方于服务器建立连接
  2. 获取channel
  3. 创建交换机 exchange,指定名称、交换机类型和其他参数
  4. 生产者和消费者 使用或者创建队列,对队列的参数进行定义
  5. 将队列和交换机进行绑定
  6. 生产者生产消息
  7. 多个消费者通过异步回调的方式来处理消息

整个过程中,消费者只关心队列,不关心这个队列之前的所以设置

5.4 Routing

RabbitMQ-个人笔记_第15张图片
在订阅/发布的模式上,增加了routingKey:路由键,发布者发布消息时,不但需要指定具体的路由,并且需要指定具体路由键,那么这个消息只会发送到与这个路由和路由键绑定的队列中

举个例子:
A和B两个队列都绑定卡了同一个路由,但是A处理的消息是订单,B处理的消息是库存,那么根据消息的不同分类,将消息放到对应的队列中,这个就是路由

关注点:

  • connection :于RabbitMQ服务器建立连接
  • channel:和服务器建立通道,并且绑定交换机
  • queue:队列,队列需要自己创建,或者进行指定,队列使用名字进行区分。如果创建队列时,该队列已存在,则直接返回。
  • exchange:创建交换机,让交换机和队列进行绑定,模式为BuiltinExchangeType.DIRECT
  • routingKey:路由键,交换机和队列绑定的钥匙,生产者发送消息需要指定路由键
// 生产者
		// 5. 创建交换机,指定交换机类型为DIRECT
        String exchangeName = "testDirect";
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT,
                false, false, null);

        // 6. 创建队列
        String queueFanout1 = "testQueueDirect1";
        String queueFanout2 = "testQueueDirect2";
        channel.queueDeclare(queueFanout1, true, false, false, null);
        channel.queueDeclare(queueFanout2, true, false, false, null);

        /*
            String queue, String exchange, String routingKey
                routingKey 路由键,绑定规则
                如果路由规则为Fanout,则为空字符串
         */
        // 7. 绑定队列和交换机
        // 队列1绑定error
        channel.queueBind(queueFanout1, exchangeName, "error");

        // 队列2绑定error info debug
        channel.queueBind(queueFanout2, exchangeName, "error");
        channel.queueBind(queueFanout2, exchangeName, "info");
        channel.queueBind(queueFanout2, exchangeName, "debug");

        // 8. 发送消息
        String body = "这是最好的时代";
        channel.basicPublish(exchangeName, "info", null, body.getBytes());

        // 9. 关闭
//        channel.close();
//        connection.close();
// 消费者
 Consumer consumer = new DefaultConsumer(channel) {
            // 回调方法
            /*
                String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body
                    1. consumerTag 消息标识
                    2. envelope 获取一些信息 :路由, 交换机
                    3. properties 配置信息
                    4. 真实数据
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("consumerTag:" + consumerTag);
                System.out.println("Exchange:" + envelope.getExchange());
                System.out.println("RoutingKey:" + envelope.getRoutingKey());
                System.out.println("properties:" + properties);
                System.out.println("body: " + new String(body));
            }
        };
        channel.basicConsume("testQueueDirect2", true, consumer);

总体步骤:

  1. 双方与服务器建立连接
  2. 获取channel
  3. 创建交换机 exchange,指定名称、交换机类型和其他参数
  4. 生产者和消费者 使用或者创建队列,对队列的参数进行定义
  5. 将队列和交换机进行绑定,并且指定路由键
  6. 生产者生产消息,并且指定路由键
  7. 多个消费者通过异步回调的方式来处理消息

5.5 Topics

Topic 主题模式可以实现 Pub/Sub 发布与订阅模式和 Routing 路由模式的功能,只是 Topic 在配置routing key 的时候可以使用通配符,显得更加灵活。

关注点:

  • connection :于RabbitMQ服务器建立连接
  • channel:和服务器建立通道,并且绑定交换机
  • queue:队列,队列需要自己创建,或者进行指定,队列使用名字进行区分。如果创建队列时,该队列已存在,则直接返回。
  • exchange:创建交换机,让交换机和队列进行绑定,模式为BuiltinExchangeType.TOPIC
  • routingKey:路由键,交换机和队列绑定的钥匙,生产者发送消息需要发送于路由键匹配的字符
		String exchangeName = "testTopic";
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.TOPIC,
                false, false, null);

        // 6. 创建队列
        String queueFanout1 = "testQueueTopic1";
        String queueFanout2 = "testQueueTopic2";
        channel.queueDeclare(queueFanout1, true, false, false, null);
        channel.queueDeclare(queueFanout2, true, false, false, null);

        /*
            String queue, String exchange, String routingKey
                routingKey 路由键,绑定规则
                如果路由规则为Fanout,则为空字符串
         */
        // 7. 绑定队列和交换机
        // * 代表零个或一个
        // # 代表零个或多个
        // 根据字符串匹配进行路由分发
        channel.queueBind(queueFanout1, exchangeName, "#.error");
        channel.queueBind(queueFanout1, exchangeName, "order.*");
        channel.queueBind(queueFanout2, exchangeName, "#.#");

        // 8. 发送消息
        String body = "这是最好的时代";
        channel.basicPublish(exchangeName, "goods", null, body.getBytes());

        // 9. 关闭
//        channel.close();
//        connection.close();
Consumer consumer = new DefaultConsumer(channel) {
            // 回调方法
            /*
                String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body
                    1. consumerTag 消息标识
                    2. envelope 获取一些信息 :路由, 交换机
                    3. properties 配置信息
                    4. 真实数据
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("consumerTag:" + consumerTag);
                System.out.println("Exchange:" + envelope.getExchange());
                System.out.println("RoutingKey:" + envelope.getRoutingKey());
                System.out.println("properties:" + properties);
                System.out.println("body: " + new String(body));
            }
        };
        channel.basicConsume("testQueueTopic1", true, consumer);

总体步骤:

  1. 双方与服务器建立连接
  2. 获取channel
  3. 创建交换机 exchange,指定名称、交换机类型和其他参数
  4. 生产者和消费者 使用或者创建队列,对队列的参数进行定义
  5. 将队列和交换机进行绑定,指定正则表达式
  6. 生产者生产消息,并且指定路由键
  7. 多个消费者通过异步回调的方式来处理消息

5.6 总结

RabbitMQ-个人笔记_第16张图片

你可能感兴趣的:(#,MQ,java-rabbitmq,rabbitmq,java)