深入学习 RabbitMQ 消息中间件(一)

  • 什么是消息队列、为什么要使用消息队列以及常见的产品?

消息队列是应用程序之间通讯方法,无需即时返回且耗时的操作进行异步处理从而提高系统的吞吐量,可以实现程序之间的解耦合,常见的产品有 ActiveMQ、ZeroMQ、RabbitMQ、RocketMQ和Kafka。

  • 安装配置RabbitMQ

安装rabbitmq前要先安装erlang:百度云下载erlang20.3和rabbitmq3.7.14 提取码:05z6

默认项目下一步就好,安装完erlang添加bin路径到path,例:%ERLANG_HOME%\bin

安装完rabbitmq后,打开cmd cd 安装路径/sbin(D:\DevInstall\rabbitmq3.7.14\rabbitmq_server-3.7.14\sbin) 

启动rabbitmq输入:rabbitmq-plugins.bat enable rabbitmq_management,不出意外的话会出现下面的情况

D:\DevInstall\rabbitmq3.7.14\rabbitmq_server-3.7.14\sbin>rabbitmq-plugins.bat enable rabbitmq_management
Enabling plugins on node rabbit@LAPTOP-IAO5L5AN:
rabbitmq_management
The following plugins have been configured:
  rabbitmq_management
  rabbitmq_management_agent
  rabbitmq_web_dispatch
Applying plugin configuration to rabbit@LAPTOP-IAO5L5AN...
Plugin configuration unchanged.

这时访问rabbitmq管理页面这访问不到的(http://localhost:15672/#/),这时候输入:rabbitmq-server start 时可以访问

D:\DevInstall\rabbitmq3.7.14\rabbitmq_server-3.7.14\sbin>rabbitmq-server start
"WARNING: Using RABBITMQ_ADVANCED_CONFIG_FILE: C:\Users\GONGBIN\AppData\Roaming\RabbitMQ\advanced.config"

  ##  ##
  ##  ##      RabbitMQ 3.7.14. Copyright (C) 2007-2019 Pivotal Software, Inc.
  ##########  Licensed under the MPL.  See https://www.rabbitmq.com/
  ######  ##
  ##########  Logs: C:/Users/GONGBIN/AppData/Roaming/RabbitMQ/log/RABBIT~1.LOG
                    C:/Users/GONGBIN/AppData/Roaming/RabbitMQ/log/rabbit@LAPTOP-IAO5L5AN_upgrade.log

              Starting broker...
 completed with 3 plugins.

进入之后简单的配置添加一个用户,以及管理 Virtual Hosts 虚拟主机

深入学习 RabbitMQ 消息中间件(一)_第1张图片

到此创建了一个用户,点击 Virtual Hosts 创建虚拟主机

深入学习 RabbitMQ 消息中间件(一)_第2张图片

Users 默认是 guest 更改成刚创建的用户

深入学习 RabbitMQ 消息中间件(一)_第3张图片

点击 Set Permission 到此完成

  • 搭建 RabbitMQ 入门工程,编写简单的生产者与消费者
        
            com.rabbitmq
            amqp-client
        

实现一个生产者和消费者都需要6步,其中公共的4个步骤为:1. 创建连接工厂设置服务参数、2. 创建连接、3. 创建频道、4. 声明队列,生产者的5、6步为:5. 发送消息、6.关闭资源,消息者为:5. 创建消费者、6.监听队列,消息者需要监听消息队列不需要关闭资源。

定义公共工具

public class ConnectionUtil {

    public static Connection getConnection() throws IOException, TimeoutException {
        // 1. 创建连接工厂设置服务参数
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setVirtualHost("itea");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("dxayga");
        connectionFactory.setPassword("dxayga");
        // 2. 创建连接
        Connection connection = connectionFactory.newConnection();
        return connection;
    }

}

生产者代码

public class Producer {

    final static String QUEUE_NAME = "simple_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 1. 创建连接工厂设置服务参数
        // 2. 创建连接
        Connection connection = ConnectionUtil.getConnection();
        // 3. 创建频道
        Channel channel = connection.createChannel();
        // 4. 声明队列
        /**
         * 参数1:队列名
         * 参数2:是否持久化(会一直保存在服务器上)
         * 参数3:是否独占本连接
         * 参数4:是否在不使用时删除
         * 参数5:其他参数
         */
        channel.queueDeclare(QUEUE_NAME,true,false,true,null);
        // 5. 发送消息
        /**
         * 参数1:交换机名,没有指定为为默认交换机
         * 参数2:路由的key(简单模式下可以使用队列名)
         * 参数3:消息的其他属性
         * 参数4:消息内容
         */
        channel.basicPublish("",QUEUE_NAME,null,"Hello World!".getBytes());
        // 6.关闭资源
        channel.close();
        connection.close();
    }

}

