RabbitMq学习笔记(四)工作模式及实现

本文详细介绍了RabbitMQ的简单模式、workqueues模式、发布订阅模式和路由模式,以及消息的自动确认机制。在简单模式中,一个生产者向一个消息队列发送消息,一个消费者接收。workqueues模式适用于处理耗时任务,允许多个消费者共同消费队列消息。发布订阅模式中,消息会被广播给所有订阅的消费者。路由模式通过交换机和路由键将消息路由到特定队列。消息的自动确认机制保证了消息在正确处理后才会被删除。此外,通过关闭自动确认,可以实现快的消费者多消费,慢的消费者少消费。

目录

简单模式

模型

生产者代码

消费者代码

结果

work queues模式

模型

生产者代码

消费者代码

结果

消息的自动确认机制

发布订阅模式

模型

生产者代码

消费者代码

结果

路由模式

Routing 之订阅模型-Direct(直连)

模型

生产者代码

消费者代码

结果

主题模式(Topic)

生产者代码

消费者代码

结果


引入依赖

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

简单模式

模型

一个生产者,一个消费者

  • P:生产者,也就是要发送消息的程序
  • C:消费者:消息的接受者,会一直等待消息到来
  • queue:消息队列,图中红色部分。类似一个邮箱,可以缓存消息;生产者向其中投递消息,消费者从其中取出消息

生产者代码

//创建连接工厂
        //创建连接mq的连接工厂对象
        ConnectionFactory  connectionFactory = new ConnectionFactory();

        //设置连接rabbitmq主机
        connectionFactory.setHost("10.30.126.252");
        //设置端口号
        connectionFactory.setPort(5672);
        //设置访问虚拟主机的用户名和密码
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        //设置连接那个虚拟主机
        connectionFactory.setVirtualHost("/");
        //获取连接对象
        Connection connection = connectionFactory.newConnection();
        //获取连接中通道
        Channel channel = connection.createChannel();
        //通道绑定对应消息队列
        //参数1:  队列名称 如果队列不存在自动创建
        //参数2:  用来定义队列特性是否要持久化 true 持久化队列   false 不持久化
        //参数3:  exclusive 是否独占队列  true 独占队列   false  不独占
        //参数4:  autoDelete: 是否在消费完成后自动删除队列  true 自动删除  false 不自动删除
        //参数5:  额外附加参数
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        String msg = "My first message";
        //发布消息
        //参数1: 交换机名称(简单模式下为空) 参数2:队列名称  参数3:传递息额外设置  参数4:消息的具体内容
        channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
        channel.close();
        connection.close();

消费者代码

public class Consumer {
    
    public static void main(String[] args) throws IOException, TimeoutException {

        //创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("10.30.126.252");
        factory.setPort(5672);
        factory.setVirtualHost("/");
        factory.setUsername("admin");
        factory.setPassword("admin");
        Connection connection = factory.newConnection();

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

        //通道绑定队列:与生产端一致
        channel.queueDeclare(Producer.QUEUE_NAME, true, false, false, null);

        //获取消息
        //参数1: 消费那个队列的消息 队列名称
        //参数2: 开始消息的自动确认机制[只要消费就从队列删除消息]
        //参数3: 消费时的回调接口
        channel.basicConsume(Producer.QUEUE_NAME, true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("取出消息:===>" + new String(body));
            }
        });
    }
}

结果

work queues模式

模型

Work queues,也被称为(Task queues),任务模型。当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。此时就可以使用work 模型:让多个消费者绑定到一个队列,共同消费队列中的消息。队列中的消息一旦消费,就会消失,因此任务是不会被重复执行的。

  • P:生产者:任务的发布者
  • C1:消费者-1,领取任务并且完成任务,假设完成速度较慢
  • C2:消费者-2:领取任务并完成任务,假设完成速度快

生产者代码

public class Producer {
    
    public static final String QUEUE_NAME = "my_second_queue";

    public static void main(String[] args) throws IOException, TimeoutException {

        //创建连接工厂
        //创建连接mq的连接工厂对象
        Connection connection = RabbitMQUtils.getConnection();
        //获取连接中通道
        Channel channel = connection.createChannel();
        //通道绑定对应消息队列
        //参数1:  队列名称 如果队列不存在自动创建
        //参数2:  用来定义队列特性是否要持久化 true 持久化队列   false 不持久化
        //参数3:  exclusive 是否独占队列  true 独占队列   false  不独占
        //参数4:  autoDelete: 是否在消费完成后自动删除队列  true 自动删除  false 不自动删除
        //参数5:  额外附加参数
        channel.queueDeclare(QUEUE_NAME, true, false, false, null);
        String msg = "My second message";
        //发布消息
        //参数1: 交换机名称(简单模式下为空) 参数2:队列名称  参数3:传递息额外设置  参数4:消息的具体内容
        for (int i = 0; i < 20; i++) {
            channel.basicPublish("", QUEUE_NAME, null, (i+1+"--"+msg).getBytes());
        }
        channel.close();
        connection.close();
    }
}

