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

不常用,不做介绍

相关推荐
跟着珅聪学java7 小时前
在电商系统中,如何确保库存扣减的原子性
分布式
JH30739 小时前
Redisson 看门狗机制:让分布式锁“活”下去的智能保镖
分布式
一点 内容10 小时前
深入理解分布式共识算法 Raft:从原理到实践
分布式·区块链·共识算法
8Qi810 小时前
分布式锁-redission
java·redis·分布式·redisson
11 小时前
鸿蒙——分布式数据库
数据库·分布式
jiayong2311 小时前
微服务架构与 Spring 生态完全指南
kafka·rabbitmq·rocketmq
Hui Baby12 小时前
分布式多阶段入参参数获取
分布式
阿拉斯攀登14 小时前
Spring Cloud Alibaba 生态中 RocketMQ 最佳实践
分布式·微服务·rocketmq·springcloud·cloudalibaba
无锡布里渊14 小时前
感温光纤 DTS 系统 vs 感温电缆 对比分析报告
分布式·实时监测·分布式光纤测温·线型感温火灾监测·感温电缆
g323086314 小时前
分布式框架seata AT模式源码分析
java·数据库·分布式