消费者代码

public class Consumer {

    public static void main(String[] args) throws IOException, TimeoutException {
        // 1. 创建连接工厂设置服务参数
        // 2. 创建连接
        Connection connection = ConnectionUtil.getConnection();
        // 3. 创建频道
        Channel channel = connection.createChannel();
        // 4. 声明队列
        /**
         * 参数1:队列名
         * 参数2:是否持久化(会一直保存在服务器上)
         * 参数3:是否独占本连接
         * 参数4:是否在不使用时删除
         * 参数5:其他参数
         */
        channel.queueDeclare(Producer.QUEUE_NAME,true,false,true,null);
        // 5. 创建消费者
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("路由的key:"+envelope.getRoutingKey());
                System.out.println("交换机为:"+envelope.getExchange());
                System.out.println("消息id为:"+envelope.getDeliveryTag());
                System.out.println("接收的消息为:"+new String(body,"utf-8"));
            }
        };
        // 6.监听队列
        /**
         * 参数1:队列名
         * 参数2:是否自动确认;true为收到消息自动从队列删除,false为手动确认,
         * 参数3:消息者
         */
        channel.basicConsume(Producer.QUEUE_NAME,true,defaultConsumer);

    }

}

启动生产者和消费者,成功的话返回:

路由的key:simple_queue
交换机为:
消息id为:1
接收的消息为:Hello World!

而没有消费的的信息也可以在服务端看到

深入学习 RabbitMQ 消息中间件(一)_第4张图片

  • 工作队列模式 

点击查看这篇文章 

消费者与消费者是竞争的关系,一条消息只能被一个消费者消费,不存在一条消息被两个消费者消费。

  • 订阅模式类型说明

订阅模式与前面的两种模式比较∶多了一个角色Exchange交换机,接收生产者发送的消息并决定如何投递消息到其绑定的队列,消息的投递决定于交换机的类型。

交换机类型:广播( fanout )、定向( direct )、通配符( topic )

交换机只做消息转发,自身不存储数据。

  • 发布与订阅模式

一个消息可以被多个消费者接收,一个消费者对应一个队列,该队列只能被一个消费者监听。使用了订阅模式中交换机类型为∶广播。

生产者
public class Producer {

    final static String FANOUT_EXCHANGE = "fanout_exchange";

    final static String FANOUT_QUEUE_1 = "fanout_queue_1";

    final static String FANOUT_QUEUE_2 = "fanout_queue_2";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 1. 创建连接
        Connection connection = ConnectionUtil.getConnection();
        // 2. 创建频道
        Channel channel = connection.createChannel();
        // 3. 声明交换机 参数1:交换机名称 参数2:交换机类型
        channel.exchangeDeclare(FANOUT_EXCHANGE,BuiltinExchangeType.FANOUT);
        // 4. 声明队列
        /**
         * 参数1:队列名
         * 参数2:是否持久化(会一直保存在服务器上)
         * 参数3:是否独占本连接
         * 参数4:是否在不使用时删除
         * 参数5:其他参数
         */
        channel.queueDeclare(FANOUT_QUEUE_1,true,false,true,null);
        channel.queueDeclare(FANOUT_QUEUE_2,true,false,true,null);
        // 5. 队列绑定到交换机 参数1:队列名称、参数2:交换机名称、参数3:路由key
        channel.queueBind(FANOUT_QUEUE_1,FANOUT_EXCHANGE,"");
        channel.queueBind(FANOUT_QUEUE_2,FANOUT_EXCHANGE,"");
        // 6. 发送消息
        /**
         * 参数1:交换机名,没有指定为为默认交换机
         * 参数2:路由的key(简单模式下可以使用队列名)
         * 参数3:消息的其他属性
         * 参数4:消息内容
         */
        for (int i = 0; i < 10; i ++) {
            String message = "Hello World!"+i;
            channel.basicPublish(FANOUT_EXCHANGE,"",null,message.getBytes());
            System.out.println("消息发送:"+message);
        }
        // 7.关闭资源
        channel.close();
        connection.close();
    }

}
消费者1
public class Consumer1 {

