SpringBoot中rabbitMQ确认机制和return机制

保证发送成功机制
保证消费成功机制

1、添加配置

rabbitmq:
    host: 10.100.4.8
    port: 5672
    username: pss123
    password: pss123
    virtual-host: host1
    #return机制
    publisher-returns: true
    #确认机制
    publisher-confirm-type: simple

2、编写配置代码

@Configuration
public class RabbitMQConfiguration {
    @Bean
    public Queue queue() {
        return new Queue("wfx-quence");
    }

    @Bean
    public Queue fanoutQuence() {
        return new Queue("wfx-fanout-quence");
    }
    /**
     * 声明交换机,fanout 类型
     */
    @Bean
    public FanoutExchange fanoutExchange() {
        FanoutExchange fanoutExchange = new FanoutExchange("fanoutExchange");
        return fanoutExchange;
    }
    /**
     * 将队列和交换机绑定
     */
    @Bean
    public Binding bindingFanoutExchange(Queue fanoutQuence, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(fanoutQuence).to(fanoutExchange);
    }


    @Bean
    public Queue directQuence1() {
        return new Queue("fwmessage-direct-queue1");
    }
    @Bean
    public Queue directQuence2() {
        return new Queue("fwmessage-direct-queue2");
    }
    /**
     * 声明交换机,direct 类型
     */
    @Bean
    public DirectExchange directExchange() {
        DirectExchange directExchange = new DirectExchange("directExchange");
        return directExchange;
    }
    /**
     * 将队列和交换机绑定
     */
    @Bean
    public Binding bindingDirectExchange(Queue directQuence1, DirectExchange directExchange) {
        return BindingBuilder.bind(directQuence1).to(directExchange).with("rk1");
    }

    @Bean
    public Binding bindingDirectExchange2(Queue directQuence2, DirectExchange directExchange) {
        return BindingBuilder.bind(directQuence2).to(directExchange).with("rk2");
    }
}
import cn.hutool.json.JSONObject;
import com.alibaba.fastjson.JSON;
import com.sun.xml.internal.ws.api.model.MEP;
import com.zjrb.bigdata.receiver.entity.MQMessageEntity;
import com.zjrb.bigdata.receiver.entity.hybase.MqAppMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

/**
 * @Description: 交换机和队列确认机制
 * @date 2021/3/16 9:41
 */
@Slf4j
@Component
public class PublisherConfireAndReturnConfig implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {
    @Resource
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void initMethod() {
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnCallback(this);
    }

    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if (ack) {
            log.info("消息发送到交换机成功!");
        } else {
            log.info(String.format("消息发送到交换机失败! 消息的id: %s", correlationData.getId()));
            // 记录数据,稍后重试
        }
    }

    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exChange, String routingKey) {
        log.info(String.format("%s 消息发送到交换机但未分发到队列", message.toString()));
        // 将数据转换成实体,取出保存,稍后重试
        String bodyContent = new String(message.getBody());
        JSONObject jsonObject = new JSONObject(bodyContent);
        JSONObject data = (JSONObject) jsonObject.get("data");
        MQMessageEntity mqAppMessage = JSON.parseObject(data.toString(), MQMessageEntity.class);

    }
}

3、生产者代码

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONObject;
import com.trs.hybase.client.TRSReport;
import com.trs.search.builder.ITRSSearchBuilder;
import com.trs.search.builder.SearchBuilderFactory;
import com.trs.search.common.PagedList;
import com.zjrb.bigdata.receiver.entity.MQMessageEntity;
import com.zjrb.bigdata.receiver.entity.hybase.AppMessagePushOriginal;
import com.zjrb.bigdata.receiver.repository.AppMessageRepository;
import com.zjrb.bigdata.receiver.service.AppMessageService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * @author pss
 * @Description:
 * @date 2021/2/1 10:16
 */
@Service
@Slf4j
public class AppMessageServiceImpl implements AppMessageService {

	// 此处不要用AmqpTemplate,否则发送问题的时候无法设置message的id
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public void sendMsgToMQ(List<AppMessagePushOriginal> list) {

        for (AppMessagePushOriginal data : list) {
            HashMap<String, MQMessageEntity> map = new HashMap<>();
            MQMessageEntity res = new MQMessageEntity();
            res.setDoctitle(data.getIrUrltitle());
            res.setDocpubtime(data.getIrUrltime());
            res.setZbSourceSite(data.getZbSourceSite());
            res.setIrSid(data.getZbGuidChar());
            map.put("data",res);
            JSONObject jsonObject = new JSONObject(map);
            log.info(String.format("本次推送的消息: %s",jsonObject.toString()));

			// 在此处设置消息的id,方便在发送失败的时候进行处理
            rabbitTemplate.convertAndSend("directExchange", "rk1", jsonObject.toString(),
                    message -> {
                        message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT);
                        return message;
                    },new CorrelationData(res.getIrSid()));

//            amqpTemplate.convertAndSend("directExchange", "rk1", jsonObject.toString());
        }

    }
}

4、消费者确认

消费者确认发生在监听队列的消费者处理业务失败,如,发生了异常,不符合要求的数据……,这些场景我们就需要手动处理,比如重新发送或者丢弃。

我们知道ACK是默认是自动的,自动确认会在消息发送给消费者后立即确认,但存在丢失消息的可能,如果消费端消费逻辑抛出异常,加入你用回滚了也只是保证了数据的一致性,但是消息还是丢了,也就是消费端没有处理成功这条消息,那么就相当于丢失了消息。

消息确认模式有
  • AcknowledgeMode.NONE:自动确认
  • AcknowledgeMode.AUTO:根据情况确认
  • AcknowledgeMode.MANUAL:手动确认