消费者代码

消费者1

public class Consumer1 {

    public static void main(String[] args) throws IOException, TimeoutException {

        //创建连接工厂
        Connection connection = RabbitMQUtils.getConnection();

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

        //通道绑定队列:与生产端一致
        channel.queueDeclare(Producer.QUEUE_NAME, true, false, false, null);

        //获取消息
        //参数1: 消费那个队列的消息 队列名称
        //参数2: 开始消息的自动确认机制[只要消费就从队列删除消息]
        //参数3: 消费时的回调接口
        channel.basicConsume(Producer.QUEUE_NAME, true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者1取出消息:===>" + new String(body));
            }
        });
    }
}

消费者2

public class Consumer2 {

    public static void main(String[] args) throws IOException, TimeoutException {

        //创建连接工厂
        Connection connection = RabbitMQUtils.getConnection();

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

        //通道绑定队列:与生产端一致
        channel.queueDeclare(Producer.QUEUE_NAME, true, false, false, null);

        //获取消息
        //参数1: 消费那个队列的消息 队列名称
        //参数2: 开始消息的自动确认机制[只要消费就从队列删除消息]
        //参数3: 消费时的回调接口
        channel.basicConsume(Producer.QUEUE_NAME, true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("消费者2取出消息:===>" + new String(body));
            }
        });
    }
}

结果

消费者1

消费者2

总结:默认情况下,RabbitMQ将按顺序将每个消息发送给下一个使用者。平均而言,每个消费者都会收到相同数量的消息。这种分发消息的方式称为循环。

如何实现快的多消费 慢的少消费???

消息的自动确认机制

如果希望实现上面需求即快的多消费,慢的少消费,则需要设置rabbitmq的自动确认机制。

ack定义:如果在处理消息的过程中,消费者的服务器在处理消息的时候出现异常,那么可能这条正在处理的消息就没有完成消息消费,数据就会丢失。为了确保数据不会丢失,RabbitMQ支持消息确定-ACK。

 ACK机制是消费者从RabbitMQ收到消息并处理完成后,反馈给RabbitMQ,RabbitMQ收到反馈后才将此消息从队列中删除。 如果一个消费者在处理消息出现了网络不稳定、服务器异常等现象,那么就不会有ACK反馈,RabbitMQ会认为这个消息没有正常消费,会将消息重新放入队列中。如果在集群的情况下,RabbitMQ会立即将这个消息推送给这个在线的其他消费者。这种机制保证了在消费者服务端故障的时候,不丢失任何消息和任务。消息永远不会从RabbitMQ中删除,只有当消费者正确发送ACK反馈,RabbitMQ确认收到后,消息才会从RabbitMQ服务器的数据中删除。
    消息的ACK确认机制默认是打开的。

 模型图

 

要解决上面问题需要解决下面问题

  • 消费者不能一次拿到多个消息(默认情况下,消息是一次性投递的channel中的)

对消费者作为改变

//会告诉RabbitMQ不要同时给一个消费者推送多于N个消息,即一旦有N个消息还没有ack,则该consumer将block掉!!!!!
channel.basicQos(1);


//关闭自动确认消费,执行手动确认
channel.basicConsume(Producer.QUEUE_NAME, true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("消费者1取出消息:===>" + new String(body));
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        });
    }

上面的basicQos告诉mq不要一次性分派多个消息,每次一个一个的给,如果消费者某个消息没有ack(确认),自己进行阻塞。基于这点我们需要确认机制改为“手动确认”,即保每次只分发一个的同时,消费快的先“ack”,再取下一个。

结果如下

  • 疑问1,如果不设置channel.basicQos(1),直接设置手动会怎么样,带着这样的疑问我们尝试一下,

可以看到依然是平均分派。

  • 疑问2:如果设置channel.basicQos(1),但是采用自动确认会怎么样

可以看到情况同上,虽然channel每次从队列中获取一个消息,但是一旦获取之后自动ack,则消费者1 和消费者2 有同样的竞争机会。

发布订阅模式

模型

fanout又称广播

在广播模式下,消息发送流程是这样的:

  • 可以有多个消费者

  • 每个消费者有自己的queue(队列)

  • 每个队列都要绑定到Exchange(交换机)

  • 生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定。

  • 交换机把消息发送给绑定过的所有队列

  • 队列的消费者都能拿到消息。实现一条消息被多个消费者消费

生产者代码

public class Producer {

    public static final String EXCHANGE_NAME = "fanoutExchange";
    public static final String EXCHANGE_TYPE = "fanout";