    public static void main(String[] args) throws IOException, TimeoutException {
        // 1. 创建连接
        Connection connection = ConnectionUtil.getConnection();
        // 2. 创建频道
        Channel channel = connection.createChannel();
        // 3. 声明交换机 参数1:交换机名称 参数2:交换机类型
        channel.exchangeDeclare(Producer.FANOUT_EXCHANGE,BuiltinExchangeType.FANOUT);
        // 4. 声明队列
        /**
         * 参数1:队列名称
         * 参数2:是否持久化(会一直保存在服务器上)
         * 参数3:是否独占本连接
         * 参数4:是否在不使用时删除
         * 参数5:其他参数
         */
        channel.queueDeclare(Producer.FANOUT_QUEUE_1,true,false,true,null);
        // 5. 队列绑定到交换机 参数1:队列名称、参数2:交换机名称、参数3:路由key
        channel.queueBind(Producer.FANOUT_QUEUE_1, Producer.FANOUT_EXCHANGE,"");
        // 6. 创建消费者
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("路由的key:"+envelope.getRoutingKey());
                System.out.println("交换机为:"+envelope.getExchange());
                System.out.println("消息id为:"+envelope.getDeliveryTag());
                System.out.println("接收的消息为:"+new String(body,"utf-8"));
            }
        };
        // 7.监听队列
        /**
         * 参数1:队列名称
         * 参数2:是否自动确认;true为收到消息自动从队列删除,false为手动确认,
         * 参数3:消费者
         */
        channel.basicConsume(Producer.FANOUT_QUEUE_1,true,defaultConsumer);

    }

}

在服务也可以看到两个队列已经绑定到交换机

深入学习 RabbitMQ 消息中间件(一)_第5张图片

  • 路由模式(Routing)

Routing(路由模式)要求队列绑定到交换机的时候指定路由key,消费发送时候需要携带路由key,只有消费的路由key与队列路由key完全一致才能让该队列接收到消息。相比上面的代码更改了定向模式和加入了Routing Key

    public static void main(String[] args) throws IOException, TimeoutException {
        ...
        // 3. 声明交换机 参数1:交换机名称 参数2:交换机类型
        channel.exchangeDeclare(DIRECT_EXCHANGE,BuiltinExchangeType.DIRECT);
        ...
        // 5. 队列绑定到交换机 参数1:队列名称、参数2:交换机名称、参数3:路由key
        channel.queueBind(DIRECT_QUEUE_1,DIRECT_EXCHANGE,"insert");
        channel.queueBind(DIRECT_QUEUE_2,DIRECT_EXCHANGE,"update");
        // 6. 发送消息
        /**
         */
        String message1 = "Hello World!";
        channel.basicPublish(DIRECT_EXCHANGE,"insert",null,message1.getBytes());
        System.out.println("消息发送:"+message1);

        String message2 = "Hello World!";
        channel.basicPublish(DIRECT_EXCHANGE,"update",null,message2.getBytes());
        System.out.println("消息发送:"+message2);
        ...
    }
    //消费者
    public static void main(String[] args) throws IOException, TimeoutException {
        ...
        // 3. 声明交换机 参数1:交换机名称 参数2:交换机类型
        channel.exchangeDeclare(Producer.DIRECT_EXCHANGE,BuiltinExchangeType.DIRECT);
        ...
        // 5. 队列绑定到交换机 参数1:队列名称、参数2:交换机名称、参数3:路由key
        channel.queueBind(Producer.DIRECT_QUEUE_1, Producer.DIRECT_EXCHANGE,"insert");
        ...
    }
    //消费者
    public static void main(String[] args) throws IOException, TimeoutException {
        ...
        // 3. 声明交换机 参数1:交换机名称 参数2:交换机类型
        channel.exchangeDeclare(Producer.DIRECT_EXCHANGE,BuiltinExchangeType.DIRECT);
        ...
        // 5. 队列绑定到交换机 参数1:队列名称、参数2:交换机名称、参数3:路由key
        channel.queueBind(Producer.DIRECT_QUEUE_1, Producer.DIRECT_EXCHANGE,"update");
        ...
    }
  • 通配符模式(Topic)