通过配置文件配置手动确认模式
spring.rabbitmq.listener.direct.acknowledge-mode=MANUAL
消费者代码

重点看注释部分

// 死信队列专用消费者,专门监听死信队列
@RabbitListener(queues = "deadQueue")
    @RabbitHandler
    public void consumer1(String str) {
        log.info("接收到死信队列的消息:" + str);
    }

@RabbitListener(queues = "fwmessage-direct-queue1")
    @RabbitHandler
    public void consumer2(String str, Message message, Channel channel) throws IOException {
        log.info("接收到消息:" + str);
        try {
            /**
             * channel.basicAck(long var1, boolean var3)参数说明:
             * deliveryTag(唯一标识 ID):当一个消费者向 RabbitMQ 注册后,会建立起一个 Channel ,RabbitMQ 会用 basic.deliver 方法向消费者推送消息,这个方法携带了一个 delivery tag, 它代表了 RabbitMQ 向该 Channel 投递的这条消息的唯一标识 ID,是一个单调递增的正整数,delivery tag 的范围仅限于 Channel
             * multiple:为了减少网络流量,手动确认可以被批处理,当该参数为 true 时,则可以一次性确认 delivery_tag 小于等于传入值的所有消息
             */
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
            // 确认回调
            log.info("消费消息确认" + message.getMessageProperties().getConsumerQueue() + ",接收到了回调方法");
        } catch (Exception exception) {
            // 注意注意!!!下面两个方法代表两种不同的处理方式,只需要用一个就好
            /**
             * basicNack(long var1, boolean var3, boolean var4)参数说明(其实前两个参数和basicAck()方法一样,区别在于最后一个):
             * deliveryTag(唯一标识 ID):当一个消费者向 RabbitMQ 注册后,会建立起一个 Channel ,RabbitMQ 会用 basic.deliver 方法向消费者推送消息,这个方法携带了一个 delivery tag, 它代表了 RabbitMQ 向该 Channel 投递的这条消息的唯一标识 ID,是一个单调递增的正整数,delivery tag 的范围仅限于 Channel
             * multiple:为了减少网络流量,手动确认可以被批处理,当该参数为 true 时,则可以一次性确认 delivery_tag 小于等于传入值的所有消息
             * requeue: true:重回队列,false:丢弃。如果选择了重回队列,消费者会一直尝试消费,消费失败继续重回队列,一直重复此循环到成功
             */
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), true, true);
            /**
             * 也可以拒绝该消息,消息会被丢弃,不会重回队列
             */
            channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
        }
    }

5、死信队列

对RabbitMQ来说,产生死信的来源大致有如下几种:
1、消息被拒绝(basic.reject或basic.nack)并且requeue=false.
2、消息TTL过期
3、队列达到最大长度(队列满了,无法再添加数据到mq中)

“死信”消息会被RabbitMQ进行特殊处理,如果配置了死信队列信息,那么该消息将会被丢进死信队列中,如果没有配置,则该消息将会被丢弃。而死信队列其实就是绑定到死信交换机上的队列。

如何配置死信队列
  • 正常配置业务队列(绑定到业务交换机上,并配置路由键)
  • 配置死信队列(绑定到死信交换机上,并配置路由键),和业务队列是一样的,只是名字不一样
  • 将业务队列绑定到死信交换机上,并配置路由键A
死信的处理流程

消息正常发送到业务队列,如果该业务队列拒绝消息,则该消息变成死信,这个时候就会根据路由键A找到对应的死信队列,将消息投放给死信队列,由死信队列处理。

死信队列配置
//-------------开始配置死信队列-------------
    // 死信交换机
    @Bean
    public DirectExchange deadDirectExchange(){
        return new DirectExchange("deadDirectExchange",true,false);
    }
	// 死信队列
    @Bean
    public Queue deadQueue(){
        return new Queue("deadQueue",true,false,false);
    }

    // 绑定死信队列和死信交换机、路由key
    @Bean
    public Binding deadQueueBiding(){
        return BindingBuilder.bind(deadQueue()).to(deadDirectExchange()).with("deadRouterKey");
    }
	// 将普通业务队列绑定到死信交换机并设置路由键
	@Bean
    public Queue directQuence1() {
        // 给普通队列绑定到死信交换机,并设置路由键
        HashMap<String, Object> hashMap = new HashMap<>();
        hashMap.put("x-dead-letter-exchange", "deadDirectExchange");
        hashMap.put("x-dead-letter-routing-key", "deadRouterKey");
        return new Queue("wfx-direct-quence1",true,false,false,hashMap);
    }

注意:此时的普通队列同时绑定了普通交换机和死信交换机

此时当消费者消费失败时,消息会自动进入死信队列,通过监听死信队列可以对死信消息进行处理了

6、延时队列

在rabbitmq中不存在延时队列,但是我们可以通过设置消息的过期时间(过期消息会变为死信)和死信队列来模拟出延时队列。

在 rabbitmq 中存在2种方可设置消息的过期时间,第一种通过对队列进行设置,这种设置后,该队列中所有的消息都存在相同的过期时间,第二种通过对消息本身进行设置,那么每条消息的过期时间都不一样。如果同时使用这2种方法,那么以过期时间小的那个数值为准。当消息达到过期时间还没有被消费,那么那个消息就成为了一个死信消息。

  • 队列设置:在队列申明的时候使用 x-message-ttl 参数(和上面死信队列设置一样),单位为 毫秒
  • 单个消息设置:发送消息时设置消息属性的 expiration 参数的值,单位为 毫秒
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值