Rabbitmq消息中间件初步学习——第二节七种模型分析

本文详细介绍了Rabbitmq的七种消息模型,包括Hello World、Work Queues、Publish/Subscribe、Routing、Topics、RPC以及Publisher Confirms。在Work Queues中,讨论了多消费者循环消费消息的机制;在Publish/Subscribe模型中,解释了如何通过Exchange将消息广播给多个消费者;Routing模型则展示了如何利用RoutingKey将消息定向到特定队列;Topics模型通过Topic类型实现更灵活的消息路由。此外,文中还探讨了RPC在Rabbitmq中的实现,以及Publisher Confirms机制,确保消息被Rabbitmq接受。
摘要由CSDN通过智能技术生成

前言

相对于其他消息中间件,Rabbitmq高可靠性应该算是不错的特点了吧

Rabbitmq应用场景
1.RabbitMQ的消息应当尽可能的小,并且只用来处理实时且要高可靠性的消息。
2.消费者和生产者的能力尽量对等,否则消息堆积会严重影响RabbitMQ的性能。
3.集群部署,使用热备,保证消息的可靠性。

本文参照官网Rabbitmq Tutorial内容对Rabbitmq进行特性分析
在这里插入图片描述
目前一共七种模型,前五种常用,后两种也是刚刚接触,一边学习一边整理吧,肯定有遗漏的地方,望见谅!

一、 Hello World!

学习任何一门语言,都离不开 “Hello World!”,就是一个入门案例!Rabbitmq的 "Hello World!"指的是一种直连的方式,何为直连呢?这就不得不提Rabbitmq的一些概念了!
在这里插入图片描述

Rabbitmq中在目前初步学习的阶段,最需要了解的概念就是生产者、消费者、虚拟主机、交换机、队列,当然还有集群,但这节主要是前几种!
如上图所示:

  1. 一个RabbitmqServer中是有多个虚拟主机的,每个虚拟主机中又存在多个交换机和队列
  2. 平时工作开发是以虚拟主机为基础进行的,交换机和队列是AMQP协议中的概念,引用百度百科的解释

在服务器中,三个主要功能模块连接成一个处理链完成预期的功能:
“exchange”接收发布应用程序发送的消息,并根据一定的规则将这些消息路由到“消息队列”。
“message queue”存储消息,直到这些消息被消费者安全处理完为止。
“binding”定义了exchange和message queue之间的关联,提供路由规则。

按我的理解就是,exchange对应的是生产者,queue对应的是消费者
3. 每个虚拟机都有一个默认的交换机

大概的概念就是这样,主要的就要知道,我们是基于虚拟主机进行开发的,生产者对应的是exchange,消费者对应的是queue,这样看一下Hello World模型,这个模型也是最简单的模型,在这个模型中是没有交换机的概念的,看一下官网的流程图
在这里插入图片描述
Producer和Consumer直接和queue相连接,也就是说我们Producer发送消息直接发送到queue中,其实这是一个错觉,因为Rabbitmq是基于AMQP协议开发的,怎么可能没有exchange呢,那么为什么上面的模型中没有exchange呢?

其实这是因为每个虚拟主机都有一个默认的exchange,在这个模型中,message是默认发送到默认的exchange(AMQP default)中的,然后路由到queue中,最后被Consumer接收!
这种模型也类似于一种直连的方式

下面是一个简单的案例!

1.POM坐标

		<dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.10.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
        </dependency>

2.工具类

简单写了一个获取连接的工具类

/**
 * @program: rabbitmq-demo
 * @description: Rabbitmq工具类
 **/
public class RabbitmqConfig {
   

    /**
     * 创建连接mq的连接工厂对象
     */
    private static ConnectionFactory connectionFactory = new ConnectionFactory();
    static {
   
        // 连接rabbitmq的主机
        connectionFactory.setHost("127.0.0.1");
        // 设置连接端口号
        connectionFactory.setPort(5672);
        /**
         * 设置连接虚拟主机,虚拟主机:类似于nacos中命名空间概念
         * 举个例子:
         *      搭建一个Rabbitmq消息中间件后,此时有多组服务进行通信,原则上讲
         *      每一组服务之间是相互隔离的,也就是说,只允许A组内部服务之间进行相互通信,
         *      不允许A组和B组相互通信,这样就可以将它们划分在不同的虚拟主机中,完成这个功能
         */
        connectionFactory.setVirtualHost("/test");
        /**
         * 设置用户名
         */
        connectionFactory.setUsername("admin");
        // 设置密码
        connectionFactory.setPassword("admin");
    }

