RabbitMQ-基础操作

工作流程

生产者通过connection向服务器(broker)发送消息,由服务器内部的交换机路由到对应的队列,消费者通过connection订阅队列来获取消息

  1. connection连接:是客户端和RabbitMQ服务器之间的一个TCP连接,负责传输客户端和服务器之间的所有数据和控制信息

  2. channel通道/信道:是connection之上的一个抽象层,一个连接可以有多个chennel,生产者(消费者)向服务器发送(接收)消息实际上就是通过channel来发送(接收)消息。作用是让少量TCP连接,可以通过多路复用来并发处理多个请求

  3. virtual host虚拟主机:为消息队列提供的一种逻辑上的隔离机制,一个服务器中可以有多个虚拟主机,每个虚拟主机都有自己的交换机和队列。当不同用户使用同一个RabbitMQ服务器时,每个用户可以创建自己的虚拟主机(类似于MySql的database)

  4. queue 队列:RabbitMQ内部的对象,用于存储消息。一个消费者可以订阅多个队列,一个队列也可被多个消费者订阅(多对多)

  5. exchange 交换机:接收生产者的消息,并按照一定规则路由到一个或多个queue中

RabbitMQ交换机的四种类型

  1. Fanout:广播,将消息发送给所有绑定到交换机的队列(发布/订阅模式)
  2. direct:定向,将消息发送给符合指定routingKey的队列(Routing模式)
  3. topic:通配符,将消息发送给符合routing pattern的队列(Topics模式)
  4. headers:根据发送的消息内容中的headers属性进行匹配

RoutingKey:路由键,生产者将消息发给交换机时,指定一个字符串(一系列由.分隔的单词),用来告诉交换机这条消息路由到哪里

BindingKey:交换机与队列在绑定时会指定一个BindingKey,如果消息的RoutingKey与BindingKey匹配,则将消息路由到对应的队列

java 复制代码
//建立连接
ConnectionFactory connectionFactory=new ConnectionFactory();
connectionFactory.setHost(HOST);
connectionFactory.setPort(PORT);
connectionFactory.setUsername(USERNAME);
connectionFactory.setPassword(PASSWORD);
connectionFactory.setVirtualHost(VHOST);
Connection connection= connectionFactory.newConnection();
//开启信道
Channel channel=connection.createChannel();
//声明交换机
//声明队列
//绑定交换机
//发送消息
channel.basicPublish(交换机名称,路由键,消息属性,消息体);
//消费消息
DefaultConsumer consumer=new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //消息处理逻辑
            }
};
channel.basicConsume(队列名称,是否自动确认,consumer);
//断开连接

工作模式介绍

简单模式

一个生产者,一个消费者,消息只能被消费一次,也称为点对点模式

声明队列,如果队列已经存在则直接返回:

String queue (队列名称)

boolean durable (是否持久化)

将数据保存到磁盘,RabbitMQ重启后,消息不丢失

boolean exclusive (是否排他)

仅对首次声明它的连接可见,该连接断开时,自动删除该队列

boolean autoDelete (是否自动删除)

所有消费者都断开连接时,自动删除

Map<String, Object> arguments (其他结构化参数)

工作队列

一个生产者,对应多个消费者,消息分配给不同的消费者,消息不会重复消费

发布/订阅

一个生产者,多个消费者。交换机将消息复制多份,每个消费者接收相同的消息。适用于实时通知或者广播消息

声明交换机

String exchange (交换机名称)

String type (交换机类型)

BuiltinExchangeType下的枚举类型

boolean durable (是否持久化)

boolean autoDelete (是否自动删除)

如果最后一个与其绑定的队列解除绑定时,这个交换机是否自动删除

boolean internal(是否为内部交换机)

客户端不能直接将消息发送给内部交换机,一般用于交换机之间的路由

Map<String, Object> arguments (其他结构化参数)

绑定交换机

String queue: 要绑定的队列名称

String exchange: 要绑定的交换机名称

String routingKey: 路由键,当消息携带的路由键与这个参数匹配,才转发到指定队列

路由模式

根据routingKey,将数据筛选后发送到指定的消息队列

通配符模式

在路由模式的基础上,增加通配符的功能,更加灵活

* :任意一个字符

#:任意多个字符

RPC模式

  1. 客户端发送消息到一个指定的队列,并在消息属性中设置replyTo字段,用于指定回调队列,来接收服务器的响应;设置correlationID属性,用于给这段消息设置标识
  2. 服务器从队列中接收到请求,处理之后,将其与correlationID字段一起发送到指定的回调队列
  3. 客户端从回调队列中接收处理后的消息,根据correlationID判断是否是要接收的消息

客户端

java 复制代码
//建立连接,开启信道
//消息唯一标识
String correlationId= String.valueOf(UUID.randomUUID());
//声明队列,一个请求队列,一个响应队列
//发送消息
AMQP.BasicProperties basicProperties=new AMQP.BasicProperties()
	.builder()
	.correlationId(correlationId)//设置消息的唯一标识
	.replyTo(RPC_RESPONSE)//指定消息处理后发送的队列
	.build();
channel.basicPublish("",RPC_REQUEST,basicProperties,body);
//接收消息
//使用阻塞队列,来存储响应信息
final ArrayBlockingQueue<String> response=new ArrayBlockingQueue<>(1);
DefaultConsumer defaultConsumer=new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
if(correlationId.equals(properties.getCorrelationId())){
	//如果消息标识正确,则放入阻塞队列
	response.offer(new String(body));
	channel.basicAck(envelope.getDeliveryTag(),false);
	}
}
};
channel.basicConsume(RPC_RESPONSE,false,defaultConsumer);
//如果阻塞队列中没有消息,则一直阻塞
String result=response.take();
//关闭连接

服务器

java 复制代码
//建立连接,开启信道
//同一时间,MQ只向消费者发送一条消息,这条消息处理完,才发送另一条
channel.basicQos(1);
//处理消息
DefaultConsumer defaultConsumer=new DefaultConsumer(channel){
	@Override
	public void handleDelivery(String consumerTag, Envelope envelope, 	AMQP.BasicProperties properties, byte[] body) throws IOException {
	//从请求队列中接收并处理消息
	//将处理后的消息,发送到响应队列
	AMQP.BasicProperties basicProperties=new 			AMQP.BasicProperties().builder().correlationId(properties.getCorrelationId()).build();
channel.basicPublish("",RPC_RESPONSE,basicProperties,body);
	//向服务器发送应答
	channel.basicAck(envelope.getDeliveryTag(),false);
	}
};
channel.basicConsume(RPC_REQUEST,false,defaultConsumer);
//关闭连接

发布确认模式

发布的每一个消息都会有一个唯一的ID,当RabbitMQ服务器成功接收消息,回向生产者发送一个ACK应答(携带消息的唯一ID),解决的是生产者向服务器发送消息时,出现的消息丢失问题

