RabbitMq入门

1.介绍

消息队列主要能实现三种功能:

1.服务解耦

2.流量削峰

3.异步调用

AMQP协议:高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计,兼容jms

JMS:消息服务的标准或者是规范

2.Rabbitmq基本原理和概念

地址:RabbitMQ: One broker to queue them all | RabbitMQ

基于AMQP协议实现的消息队列,它是一种应用程序之间的通信方法

原理:

producer

消息生产者,通过信道channel发送到rabbitmq

connection

连接对象

producer和broker,broker和consumer之间的连接采用TCP,connection连接对象创建信道进行通信

channel

信道,消息传递数据的通道

broker

可以认为是mq

消息队列服务的进程,此进程包括两个部分:exchange交换机和queue队列

exchange交换机

rabbitmq非常重要的组件,接受来自于生产者的消息,推送消息到队列中

queue队列

rabbitmq内内部中的数据结构队列,是消息真正存储的地方。

consumer

消费者,消费方客户端。通过信道channel接受mq消息,并进行相关处理。

发送消息:消费者通过connection和broker建立TCP连接,connection建立channel信道,生产者通过信道,将消息发送给broker,由exchange将消息进行转发到队列中去

消费消息:消费者通过connection和broker 建立tcp连接,connection建立信道,消费者监听指定的queue队列,当有消息到达时候,通过channel推送给消费者。

routingkey

路由键,是用于将消息路由到指定队列的关键字

exchange交换机四种类型:

rabbitmq核心思想,生产者永远不会将消息直接发送到队列,而是发送到交换机,交换机将消息发送到队列

fanout

广播模式,交换机将消息发送给所有的队列,发送相同的消息。

direct

路由模式,根据对应routing key发送消息

topic

动态路由模式。通过规则定义routing key 使交换机动态的多样性选择队列

headers

请求头模式,像请求头一样附带头部数据,交换机根据头部数据匹配对应的队列

3.安装

下面直接展示docker容器安装

bash 复制代码
#在线安装
docker pull rabbitmq:management
#使用官方定义的端口号启动
docker run -d -it  --rm --name rabbit -p 15672:15672 -p 5672:5672 rabbitmq:3.13-management
java 复制代码
# 查询rabbitmq容器ID
docker ps 
# 进入容器
docker exec -it 容器ID /bin/bash 
# 开启管控台插件
rabbitmq-plugins enable rabbitmq_management

访问15762端口,能否出现登录界面,如果出现则表示启动成功。

4.应用

简单应用

一个生产者,默认交换机,一个队列一个消费者

Java连接rabbitmq

java 复制代码
	
	public Connection getConnection () {
	Connection connection = null;
	// 获取链接
	
	
	String HOST=EidConfigurer.get("rabbitmq.host");
	String VIRTUALHOST=EidConfigurer.get("rabbitmq.virhost");
	String USERNAME=EidConfigurer.get("rabbitmq.username");
	String PASSWORD=EidConfigurer.get("rabbitmq.password");
	String PORT=EidConfigurer.get("rabbitmq.port");
	
	ConnectionFactory connectionFactory = new ConnectionFactory();
	connectionFactory.setHost(HOST);
	connectionFactory.setVirtualHost(VIRTUALHOST);
	connectionFactory.setUsername(USERNAME);
	connectionFactory.setPassword(PASSWORD);
	// rabbitmq 的服务器地址 15672:给rabbitmq management web程序,插件 web端客户端管理工具
	//5672:给rabbitmq-server 服务器的
	connectionFactory.setPort(Integer.valueOf(PORT));
	// 建立链接
	try {
	    Connection newConnection = connectionFactory.newConnection();
	    connection = newConnection;
	} catch (IOException e) {
	    System.out.println("连接失败!!!");
	    e.printStackTrace();
	} catch (TimeoutException e) {
	    System.out.println("连接超时!!!");
	    e.printStackTrace();
	}
	return connection;
	}
	

发送消息

