中间件-消息队列之RabbitMQ

243 阅读3分钟

2. 消息队列

不考虑消息确认机制与持久化的话,BlockingQueue即可实现一个简单的消息队列;

<img src="Java中间件交互.assets/Queue_Image.jpg" alt="img" style="zoom:50%;" />

其中包含三个角色是不可或缺的:

  • Producer,生产消息
  • Consumer,消费消息
  • Broker,存储消息

2.1 AMQP

与JMS不同,AMQP是一个线路层wire-level协议,高级消息队列协议AMQP全称为Advanced Message Queueing Protocol,关于AMQP的介绍可以参考RabbitMQ与AMQP协议详解这篇博客

发布/订阅模式,以及与观察者模式的区别:

2.1.1 RabbitMQ

在线模拟器[RabbitMQ Simulator][tryrabbitmq.com/]

Exchange类型

  • 当Exchange为Direct时

  • 当Exchange类型为Fanout时

  • 当Exchange类型为Topic时,主要有两个通配符:
    • #代表任何零个或多个word,对应的正则表达式为:.*
    • *匹配任何单个word,对应的正则表达式为:.

2.1.2 代码示例

启动RabbitMQ服务

rabbitmq-server.bat

RabbitMQ提供了Java客户端,其API详见:[Java Client API Guide][www.rabbitmq.com/api-guide.h…] ,[AMQP 0-9-1模型说明][www.rabbitmq.com/tutorials/a…]

rabbimq.client包中核心的类为:ConnectionChannel,其中Connection由工厂模式创建,Channel提供了操纵Exchange,Queue的API;

本示例的结构如下图所示:

1.首先创建Publisher:

public class Publisher {
    //定义队列名
    static String QUEUE_NAME_1 = "queue1";
    static String QUEUE_NAME_2 = "queue2";
    static String QUEUE_NAME_3 = "queue3";
    static String QUEUE_NAME_4 = "queue4";
    public static String[] queueList = {QUEUE_NAME_1,QUEUE_NAME_2,QUEUE_NAME_3,QUEUE_NAME_4};
    static String EXCHANGE_DIRECT = "direct";
    static String EXCHANGE_FANOUT = "fanout";
    static String EXCHANGE_TOPIC = "topic";
    public static void main(String[] args) {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = null;
        Channel channel = null;
        try {
            //1.创建连接和通道
            connection = factory.newConnection();
            channel = connection.createChannel();
            //声明三种类型的Exchange,两个boolean属性分别为durable,autoDelete
            channel.exchangeDeclare(EXCHANGE_DIRECT,BuiltinExchangeType.DIRECT,false,false,null);
            channel.exchangeDeclare(EXCHANGE_FANOUT,BuiltinExchangeType.FANOUT,false,false,null);
            channel.exchangeDeclare(EXCHANGE_TOPIC,BuiltinExchangeType.TOPIC,false,false,null);
            //声明队列,三个boolean属性分别为:durable,exclusive,autoDelete
            channel.queueDeclare(QUEUE_NAME_1, false, false, false, null);
            channel.queueDeclare(QUEUE_NAME_2, false, false, false, null);
            channel.queueDeclare(QUEUE_NAME_3, false, false, false, null);
            channel.queueDeclare(QUEUE_NAME_4, false, false, false, null);
            //绑定关系
            channel.queueBind(QUEUE_NAME_1,EXCHANGE_DIRECT,"direct");
            channel.queueBind(QUEUE_NAME_2,EXCHANGE_FANOUT,"");
            channel.queueBind(QUEUE_NAME_3,EXCHANGE_FANOUT,"");
            channel.queueBind(QUEUE_NAME_3,EXCHANGE_TOPIC,"#.tiger");
            channel.queueBind(QUEUE_NAME_4,EXCHANGE_TOPIC,"documentary.#");
            //3.发布消息
            Scanner scanner = new Scanner(System.in);
            while(true) {
                String command = scanner.nextLine();
                System.out.println("指令:" + command);
                if(command.indexOf("quit")!=-1){
                    break;
                }
                String[] subStr = command.split("\\|");
                if(subStr.length==3){
                    String exchange = subStr[0];
                    String routingKey = subStr[1];
                    String msg = subStr[2];
                    channel.basicPublish(exchange,routingKey , null, msg.getBytes());
                    System.out.println("provider send a msg: " + msg);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            //4.关闭连接
            if (channel != null) {
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (TimeoutException e) {
                    e.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

然后在终端输入指令:

2.创建消费者Consumer,创建机制与线程类似:

public class MsgConsumer {
    public static void main(String[] args) {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = null;
        Channel channel = null;
        try {
            // 1.创建连接和通道
            connection = factory.newConnection();
            channel = connection.createChannel();
            System.out.println(" **** keep alive,waiting for messages, and then deal them **** ");
            // 3.通过回调生成消费者
            for(int i=0;i<Publisher.queueList.length;i++){
                //4.消费消息
                channel.basicConsume(Publisher.queueList[i], true, "队列"+i,new DefaultConsumer(channel) {
                    @Override
                    public void handleDelivery(String consumerTag, Envelope envelope,
                                               com.rabbitmq.client.AMQP.BasicProperties properties, byte[] body) throws IOException {
                        //获取消息内容然后处理
                        String msg = new String(body, "UTF-8");
                        System.out.println(String.format("consumerTag:%s,get message :[%s]",consumerTag,msg));
                    }
                });
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
    }
}

消费者端输出如下:

image-20200813182435640

可以看到类型为Topic的交换机会根据相连的队列的绑定键bindingKey进行消息的路由;

  • 路由键为documentary.lion的消息仅被发送到绑定键为**documentary.#**的队列

  • 路由键为documentary.lion的消息仅被发送到绑定键为:documentary.#的队列,#.tiger的队列

2.1.3 可视化工具

RabbitMQ提供可视化插件,可以在sbin目录执行如下命令启动:

rabbitmq-plugins enable rabbitmq_management

打开:http://127.0.0.1:15672/#/即可访问

image-20200813182246172

2.1.4 思维导图总结

高级消息队列协议AMQP:

RabbitMQ:

参考资料: