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

不常用,不做介绍

相关推荐
上海锟联科技7 小时前
DAS一体化光模块
分布式·分布式光纤传感·ofdr·光频域反射·das
Java 码农8 小时前
RabbitMQ集群部署方案及配置指南01
linux·服务器·rabbitmq
Overt0p8 小时前
抽奖系统(6)
java·spring boot·redis·设计模式·rabbitmq·状态模式
Java 码农8 小时前
RabbitMQ集群部署方案及配置指南04
分布式·rabbitmq
独自破碎E8 小时前
在RabbitMQ中,怎么确保消息不会丢失?
分布式·rabbitmq
Java 码农9 小时前
RabbitMQ集群部署方案及配置指南02
分布式·rabbitmq
虫小宝9 小时前
京东返利app分布式追踪系统:基于SkyWalking的全链路问题定位
分布式·skywalking
星图易码9 小时前
星图云开发者平台功能详解 | IoT物联网平台:工业设备全链路智能管控中枢
分布式·物联网·低代码·低代码平台
王五周八9 小时前
基于 Redis+Redisson 实现分布式高可用编码生成器
数据库·redis·分布式
成为你的宁宁9 小时前
【Zabbix 分布式监控实战指南(附图文教程):Server/Proxy/Agent 三者关系解析 + Proxy 部署、Agent 接入及取数路径验证】
分布式·zabbix