    /**
     * 获取连接对象
     * @return
     * @throws IOException
     * @throws TimeoutException
     */
    public static Connection getConnection() throws IOException, TimeoutException {
   
        return connectionFactory.newConnection();
    }

    /**
     * 获取连接中的通道
     * @return
     * @throws IOException
     * @throws TimeoutException
     */
    public static Channel getChannel() throws IOException, TimeoutException {
   
        return getConnection().createChannel();
    }
}

3.Producer

/**
 * @program: rabbitmq-demo
 * @description: 消息生产者
 **/
public class Producer {
   

    @Test
    public void publishing() {
   
        try {
   
            // 通过工具类获取Channel
            Channel channel = RabbitmqConfig.getChannel();
            /**
             *  声明队列
             *    queue: 队列名称 ,若队列已存在,但是参数不一致,则报错!
             *    durable:队列持久化,若为true,则rabbitmq重启后仍然存在
             *    exclusive:排它性,true:只可以此Connection连接这个queue,当当前Connection关闭时,这个queue会自动删除!
             *              并且在存在过程中,其他的Connection不可以连接这个queue,原理嘛,就是加个排它锁
             *    autoDelete:自动删除,true:当queue不再使用后自动删除;不再使用:即queue中无数据,没有其它connection连接此queue
             *    arguments:额外参数
             */
            channel.queueDeclare("hello",false,false,false,null);
            /**
             * 发布消息
             *  exchange: exchange名称
             *  routingKey:路由名称,不一定是某个对应的queue名称,当然肯定是需要匹配到某个queue的
             *  props:额外配置
             *  body:消息体
             */
            channel.basicPublish("","hello",null,"hello rabbitmq".getBytes());
            // 关闭channel
            channel.close();
            // 关闭Connection
            channel.getConnection().close();
        }catch (Exception e){
   
            e.printStackTrace();
        }
    }

    /**
     * 测试队列持久化及消息持久化
     */
    @Test
    public void testDurable() throws IOException, TimeoutException {
   
        Channel channel = RabbitmqConfig.getChannel();
        // 声明队列持久化
        channel.queueDeclare("durableQueue",true,false,false,null);
        // 声明消息持久化
        channel.basicPublish("","durableQueue", MessageProperties.PERSISTENT_TEXT_PLAIN,"持久化消息".getBytes());
    }
    /**
     * 测试消息过期时间
     */
    @Test
    public void testTTL() throws IOException, TimeoutException {
   
        Map<String, Object> arguments = new HashMap<String, Object>();
        // 队列内消息十秒过期
        arguments.put("x-message-ttl", 10000);
        // 队列十秒没有消费者访问该队列则自动删除
        arguments.put("x-expires", 20000);
        Channel channel = RabbitmqConfig.getChannel();
        // 声明队列内消息的过期时间
        channel.queueDeclare("ttlQueue",false,false,false,arguments);
        channel.basicPublish("","ttlQueue",null,"10秒后消息过期".getBytes());
        // 设置单个消息过期时间
        AMQP.BasicProperties.Builder properties = new AMQP.BasicProperties.Builder().expiration(20000+"");
        channel.basicPublish("","durableQueue",properties.build(),"20秒后消息过期".getBytes());
    }

    /**
     * x-max-length:用于指定队列的长度,如果不指定,可以认为是无限长,例如指定队列的长度是4,当超过4条消息,前面的消息将被删除,给后面的消息腾位,类似于栈的结构,
     * 当设置了x-max-priority后,优先级高的排在前面,所以基本上排除的话就是排除优先级高的这些
     * x-max-length-bytes: 用于指定队列存储消息的占用空间大小,当达到最大值是会删除之前的数据腾出空间
     * x-max-priority: 设置消息的优先级,优先级值越大,越被提前消费。
     */
    @Test
    public void testMax() throws IOException, TimeoutException {
   
        Map<String, Object> arguments = new HashMap<String, Object>();
        arguments.put("x-max-length", 4);
        arguments.put("x-max-length-bytes", 1024);
        arguments.put("x-max-priority", 5);
        Channel channel = RabbitmqConfig.getChannel();
        declareDead(arguments,channel);
        channel.queueDeclare("maxQueue",false,false,false,arguments);
        for (int i=1; i<=6;++i){
   
            AMQP.BasicProperties.Builder properties = new AMQP.BasicProperties.Builder().priority(i);
            channel.basicPublish("","maxQueue",properties.build(),("第"+i+"条消息,优先级是:"+i).getBytes());
        }
    }

