RabbitMQ消息可靠性等机制详解(精细版三)

目录

[七 RabbitMQ的其他操作](#七 RabbitMQ的其他操作)

[7.1 消息的可靠性(发送可靠)](#7.1 消息的可靠性(发送可靠))

[7.1.1 confim机制(保证发送可靠)](#7.1.1 confim机制(保证发送可靠))

[7.1.2 Return机制(保证发送可靠)](#7.1.2 Return机制(保证发送可靠))

[7.1.3 编写配置文件](#7.1.3 编写配置文件)

[7.1.4 开启Confirm和Return](#7.1.4 开启Confirm和Return)

[7.2 手动Ack(保证接收可靠)](#7.2 手动Ack(保证接收可靠))

[7.2.1 添加配置文件](#7.2.1 添加配置文件)

[7.2.2 手动ack](#7.2.2 手动ack)

[7.3 避免消息重复消费](#7.3 避免消息重复消费)

[7.3.1 导入依赖](#7.3.1 导入依赖)

[7.3.2 编写配置文件](#7.3.2 编写配置文件)

[7.3.3 修改生产者](#7.3.3 修改生产者)

[7.3.4 修改消费者](#7.3.4 修改消费者)


官方文档RabbitMQ Documentation | RabbitMQ

MQ全称为Message Queue,消息队列是应用程序和应用程序之间的通信方法。

RabbitMQ是一个Erlang开发的AMQP(高级消息排队 协议)(英文全称:Advanced Message Queuing Protocol )的开源实现。-------------接上章

七 RabbitMQ的其他操作

7.1 消息的可靠性(发送可靠)

7.1.1 confim机制(保证发送可靠)

RabbitMQ的事务:事务可以保证消息100%传递,可以通过事务的回滚去记录日志,后面定时再次发送当前消息。事务的操作,效率太低,加了事务操作后,比平时的操作效率至少要慢100倍。

RabbitMQ除了事务,还提供了Confirm的确认机制,这个效率比事务高很多。

消息传递可靠性
7.1.2 Return机制(保证发送可靠)

Confirm只能保证消息到达exchange,无法保证消息可以被exchange分发到指定queue。

而且exchange是不能持久化消息的,queue是可以持久化消息。

采用Return机制来监听消息是否从exchange送到了指定的queue中

消息传递可靠性

在消息发送方项目上加入下面内容:

7.1.3 编写配置文件
java 复制代码
spring:
  rabbitmq:
    host: 你的地址
    port: 5672
    virtual-host: /tingyi
    username: test
    password: test
    publisher-confirms: true
    publisher-returns: true
7.1.4 开启Confirm和Return
java 复制代码
package com.tingyi.rabbitmq.config;
​
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
​
import javax.annotation.PostConstruct;
​
/**
 * @author 听忆
 */
@Component
public class PublisherConfirmAndReturnConfig implements RabbitTemplate.ConfirmCallback ,RabbitTemplate.ReturnCallback{
​
    @Autowired
    private RabbitTemplate rabbitTemplate;
​
    @PostConstruct  // init-method
    public void initMethod(){
        //指定 ConfirmCallback
        rabbitTemplate.setConfirmCallback(this);
​
        //指定 ReturnCallback
        rabbitTemplate.setReturnCallback(this);
    }
​
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if(ack){
            System.out.println("消息已经送达到Exchange");
        }else{
            System.out.println("消息没有送达到Exchange");
        }
    }
​
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        System.out.println("消息没有送达到Queue");
    }
}

7.2 手动Ack(保证接收可靠)

7.2.1 添加配置文件
  • 在消费方application.yml文件添加下面配置, 改为手动应答机制.
html 复制代码
spring:
  rabbitmq:
    host: 你的地址
    port: 5672
    virtual-host: /tingyi
    username: test
    password: test
    listener:
      simple:
        acknowledge-mode: manual
7.2.2 手动ack
java 复制代码
package com.tingyi.rabbitmq.topic;
​
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
​
import java.io.IOException;
​
/**
 * @author 听忆
 */
@Component
public class Consumer {
​
    @RabbitListener(queues = "boot-queue")
    public void getMessage(String msg, Channel channel, Message message) throws IOException {
        System.out.println("接收到消息:" + msg);
        try {
            int i = 1 / 0;
            /**
             * 消费者发起成功通知
             * 第一个参数: DeliveryTag,消息的唯一标识  channel+消息编号
             * 第二个参数:是否开启批量处理 false:不开启批量
             * 举个栗子: 假设我先发送三条消息deliveryTag分别是5、6、7,可它们都没有被确认,
             *          当我发第四条消息此时deliveryTag为8,multiple设置为 true,会将5、6、7、8的消息全部进行确认。
             */
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
        } catch (Exception e) {
            e.printStackTrace();
            /**
             * 返回失败通知
             * 第一个参数: DeliveryTag,消息的唯一标识  channel+消息编号
             * 第二个boolean true所有消费者都会拒绝这个消息,false代表只有当前消费者拒绝
             * 第三个boolean true消息接收失败重新回到原有队列中
             */
            channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);
        }
​
    }
}

7.3 避免消息重复消费

重复消费消息,会对非幂等行操作造成问题
重复消费消息的原因是,消费者没有给RabbitMQ一个ack

重复消费
  1. 为了解决消息重复消费的问题,可以采用Redis,在消费者消费消息之前,现将消息的id放到Redis中,

  2. id-0(正在执行业务)

  3. id-1(执行业务成功)

  4. 然后使用ack给RabbitMQ返回消息

  5. 如果RabbitMQack失败,在RabbitMQ将消息交给其他的消费者时,先执行setnx,如果key已经存在,获取他的值,如果是0,当前消费者就什么都不做,如果是1,直接ack。

  6. 极端情况:第一个消费者在执行业务时,出现了死锁,在setnx的基础上,再给key设置一个生存时间。

备注: java中的方法叫做setIfAbsent, redis中的命令叫做setnx

复制代码
       作用:
            如果为空就set值,并返回1, true

​ 如果存在(不为空)不进行操作,并返回0, false​

7.3.1 导入依赖

生产者和消费者都加入下面依赖

java 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>2.4.5</version>
</dependency>
7.3.2 编写配置文件
java 复制代码
spring:
  redis:
    host: 你的地址
    port: 6379
7.3.3 修改生产者
java 复制代码
@Test
public void contextLoads() throws IOException {
    CorrelationData messageId = new CorrelationData(UUID.randomUUID().toString());
    //第四个参数: 设置消息唯一id
    rabbitTemplate.convertAndSend("boot-topic-exchange","slow.red.dog","你看听忆哇",messageId);
    System.in.read();
}
7.3.4 修改消费者
java 复制代码
package com.tingyi.rabbitmq.topic;
​
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
​
import java.io.IOException;
import java.util.concurrent.TimeUnit;
​
/**
 * @author 听忆
 */
/**
         * java中的方法叫做setIfAbsent, redis中的命令叫做setnx
         * 作用:
         *      如果为空就set值,并返回1, true
         *      如果存在(不为空)不进行操作,并返回0, false
         */
@Component
public class Consumer {
​
    @Autowired
    private StringRedisTemplate redisTemplate;
​
    @RabbitListener(queues = "boot-queue")
    public void getMessage(String msg, Channel channel, Message message) throws IOException {
        //0. 获取MessageId, 消息唯一id
        String messageId = (String) message.getMessageProperties().getHeaders().get("spring_returned_message_correlation");
        //1. 设置key到Redis
        if(redisTemplate.opsForValue().setIfAbsent(messageId,"0", 10, TimeUnit.SECONDS)) {
​
            //2. 消费消息
            System.out.println("接收到消息:" + msg);
​
            //3. 设置key的value为1
            redisTemplate.opsForValue().set(messageId,"1",10,TimeUnit.SECONDS);
​
            //4.  手动ack
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
​
        }else {
​
            //5. 获取Redis中的value即可 如果是1,手动ack
            if("1".equalsIgnoreCase(redisTemplate.opsForValue().get(messageId))){
                channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
            }
        }
​
    }
}
相关推荐
Abladol-aj3 分钟前
并发和并行的基础知识
java·linux·windows
清水白石0083 分钟前
从一个“支付状态不一致“的bug,看大型分布式系统的“隐藏杀机“
java·数据库·bug
Elihuss1 小时前
ONVIF协议操作摄像头方法
开发语言·php
Swift社区4 小时前
在 Swift 中实现字符串分割问题:以字典中的单词构造句子
开发语言·ios·swift
没头脑的ht4 小时前
Swift内存访问冲突
开发语言·ios·swift
没头脑的ht5 小时前
Swift闭包的本质
开发语言·ios·swift
wjs20245 小时前
Swift 数组
开发语言
吾日三省吾码5 小时前
JVM 性能调优
java
stm 学习ing6 小时前
FPGA 第十讲 避免latch的产生
c语言·开发语言·单片机·嵌入式硬件·fpga开发·fpga
Estar.Lee6 小时前
查手机号归属地免费API接口教程
android·网络·后端·网络协议·tcp/ip·oneapi