java 复制代码
Connection connection = RabbitConfig.getConnection();
    Channel channel = connection.createChannel();
      String msg = "测试发布";
    /**
     * 参数1:指定exchange,使用""。默认的exchange
     * 参数2:指定路由的规则,使用具体的队列名称。exchange为""时,消息直接发送到队列中
     * 参数3:制动传递的消息携带的properties
     * 参数4:指定传递的消息,byte[]类型
     */
    channel.basicPublish("", "test1", null,msg.getBytes());
    channel.close();
    connection.close();

消费消息

java 复制代码
    Connection connection = RabbitConfig.getConnection();
    Channel channel = connection.createChannel();
    /**
         * 参数1:queue 指定队列名称
         * 参数2:durable 是否开启持久化(true)
         * 参数3:exclusive 是否排外
         * 参数4:autoDelete 如果这个队列没有其他消费者在消费,队列自动删除
         * 参数5:arguments 指定队列携带的信息
         *
         */    
channel.queueDeclare("test1",true,false,false,null);
    DefaultConsumer consumer = new DefaultConsumer(channel){
        @Override
        public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
           //业务代码
        }
    };
    /**
         * 参数1:queue 指定消费哪个队列
         * 参数1:deliverCallback 指定是否ACK(true:收到消息会立即告诉rabbitmq,false:手动告诉)
         * 参数1:cancelCallback 指定消费回调
         *
         */
    channel.basicConsume("test1",true,consumer);
    channel.close();
    connection.close();

整合springboot

需要引入依赖

java 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

配置文件

java 复制代码
spring:
  rabbitmq:
    host: 39.107.111.12
    virtual-host: /
    username: username
    password: username
    port: 5672

创建配置

java 复制代码
@Configuration
public class DirectExchangeConfig {
	@Bean
 	public DirectExchange directExchange(){
		DirectExchange directExchange=new DirectExchange("direct");
 		return directExchange;
 	}
	
	@Bean
    public Queue directQueue1() {
       Queue queue=new Queue("directqueue1");
       return queue;
    }
 	
 	@Bean
    public Queue directQueue2() {
       Queue queue=new Queue("directqueue2");
       return queue;
    }
	
 	//3个binding将交换机和相应队列连起来
 	@Bean
 	public Binding bindingorange(){
 		Binding binding=BindingBuilder.bind(directQueue1()).to(directExchange()).with("orange");
 		return binding;
 	}
 	
 	@Bean
 	public Binding bindingblack(){
 		Binding binding=BindingBuilder.bind(directQueue2()).to(directExchange()).with("black");
 		return binding;
 	}
 	
 	@Bean
 	public Binding bindinggreen(){
 		Binding binding=BindingBuilder.bind(directQueue2()).to(directExchange()).with("green");
 		return binding;
 	}
 	
 	
 	
}

接收消息

java 复制代码
@Component
public class Consumer {
  
   @RabbitListener(queues = "directqueue1")
    public void getMassage(Object massage){
        业务代码
    }

}

5.ack机制

消息一旦被消费者接收,队列中的消息就会被删除。

自动ack:消息一旦被接收,消费者自动发送ack,默认此模式。

消费者高效处理消息的情况下才会用此模式

手动ack:消息接收后,不会发送ack,需要代码调用

ack调用代码:

channel.basicAck(long deliveryTag,boolean multiple);//用于肯定确认

deliveryTag:参数标识,

multiple:批处理

channel.basicNack(long deliveryTag, boolean false, boolean requeue); //用于否定确认

deliveryTag:参数标识,

multiple:批处理

requeue:true重回队列,false丢弃,或者进入死信队列

channel.basicReject(long deliveryTag, boolean requeue); //用于否定确认 不进行批处理

一般消息不用批处理,所以这里不展开讨论

6.持久化

消息持久化

channel.basicPublish("交换机", "队列名", MessageProperties.PERSISTENT_TEXT_PLAIN, "消息");

队列持久化

// 让队列持久化

boolean durable = true; // false 不持久化 true 持久化

// 声明

channel.queueDeclare("队列名", durable, false, false, null);

7.分发机制

轮训分发

轮训分发是 RabbitMQ 的默认消息分发策略。它将消息按顺序均匀地分发给每个消费者,而不考虑每个消费者的处理能力和处理速度。这种策略的优点是实现简单,能够在消费者处理能力均衡的情况下提供较好的性能。然而,在消费者处理能力不均衡的情况下,轮训分发可能导致一些消费者过载,而另一些消费者闲置,降低系统的整体效率