三种确认方式

  1. 单独确认模式

    java 复制代码
    //建立连接,开启信道
    //设置信道为Confirm模式
    channel.confirmSelect();
    //声明交换机
    //声明队列
    //发送消息
    for(int i=0;i<MESSAGE_COUNT;i++){
    channel.basicPublish("",PUBLISHER_CONFIRM_QUEUE1,null,body);
    //消息确认
    channel.waitForConfirmsOrDie(5000);
    }
  2. 批量确认

    java 复制代码
    //建立连接,开启信道
    //设置信道为Confirm模式
    channel.confirmSelect();
    //声明交换机,队列
    //发送消息,并进行确认
    int batchSize=100;
    int counts=0;
    for(int i=0;i<MESSAGE_COUNT;i++){
    channel.basicPublish("",PUBLISHER_CONFIRM_QUEUE2,null,body);
    counts++;
    if(counts==batchSize){
    //消息数量达到batchSize时进行批量确认
    channel.waitForConfirmsOrDie(5000);
    counts=0;
    }
    }
    //解决发送消息数与批量确认数无法整除时,造成的没有确认问题
    if(counts>0){
    channel.waitForConfirmsOrDie(5000);
    }
  3. 异步确认

    1. 生产者注册确认监听器,发送消息后无需等待服务器返回确认信息,继续发送后续消息
    2. 服务器返回确认信息时,客户端自动调用监听器的回调方法,在回调方法中处理消息的确认结果
    java 复制代码
    //建立连接,开启信道
    //设置信道为Confirm模式
    channel.confirmSelect();
    //声明交换机,队列
    //集合中存储着未确认的消息ID
    SortedSet<Long> confirm= Collections.synchronizedSortedSet(new TreeSet<>());
    //注册监听器
    channel.addConfirmListener(new ConfirmListener() {
       //消息被成功确认
    @Override
    public void handleAck(long deliveryTag, boolean multiple) throws IOException {
    if(multiple){
    confirm.headSet(deliveryTag+1).clear();
    }else{
    confirm.remove(deliveryTag+1);
    }
    }
       //消息未被成功确认
    @Override
    public void handleNack(long deliveryTag, boolean multiple) throws IOException {
    if(multiple){
    confirm.headSet(deliveryTag+1).clear();
    }else{
    confirm.remove(deliveryTag+1);
    }
    //消息重发
    }
    });
    //发送消息,并进行确认
    for(int i=0;i<MESSAGE_COUNT;i++){
    long id=channel.getNextPublishSeqNo();
    channel.basicPublish("",PUBLISHER_CONFIRM_QUEUE3,null,body);
    confirm.add(id);
    }
    while(!confirm.isEmpty()){
    Thread.sleep(10);
    }

Spring整合RabbitMQ

  1. 引入依赖

  2. 建立连接,创建信道

    yml 复制代码
    spring:
      application:
        name: rabbitmq-spring-boot
      rabbitmq:
        host: 
        port: 
        username: 
        password: 
        virtual-host: 
  3. 声明队列

    java 复制代码
    @Bean
        public Queue directQueue(){
          return QueueBuilder.durable(name).build();
        }
  4. 声明交换机,direct/fanout/topic方法类似

    java 复制代码
    @Bean
        public TopicExchange topicExchange(){
            return ExchangeBuilder.topicExchange(name).durable(true).build();
        }
  5. 声明绑定关系,因为BeanName不一定为参数名,所以使用@Qualifier注解指定名称

    java 复制代码
    @Bean
        public Binding topicQueueBinding3(@Qualifier("beanName")TopicExchange topicExchange , @Qualifier("beanName") Queue queue){
            return BindingBuilder.bind(queue).to(topicExchange).with(bindingKey);
        }
  6. 发送消息,rabbitTemplate对象使用@Autowired注入

    java 复制代码
    rabbitTemplate.convertAndSend(EXCHANGE_NAME,routingKey,body);
  7. 接收消息

    复制代码
    @RabbitListener(queues = QUEUE_NAME)
        public void queueListener2(Message message,Channel channel){
            //具体操作
        }

    Message/Channel对象的内部构造:

相关推荐
柒和远方2 小时前
从审计日志到可下载证据包:事务型 Outbox、租约 fencing 与保留水位
前端·后端·架构
卓怡学长2 小时前
w266基于spring boot + vue 圣地延安美食乐享系统
java·数据库·vue.js·spring boot·spring·intellij-idea
C++、Java和Python的菜鸟2 小时前
第7章 Java高级技术
java·开发语言
程序员佳佳2 小时前
模型网关灰度不是调百分比:把放量、观测和回滚做成账本
java·数据库·人工智能·redis·gpt·aigc·embedding
geovindu3 小时前
CSharp: Prototype Pattern
开发语言·后端·设计模式·.net·原型模式·创建型模式
Listen·Rain4 小时前
Springboot整合Ollama实现调用本地多模型
java·spring boot·后端
霸道流氓气质4 小时前
Java 工程师 AI 智能体(Agent)完整学习路线
java·人工智能·学习
aaPIXa6224 小时前
C++模板元编程:编译期计算Fibonacci数列
java·开发语言·c++
SunnyDays10114 小时前
Java 编辑 PDF:从增删改查到加密保护
java·修改 pdf·编辑 pdf