可靠投递

原理

rabbitmq基于confirm模式、持久化策略,使用redis作为消息的临时储存空间

首先开启rabbitmq的confirm和持久化策略(可持久的对象为交换机、队列、消息)

1 开启confirm,在配置文件application.yml中

spring:
  rabbitmq:
    host: 192.168.200.128
    publisher-confirms: true  #开启confirm数据保护机制

2 开启交换机、队列、消息的持久化,在rabbitmq的配置类里

@Configuration
public class RabbitMQConfig {

    public static final String SECKILL_ORDER_QUEUE ="seckill_order";

    @Bean
    public Queue queue(){
        return new Queue(SECKILL_ORDER_QUEUE,true);//true为开启队列持久化
    }

}

此处没有用交换机,如果想要开启交换机持久化参考 https://www.cnblogs.com/wrc-blog/p/14340064.html

消息默认就是持久的

编写一个增强spring自带的rabbitTemplate的类

使其在向队列发消息前,先往redis中备份数据

同时实现RabbitTemplate.ConfirmCallback接口,接收confirm模式下的回调函数,根据结果执行下一步操作

package com.changgou.seckill.config;

import com.alibaba.fastjson.JSON;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

//增强rabbitmq
@Component
public class ConfirmMessageSender implements RabbitTemplate.ConfirmCallback {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private RedisTemplate redisTemplate;


    public static final String MESSAGE_CONFIRM_KEY="message_confirm_key";

    //构造函数
    public ConfirmMessageSender(RabbitTemplate rabbitTemplate) {
        this.rabbitTemplate = rabbitTemplate;
        rabbitTemplate.setConfirmCallback(this);
    }

    //开启生产者的confirm可靠投递时,用于接收rabbit服务器返回的通知
    //参数1 消息的唯一标识,需要在发送消息时就传入,参见下方自定义发送方法
    //参数2 发送是否成功
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if (ack){
            //成功 删除redis中的相关数据
            redisTemplate.delete(correlationData.getId());
            redisTemplate.delete(MESSAGE_CONFIRM_KEY+correlationData.getId());
        }else{
            //失败
            //从redis中获取失败的消息,重新发送
            Map<String,String> map = ( Map<String,String>)redisTemplate.opsForHash().entries(MESSAGE_CONFIRM_KEY + correlationData.getId());
            String exchange = map.get("exchange");
            String routingKey = map.get("routingKey");
            String message = map.get("message");
            rabbitTemplate.convertAndSend(exchange,routingKey, JSON.toJSONString(message));
        }
    }

    //自定义消息发送方法  先往redis里保存一份,再往消息队列里发送
    public void sendMessage(String exchange,String routingKey,String message){
        //将消息保存到redis中,在投递失败时重新取出再次投递
            //设置消息的唯一标识,这个类由rabbit提供,用来为消息设置唯一标识,在上方confrim用于回调时区分消息
            CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
            redisTemplate.opsForValue().set(correlationData.getId(),message);
            //将本次发送消息的元数据保存到redis
            Map<String,String> map = new HashMap<>();
            map.put("exchange",exchange);
            map.put("routingKey",routingKey);
            map.put("message",message);
            //注意这是哈希结构,你可以想为一大一小两个map,putAll会把上边定义的map作为小map的键值
            redisTemplate.opsForHash().putAll(MESSAGE_CONFIRM_KEY+correlationData.getId(),map);
        //携带本次消息的唯一标识,发送到消息队列
        //注意这里多了一个参数4,是我们上边定义的消息唯一标识,通过confirm模式确认可靠投递时,用corrlationData来区分不同的消息
        rabbitTemplate.convertAndSend(exchange,routingKey,message,correlationData); //这里的message是由外部传入的,如果是一个对象,要在外部先转为json
    }
}

 在需要进行可靠投递时,不用原生的rabbittemplate,而注入这个类,并调用sendMessage方法,回调函数会自动执行,并判断如何继续处理


 

可靠接收

默认为消费者一收到消息就立刻返回已收到,rabbit服务器会将消息从队列中删除

但是,万一消费者处理中宕机,则这条消息会丢失,所以我们希望在处理完后在签收这条消息

修改消费者的appication.yml

spring:
  rabbitmq:
    host: 192.168.200.128
    listener:
      simple:
        acknowledge-mode: manual  #开启消费者手动应答,rabbit接到应答后会删除队列中的消息,默认为自动,一接到就立刻应答

 

在监听者中手动签收消息  无需担心操作数据库但还未签收时,消息被重复获取的问题

package com.changgou.consumer.listener;

import com.alibaba.fastjson.JSON;
import com.changgou.consumer.config.RabbitMQConfig;
import com.changgou.consumer.service.SecKillOrderService;
import com.changgou.seckill.pojo.SeckillOrder;
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.stereotype.Component;

import java.io.IOException;

@Component
public class ConsumerListener {
    @Autowired
    private SecKillOrderService secKillOrderService;


    @RabbitListener(queues = RabbitMQConfig.SECKILL_ORDER_QUEUE)
    public void receiveSecKillOrderMessage(Message message, Channel channel){//以往都是用String直接接收消息内容
        //转换消息格式
        SeckillOrder seckillOrder = JSON.parseObject(message.getBody(), SeckillOrder.class);
        //基于业务层完成同步musql的操作
        int result = secKillOrderService.createOrder(seckillOrder);
        if(result>0){
            //同步mysql成功
            //向rabbitmq返回成功通知,rabbitmq会删除消息队列中的相关消息
            try {
                //参数1 在消息队列中的唯一标识,参数2 开启批处理
                channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }else {
            //同步mysql失败
            try {
                //参数1 消息的唯一标识
                //参数2 true所有的消费者都会拒绝这个消息 false只有当前的消费者拒绝这个消息
                //参数3 true当前的消息会进入死信队列(延迟消息队列) false当前的消息会重新进入消息队列,默认在头部
                channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

 

1

1

 

posted on 2021-01-28 19:57  wuzi2333  阅读(116)  评论(0)    收藏  举报