Topics通配符模式∶可以根据路由key将消息传递到对应路由key的队列,队列绑定到交换机的路由key可以有多个,通配符模式中路由key可以使用 * 和 # 使用了通配符模式之后对于路由Key的配置更加灵活。

举例:定义了两个路由key:item.insert.abc 和 item.insert

当使用路由key为:item.* 可以匹配到一个(item.insert),当使用item.# 可以匹配到两个(item.insert、item.insert.abc)

        ...
        // 3. 声明交换机 参数1:交换机名称 参数2:交换机类型
        channel.exchangeDeclare(DIRECT_EXCHANGE,BuiltinExchangeType.TOPIC);
        而第3、4步可以省略
        // 6. 发送消息
        /**
         * 参数1:交换机名,没有指定为为默认交换机
         * 参数2:路由的key(简单模式下可以使用队列名)
         * 参数3:消息的其他属性
         * 参数4:消息内容
         */
        String message1 = "Hello World!";
        channel.basicPublish(DIRECT_EXCHANGE,"item.insert",null,message1.getBytes());
        System.out.println("消息发送:"+message1);

        String message2 = "Hello World!";
        channel.basicPublish(DIRECT_EXCHANGE,"item.insert.abc",null,message2.getBytes());
        System.out.println("消息发送:"+message2);

        String message3 = "Hello World!";
        channel.basicPublish(DIRECT_EXCHANGE,"item.delete",null,message3.getBytes());
        System.out.println("消息发送:"+message3);
        ...
//消费者
    public static void main(String[] args) throws IOException, TimeoutException {
        ...
        // 3. 声明交换机 参数1:交换机名称 参数2:交换机类型
        channel.exchangeDeclare(Producer.DIRECT_EXCHANGE,BuiltinExchangeType.TOPIC);
        ...
        // 5. 队列绑定到交换机 参数1:队列名称、参数2:交换机名称、参数3:路由key
        channel.queueBind(Producer.DIRECT_QUEUE_1, Producer.DIRECT_EXCHANGE,"item.*");
        channel.queueBind(Producer.DIRECT_QUEUE_1, Producer.DIRECT_EXCHANGE,"item.delete");
        ...
    }

 

 

public static void main(String[] args) throws IOException, TimeoutException {
        ...
        // 3. 声明交换机 参数1:交换机名称 参数2:交换机类型
        channel.exchangeDeclare(Producer.DIRECT_EXCHANGE,BuiltinExchangeType.TOPIC);
        ...
        // 5. 队列绑定到交换机 参数1:队列名称、参数2:交换机名称、参数3:路由key
        channel.queueBind(Producer.DIRECT_QUEUE_2, Producer.DIRECT_EXCHANGE,"item.#");
       ...
    }
  • 总结

不直接Exchange交换机(默认交换机) :

1. simple简单模式:一个生产者生产一个消息到一个队列被一个消费者接收。

2. work工作队列模式:生产者发送消息到一个队列中,然后可以被多个消费者监听该队列,一个消息只能被一个消费者接收,消费者之间是竞争关系。

使用Exchange交换机;订阅模式(交换机:广播fanout,定向direct. 通配符topic ):

1.发布与订阅模式:使用了fanout广播类型的交换机,可以将一个消息发送到所有绑定了该交换机的队列。

2.路由模式:使佣了direct定向类型的交换机,消费会携带路由key,交换机根据消息的路由key与队列的路由key进行对比,一致的话那么该队列可以接收到消息。

3.通配符模式:使用了topic通配符类型的交换机,消费会携带路由key(*, #) , 交换机根据消息的路由key与队列的路由key进行对比,匹配的话那么该队列可以接收到消息。

你可能感兴趣的:(消息中间件)