公平分化

公平分发是一种更为智能的消息分发策略,它根据每个消费者的处理能力和处理速度来分配消息,确保处理能力强的消费者能够处理更多的消息,从而提高整体系统的效率。公平分发通过配置 prefetch 值来实现,该值定义了 RabbitMQ 在接收消费者的 ack(确认)之前可以发送给该消费者的最大消息数。通过设置较小的 prefetch 值,可以确保每个消费者在处理完当前消息之前不会收到更多的消息,从而实现消息的公平分发

不公平分发

信道设置:channel.basicQos(1);

预取值分发

和不公平分发类似

channel.basicQos(X);X>1.

8.confirm机制

将信道设置为confirm模式,所有在该信道上面发布消息都将会为消息指派一个消息id,且唯一

生产者将消息发送给broker,broker收到消息后就会发送一个确认confirm给生产者,消息达到队列

如果消息进行了持久化设置,那么消息写入磁盘后会发送一个confirm给生产者。

发布策略确认:

rabbitmq是没有设置发布确认机制,如果想要开启,需要在信道channel上面开启

java 复制代码
channel.confirmSelect();

单个发布确认:以同步方式发送。优点可靠,缺点很慢,如果数据量很大,不建议。

批量发布确认:同样以同步方式发送,增加了批处理的规则。优点,时间比单个发布确认耗时少,缺点是当某个消息未送达到broker,无法准确知晓是哪个消息。

异步发布确认:比上面两个来说都复杂,但性能和效率提升不少。

原理:

发送者每次给broker发送消息,会默认给每个消息带上一个唯一的id标识

发送者在方法中写一个异步确认监听器addConfirmListener,用于监听broker给生产者发送消息

发送者根据addConfirmListener(ack,nack)来处理业务

java 复制代码
 ConcurrentSkipListMap<Long, String> outstandingConfirms = new ConcurrentSkipListMap<>();

    /**  编写回调监听器,因为: 消息发送出错要立刻进行监听所以,所以创建在发送消息之前; **/
    /** ack 确认收到消息的一个回调 1.消息序列号 2.true 批量确认接受小于等于当前序列号的数据 false 确认当前序列号消息 */
    ConfirmCallback ackCallback = (sequenceNumber, multiple) -> {
        if (multiple) {
            System.out.println("消息成功接收:"+sequenceNumber);
            // ConcurrentNavigableMap方法()返回的是小于|等于 K 的集合, true:小于等于 false:返回小于该序列号的数据集合;
            ConcurrentNavigableMap<Long, String> confirmed = outstandingConfirms.headMap(sequenceNumber, true);
            // 清除该部分确认消息 confirmed 里保存的都是,MQ 已经接收的消息;
            // 遍历 confirmed K, 根据 K 删除 outstandingConfirms 的值...
            // outstandingConfirms 里面保存的都是,MQ 还未确认的消息...
        }else{
            //只清除当前序列号的消息
            outstandingConfirms.remove(sequenceNumber);
        }
    };
    // nack 消息失败执行{} 可以写,消息失败需要执行的代码...
    ConfirmCallback nackCallback = (sequenceNumber, multiple) -> {
        // 这里就输出一下为被确认的消息...
        String message = outstandingConfirms.get(sequenceNumber);
        System.out.println("发布的消息"+message+"未被确认,序列号"+sequenceNumber);
    };
    // 发生者 等待MQ回调消息确认的 监听器, 本次程序值监听 ack成功的消息;
    channel.addConfirmListener(ackCallback, null);
  • 发送者,只需要关注消息的发送,MQ 会将每条消息发布情况,回调给发生者
  • 发送者每次发消息前,将消息存储在缓存中,成功了就删除,失败了就重新发送

9.交换机

前面简单介绍过交换机,增加补充一点:topic

动态路由模式:*表示一个单词,#表示任意数量的单词,0个或者多个

发布订阅模式:fanout

最常见的场景:群聊

定义一个生产者,交换机,生产者不停往交换机发消息,交换机提前与一个或者多个队列进行绑定