    /**
     * 声明Dead-exchange、dead-queue
     */
    public void declareDead(Map<String, Object> arguments,Channel channel) throws IOException {
   
        channel.exchangeDeclare("EXCHANGE_DEAD",BuiltinExchangeType.DIRECT);
        channel.queueDeclare("QUEUE_DEAD",false,false,false,null);
        // 若不指定exchange、queue,则默认使用AMQP default默认exchange,默认使用queue的名字作为routingkey
        channel.queueBind("QUEUE_DEAD","EXCHANGE_DEAD","routing_dead");
        arguments.put("x-dead-letter-exchange", "EXCHANGE_DEAD");
        arguments.put("x-dead-letter-routing-key", "routing_dead");
    }

    /**
     * 测试排它性exclusive
     */
    public static void main(String[] args) {
   
        Connection connection = null;
        try {
   
            connection = RabbitmqConfig.getConnection();
            Channel channel = connection.createChannel();
            // 声明一个排它queue
            channel.queueDeclare("testExclusive",false,true,false,null);
            // 关闭当前channel,以证明queue的排它性和channel没关系
            channel.close();
            testExclusiveA(connection);
            testExclusiveB(connection);
            // 在一个服务项目中是没有办法测试排它性的,因为本项目中所有对rabbitmq的连接,在Rabbitmq看来都是一个Connection,所以是不会触发排它锁的,如果需要测试,可以再创建一个项目进行测试
            testExclusiveC();
            // 等待十秒,这区间可以看一下Rabbitmq ui界面,此时rabbitmq ui是可以看到此queue的
            Thread.sleep(10000);
            // 关闭连接,关闭连接后,testExclusive这个queue也会随之删除
            connection.close();
        } catch (IOException e) {
   
            e.printStackTrace();
        } catch (TimeoutException e) {
   
            e.printStackTrace();
        } catch (InterruptedException e) {
   
            e.printStackTrace();
        }
    }

    /**
     * 测试发送消息
     * @param connection
     * @throws IOException
     * @throws TimeoutException
     */
    public static void testExclusiveA(Connection connection) throws IOException, TimeoutException {
   
        Channel channel = connection.createChannel();
        channel.basicPublish("","testExclusive",null,"hello testExclusiveA".getBytes());
        System.out.println("testExclusiveA");
        channel.close();
    }

    /**
     * 测试发送消息
     * @param connection
     * @throws IOException
     * @throws TimeoutException
     */
    public static void testExclusiveB(Connection connection) throws IOException, TimeoutException {
   
        Channel channel = connection.createChannel();
        channel.basicPublish("","testExclusive",null,"hello testExclusiveB".getBytes());
        System.out.println("testExclusiveB");
        channel.close();
    }

    /**
     * 这里重新建了一个ConnectionFactory,并且获取一个新的Connection,但是事实证明,也是可以发送消息到testExclusive的,因为在同一个项目中建立的连接,
     * 在Rabbitmq看来是一个Connection
     * @throws IOException
     * @throws TimeoutException
     */
    public static void testExclusiveC() throws IOException, TimeoutException {
   
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/test");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.basicPublish("","testExclusive",null,"hello testExclusiveC".getBytes());
        System.out.println("testExclusiveC");
        channel.close();
        connection.close();
    }
}

4.Consumer

/**
 * @program: rabbitmq-demo
 * @description: 消费者-接受消息
 **/
public class Consumer {
   
    public static void main(String[] args) {
   
        try {
   
            Channel channel = RabbitmqConfig.getChannel();
            /**
             * 这里解释一下为什么需要在订阅queue之前,提前queueDeclare一下,这个是为了防止provider还没有启动,而consumer先启动了,
             * 如果不提前声明的话,那么在rabbitmq中是不存在hello的,那么是没办法订阅消息的,反馈到程序中就是报错!
             * 但是声明时,也要特别注意参数不要弄错
             */
            channel.queueDeclare("hello",false,false,false,null);
            /**
             * 消费消息
             * queue:队列名称
             * autoAck:消息确认机制;true:自动确认消息,false:手动确认消息
             * callback:Consumer接口,
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值