    public static void main(String[] args) throws IOException, TimeoutException {

        //创建连接工厂
        Connection connection = RabbitMQUtils.getConnection();
        //获取连接中通道
        Channel channel = connection.createChannel();
        //声明交换机
        //参数1: 交换机名称
        //参数2: 交换机类型(fanout广播模式)
        channel.exchangeDeclare(EXCHANGE_NAME, EXCHANGE_TYPE);

        String msg = "My third message----fanout";
        //发布消息
        //参数1: 交换机名称 参数2:队列名称  参数3:传递息额外设置  参数4:消息的具体内容
        channel.basicPublish(EXCHANGE_NAME, "", null, (msg).getBytes());
        channel.close();
        connection.close();
    }
}

消费者代码

消费者1

public class Consumer1 {

    public static void main(String[] args) throws IOException, TimeoutException {

        //创建连接工厂
        Connection connection = RabbitMQUtils.getConnection();

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

        //声明交换机
        //参数1: 交换机名称
        //参数2: 交换机类型(fanout广播模式)
        channel.exchangeDeclare(Producer.EXCHANGE_NAME,Producer.EXCHANGE_TYPE);

        //创建临时队列
        String queue = channel.queueDeclare().getQueue();

        //临时队列和交换机 [参数1:临时队列,参数2:交换机,参数3:路由]
        channel.queueBind(queue, Producer.EXCHANGE_NAME, "");

        //获取消息
        //参数1: 消费那个队列的消息 队列名称
        //参数2: 开始消息的自动确认机制[只要消费就从队列删除消息]
        //参数3: 消费时的回调接口
        channel.basicConsume(queue, true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者1取出消息:===>" + new String(body));
            }
        });
    }
}

消费者1生成的临时队列

消费者2

public class Consumer2 {

    public static void main(String[] args) throws IOException, TimeoutException {

        //创建连接工厂
        Connection connection = RabbitMQUtils.getConnection();

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

        //声明交换机
        //参数1: 交换机名称
        //参数2: 交换机类型(fanout广播模式)
        channel.exchangeDeclare(Producer.EXCHANGE_NAME, Producer.EXCHANGE_TYPE);

        //创建临时队列
        String queue = channel.queueDeclare().getQueue();

        //临时队列和交换机 [参数1:临时队列,参数2:交换机,参数3:路由]
        channel.queueBind(queue, Producer.EXCHANGE_NAME, "");

        //获取消息
        //参数1: 消费那个队列的消息 队列名称
        //参数2: 开始消息的自动确认机制[只要消费就从队列删除消息]
        //参数3: 消费时的回调接口
        channel.basicConsume(queue, true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者2取出消息:===>" + new String(body));
            }
        });
    }
}

消费者2创建的临时队列

结果

路由模式

Routing 之订阅模型-Direct(直连)

在Fanout模式中,一条消息,会被所有订阅的队列都消费。但是,在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。

在Direct模型下:

  • 队列与交换机的绑定,不能是任意绑定了,而是要指定一个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 的消息

生产者代码

public class Producer {

    public static final String EXCHANGE_NAME = "routeExchange";
    public static final String EXCHANGE_TYPE = "direct";

    public static void main(String[] args) throws IOException, TimeoutException {

        //创建连接工厂
        Connection connection = RabbitMQUtils.getConnection();
        //获取连接中通道
        Channel channel = connection.createChannel();
        //声明交换机
        //参数1: 交换机名称
        //参数2: 交换机类型(路由模式)
        channel.exchangeDeclare(EXCHANGE_NAME, EXCHANGE_TYPE);

        String msg = "My fourth message----route";

        //发布消息[参数1:交换机名字,参数2:路由名字,参数3:消息内容]
        String key = "error";
        channel.basicPublish(EXCHANGE_NAME, key, null, ("发送给指定路由" + key + "的消息").getBytes());
        channel.close();
        connection.close();
    }
}

消费者代码

消费者1

public class Consumer1 {

    public static void main(String[] args) throws IOException, TimeoutException {

        //创建连接工厂
        Connection connection = RabbitMQUtils.getConnection();

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

        //声明交换机
        //参数1: 交换机名称
        //参数2: 交换机类型(fanout广播模式)
        channel.exchangeDeclare(Producer.EXCHANGE_NAME,Producer.EXCHANGE_TYPE);

        //创建临时队列
        String queue = channel.queueDeclare().getQueue();

        //临时队列和交换机并设置路由 [参数1:临时队列,参数2:交换机,参数3:路由]
        channel.queueBind(queue, Producer.EXCHANGE_NAME, "info");
        channel.queueBind(queue, Producer.EXCHANGE_NAME, "error");
        channel.queueBind(queue, Producer.EXCHANGE_NAME, "warn");

        //获取消息
        //参数1: 消费那个队列的消息 队列名称
        //参数2: 开始消息的自动确认机制[只要消费就从队列删除消息]
        //参数3: 消费时的回调接口
        channel.basicConsume(queue, true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者1取出消息:===>" + new String(body));
            }
        });
    }
}