生产者发布消息

java 复制代码
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8"));

消费者1接收代码

java 复制代码
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");

        /** 生成一个临时的队列 队列的名称是随机的 当消费者断开和该队列的连接时 队列自动删除  */
        String queueName = channel.queueDeclare().getQueue();
        // 绑定: 把该临时队列绑定我们的 exchange 其中 routingkey(也称之为 binding key)为空字符串: Fanout模式 routingkey 没作用!
        channel.queueBind(queueName, EXCHANGE_NAME, "");

        // 发送回调
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println("wsm 发布的最新消息:"+message);
        };
        // 消费者监听消息
        channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {});

消费者2和消费者1代码一样。

路由模式:direct

绑定交换机需要指定routing key,一个队列可以绑定一个或者多个routingkey

生产者

java 复制代码
  channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        /** 发送消息 **/
        channel.basicPublish(EXCHANGE_NAME, "key1", null, "key1发送的消息".getBytes("UTF-8"));
        channel.basicPublish(EXCHANGE_NAME, "key2", null, "key2发送的消息".getBytes("UTF-8"));

消费者1

java 复制代码
 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        /** 生成一个临时的队列 队列的名称是随机的 当消费者断开和该队列的连接时 队列自动删除  */
        String queueName = channel.queueDeclare().getQueue();
        // 绑定: 把该临时队列绑定我们的 exchange


##############################区别代码###########
        // 参数二 设置该队列和交换和绑定的 routingkey
        channel.queueBind(queueName, EXCHANGE_NAME, "key1");
##############################区别代码###########
        // 发送回调
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println("最新的消息是:"+message);
        };
        // 消费者监听消息
        channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {});

消费者2

java 复制代码
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        /** 生成一个临时的队列 队列的名称是随机的 当消费者断开和该队列的连接时 队列自动删除  */
        String queueName = channel.queueDeclare().getQueue();
        // 绑定: 把该临时队列绑定我们的 exchange


####################################################################
        // 参数二 设置该队列和交换和绑定的 routingkey
        channel.queueBind(queueName, EXCHANGE_NAME, "key1");
        channel.queueBind(queueName, EXCHANGE_NAME, "key2");
####################################################################
        // 发送回调
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println("最新的消息是:"+message);
        };
        // 消费者监听消息
        channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {});

这样消费者2就能接收key1 和key2 的消息

主题模式(也叫动态路由):topic

动态路由模式 可以根据一些特殊的符合匹配多种 Routingkey 的匹配

#:匹配0个或者多个:sensormsg.#=sensormsg.com sensormsg.com.aa sensormsg.c.s.v

*:匹配一个:sensormsg.*=sensormsg.com sensormsg.a sensormsg.b

头部模式:headers

不常用,不做介绍

相关推荐
spiker_7 小时前
RabbitMQ 常见使用模式详解
分布式·rabbitmq
不能再留遗憾了7 小时前
RabbitMQ 高级特性——持久化
分布式·rabbitmq·ruby
成为大佬先秃头7 小时前
解决RabbitMQ设置TTL过期后不进入死信队列
分布式·中间件·rabbitmq·java-rabbitmq
七夜zippoe9 小时前
分布式系统实战经验
java·分布式
nomi-糯米10 小时前
Fisco Bcos 2.11.0配置console控制台2.10.0及部署调用智能合约
分布式·网络安全·区块链·智能合约·分布式账本
喜欢猪猪10 小时前
Kafka是如何保证数据的安全性、可靠性和分区的
分布式·kafka
芊言芊语10 小时前
分布式消息服务Kafka版的详细解析和配置方式
分布式·kafka
Alluxio10 小时前
选择Alluxio来解决AI模型训练场景数据访问的五大理由
大数据·人工智能·分布式·ai·语言模型
武子康11 小时前
大数据-133 - ClickHouse 基础概述 全面了解
java·大数据·分布式·clickhouse·flink·spark
.生产的驴11 小时前
SpringBoot 消息队列RabbitMQ 消费者确认机制 失败重试机制
java·spring boot·分布式·后端·rabbitmq·java-rabbitmq