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);
            }
        }
​
    }
}
相关推荐
暗黑起源喵1 分钟前
设计模式-工厂设计模式
java·开发语言·设计模式
WaaTong6 分钟前
Java反射
java·开发语言·反射
Troc_wangpeng7 分钟前
R language 关于二维平面直角坐标系的制作
开发语言·机器学习
努力的家伙是不讨厌的9 分钟前
解析json导出csv或者直接入库
开发语言·python·json
Envyᥫᩣ22 分钟前
C#语言:从入门到精通
开发语言·c#
齐 飞30 分钟前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
狂放不羁霸38 分钟前
idea | 搭建 SpringBoot 项目之配置 Maven
spring boot·maven·intellij-idea
九圣残炎39 分钟前
【从零开始的LeetCode-算法】1456. 定长子串中元音的最大数目
java·算法·leetcode
wclass-zhengge41 分钟前
Netty篇(入门编程)
java·linux·服务器
童先生43 分钟前
Go 项目中实现类似 Java Shiro 的权限控制中间件?
开发语言·go