消费者2

public class Consumer2 {

    public static void main(String[] args) throws IOException, TimeoutException {

        //创建连接工厂
        Connection connection = RabbitMQUtils.getConnection();

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

        //声明交换机
        //参数1: 交换机名称
        //参数2: 交换机类型(route广播模式)
        channel.exchangeDeclare(Producer.EXCHANGE_NAME, Producer.EXCHANGE_TYPE);

        //创建临时队列
        String queue = channel.queueDeclare().getQueue();

        //临时队列和交换机并设置路由 [参数1:临时队列,参数2:交换机,参数3:路由]
        channel.queueBind(queue, Producer.EXCHANGE_NAME, "error");

        //获取消息
        //参数1: 消费那个队列的消息 队列名称
        //参数2: 开始消息的自动确认机制[只要消费就从队列删除消息]
        //参数3: 消费时的回调接口
        channel.basicConsume(queue, true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者2取出消息:===>" + new String(body));
            }
        });
    }
}

结果

主题模式(Topic)

Routing 之订阅模型-Topic

Topic 类型的 Exchange 与 Direct 相比,都是可以根据 RoutingKey 把消息路由到不同的队列

只不过Topic类型 Exchange 可以让队列在绑定Routing key 的时候使用通配符!

这种模型 Routingkey 一般都是由一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert

# 统配符
        * (star) can substitute for exactly one word.    匹配不多不少恰好1个词
        # (hash) can substitute for zero or more words.  匹配一个或多个词
# 如:
        audit.#    匹配audit.irs.corporate或者 audit.irs 等
    audit.*   只能匹配 audit.irs

生产者代码

public class Producer {

    public static final String EXCHANGE_NAME = "topicExchange";
    public static final String EXCHANGE_TYPE = "topic";

    public static void main(String[] args) throws IOException, TimeoutException {

        //创建连接工厂
        Connection connection = RabbitMQUtils.getConnection();
        //获取连接中通道
        Channel channel = connection.createChannel();
        //声明交换机
        //参数1: 交换机名称
        //参数2: 交换机类型(路由模式)
        channel.exchangeDeclare(EXCHANGE_NAME, EXCHANGE_TYPE);

        String msg = "My fifth message----topic";
        //发布消息[参数1:交换机名字,参数2:路由名字,参数3:消息内容]
        //使用动态路由
        String key = "user.save";
        channel.basicPublish(EXCHANGE_NAME, key, null, ("发送给指定路由" + key + "的消息:" + msg).getBytes());
        channel.close();
        connection.close();
    }
}

消费者代码

消费者1

public class Consumer1 {

    public static void main(String[] args) throws IOException, TimeoutException {

        //创建连接工厂
        Connection connection = RabbitMQUtils.getConnection();

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

        //声明交换机
        //参数1: 交换机名称
        //参数2: 交换机类型(fanout广播模式)
        channel.exchangeDeclare(Producer.EXCHANGE_NAME,Producer.EXCHANGE_TYPE);

        //创建临时队列
        String queue = channel.queueDeclare().getQueue();

        //临时队列和交换机并设置路由 [参数1:临时队列,参数2:交换机,参数3:路由]
        channel.queueBind(queue, Producer.EXCHANGE_NAME, "user.*");

        //获取消息
        //参数1: 消费那个队列的消息 队列名称
        //参数2: 开始消息的自动确认机制[只要消费就从队列删除消息]
        //参数3: 消费时的回调接口
        channel.basicConsume(queue, true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者1取出消息:===>" + new String(body));
            }
        });
    }
}

消费者2

public class Consumer2 {

    public static void main(String[] args) throws IOException, TimeoutException {

        //创建连接工厂
        Connection connection = RabbitMQUtils.getConnection();

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

        //声明交换机
        //参数1: 交换机名称
        //参数2: 交换机类型(route广播模式)
        channel.exchangeDeclare(Producer.EXCHANGE_NAME, Producer.EXCHANGE_TYPE);

        //创建临时队列
        String queue = channel.queueDeclare().getQueue();

        //临时队列和交换机并设置路由 [参数1:临时队列,参数2:交换机,参数3:路由]
        channel.queueBind(queue, Producer.EXCHANGE_NAME, "user.#");

        //获取消息
        //参数1: 消费那个队列的消息 队列名称
        //参数2: 开始消息的自动确认机制[只要消费就从队列删除消息]
        //参数3: 消费时的回调接口
        channel.basicConsume(queue, true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者2取出消息:===>" + new String(body));
            }
        });
    }
}

结果

如果发送

则只有消费者2能收到消息

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值