RabbitMQ

本文详细介绍了RabbitMQ的核心概念,包括消息队列、消息应答机制(自动应答与手动应答)、消息持久化、交换机类型(Fanout、Direct、Topic)、死信队列的配置与作用、延迟队列的实现以及发布确认的高级用法。此外,还涵盖了备份交换机、重复消费问题的解决、优先级队列和惰性队列的配置。
摘要由CSDN通过智能技术生成

RabbitMQ -> 概述理解

MQ:message queue,指消息队列,是存在于主系统之外的,能够存放消息的地方;发送方连接MQ,将消息发送到MQ进行存放,消费方连接MQ,在指定的队列获取消息进行消费,能够达到,异步处理,应用解耦,流量消峰的目的。*

文章目录


四大核心

  1. 生产者:将消息发送到交换机,交换机通过路由将消息发送到指定的队列保存
  2. 交换机:生产者是通过Exchange交换机将消息发送给队列的
  3. 队列:存放交换机路由的信息
  4. 消费者:连接队列进行消费
    Broker:代表MQ整个应用,包含exchange,queue
    exchange和queue之间通过routingKey进行Binding
    producer:生产者
    Consumer:消费者
    生产者和消费者都是通过Connection和Broker进行连接(每个Connection可以创建多个channel进行通讯)

一、安装RabbitMQ

  1. 官网下载地址:
https://www.rabbitmq.com/download.html
  1. 文件上传服务器(路劲任意),包括erlang的环境
  2. 安装文件执行
rpm -ivh erlang-21.3-1.el7.x86_64.rpm
yum install socat -y
rpm -ivh rabbitmq-server-3.8.8-1.el7.noarch.rpm
  1. 常用指令
    启动:systemctl start rabbitmq-server
    关闭:systemctl stop rabbitmq-server
    重启:systemctl restart rabbitmq-server
    状态:systemctl status rabbitmq-server
    安装Web管理插件:rabbitmq-plugins enable rabbitmq_management(访问15672)

二、使用RabbitMQ

1.引入库(Springboot搭建)

<dependency>
   	<groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
    <version>${rabbit.version}</version>
</dependency>

2.简单的入门案例

MQ的配置:

@Configuration
public class SummarizeConfig {

    //交换机NAME
    public String EXCHANGE = "test_exchange";

    //队列NAME
    public String QUEUE = "test_queue";
    //路由
    public String ROUTINGKEY = "test_routingKey";

    //声明交换机
    @Bean
    public DirectExchange getExchange() {
        return ExchangeBuilder.directExchange(EXCHANGE).build();
    }
    //声明队列
    @Bean
    public Queue getQueue() {
        return QueueBuilder.durable(QUEUE).build();
    }
    //队列绑定交换机
    @Bean
    public Binding bindingQueueToExchange() {
        return BindingBuilder.bind(getQueue()).to(getExchange()).with(ROUTINGKEY);
    }
}

生产者:

@RestController
public class SummarizeController {

    @Resource
    private RabbitTemplate rabbitTemplate;
    
    @GetMapping
    public void sendMessage(){
        rabbitTemplate.convertAndSend("test_exchange", "test_routingKey", "message");
    }
}

消费者:

@Component
public class RabbitMqLister {
    @RabbitListener(queues = "test_queue")
    public void acceptDealyInfo(Message message, Channel channel){
        System.out.println("接受到的消息是" + new Date().toString() + new String(message.getBody()));
    }
}

三、RabbitMQ实战技能点

1.消息应答

消息应答就是在消费者消费掉消息之后,告诉MQ,我已经消费了消息,MQ就会把该消息进行删除;应答的方式分为自动应答和手动应答,都是在消费方进行设置

  1. 自动应答:(默认)
    消息一旦被消费就会立马被删除,但是由于消费者在处理过程中可能会出现异常,就可能会导致消息不能成功消费,消息丢失

  2. 手动应答:
    在消息消费的地方,手动进行一个消息的应答确认

@Component
public class SummarizeLister {

    @RabbitListener(queues = "delay_queue")
    public void acceptDealyInfo(Message message, Channel channel) {
        System.out.println("接受到的消息是" + new Date().toString() + new String(message.getBody()));
        try {
            if (true) {
            	//应答确认(第二个参数就是是否批量确认,true:该队列的所有消息都会确认,)
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            } else {
            	//未确认
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
注意:未应答的消息会重新入队列,被其他队列消费

2.消息持久化

消息默认是存储在内存中的,一旦mq服务器宕掉了,消息就会丢失,我们就需要将队列和消息进行持久化

队列持久化:

队列持久化需要在申明队列的时候,进行持久化参数的设置

@Configuration
public class SummarizeConfig {
    
    //队列NAME
    public String QUEUE = "test_queue";
    
    //声明队列
    @Bean
    public Queue getQueue() {
        //第二个参数就是durable:true配置队列持久化
        return new Queue(QUEUE, true);
    }
}
消息持久化:

消息持久化需要在生产消息方进行设置

@RestController
public class SummarizeController {

    @Resource
    private RabbitTemplate rabbitTemplate;

    @GetMapping
    public void sendMessage(){
        MessageProperties messageProperties = new MessageProperties();
        //设置deliveryMode属性
        messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
        rabbitTemplate.convertAndSend("test_exchange", "test_routingKey",messageProperties);
    }
}
注:消息配置了持久化,但是不能保证绝对的消息不丢失,还需要配合发布确认
不公平分发
@Component
public class SummarizeLister {

    @RabbitListener(queues = "delay_queue")
    public void acceptDealyInfo(Message message, Channel channel) throws IOException {
        //不公平分发,这个预取值得调节来判断队列处理的速度,速度慢的可以设置低值,来让消息分发到其他更快的机器上
        channel.basicQos(5);
        System.out.println("接受到的消息是" + new Date().toString() + new String(message.getBody()));
        try {
            if (true) {
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            } else {
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

3.发布确认

生产者将信道设置成confirm模式,所有在该信道上发布的消息将会指派一个唯一的id(从1开始),消息一旦被投递到匹配的队列之后,broker就会发布一个确认给生产者,如果消息和队列有持久化,那么确认消息就会在持久化之后发送,confirm模式是异步的,在确认消息的同时,继续发送消息,如果消息丢失,会发送一条nack消息,可以在回调方法中处理该nack消息

1.单个确认(批量确认就是使用同一个channel发送多条数据之后,waitForConfirm一次)
//开启发布确认
channel.confirmSelect();
//确认发布
channel.waitForConfirms();
2.异步确认
//开启发布确认
 channel.confirmSelect();

 /**
 1. 线程安全有序的一个哈希表,适用于高并发的情况
 2. 1.轻松的将序号与消息进行关联
 3. 2.轻松批量删除条目 只要给到序列号
 4. 3.支持并发访问
  */
 ConcurrentSkipListMap<Long, String> outstandingConfirms = new
         ConcurrentSkipListMap<>();
 /**
 5. 确认收到消息的一个回调
 6. 1.消息序列号
 7. 2.true 可以确认小于等于当前序列号的消息
 8. false 确认当前序列号消息
  */
 ConfirmCallback ackCallback = (sequenceNumber, multiple) -> {
     if (multiple) {
         //返回的是小于等于当前序列号的未确认消息 是一个 map
         ConcurrentNavigableMap<Long, String> confirmed =
                 outstandingConfirms.headMap(sequenceNumber, true);
         //清除该部分未确认消息
         confirmed.clear();
     }else{
         //只清除当前序列号的消息
         outstandingConfirms.remove(sequenceNumber);
     }
 };
 ConfirmCallback nackCallback = (sequenceNumber, multiple) ->
 {String message = outstandingConfirms.get(sequenceNumber);
     System.out.println("发布的消息"+message+"未被确认,序列号"+sequenceNumber);
 };

 //异步确认发布
 channel.addConfirmListener(ackCallback, nackCallback);
 注:增加了一个监听器,异步的,在发送消息的同时能够确认消息ack/nack两个回调方法;异步的方法显然效率是最高的!

四、交换机(Exchange)

发送者将消息是发给交换机,然后交换机通过一定的规则发送给匹配的队列进行消费

交换机类型:

直接(direct), 主题(topic) ,标题(headers) , 扇出(fanout)

1. 扇出交换机(Fanout):

广播类型:该类型的交换机,会把消息发送给绑定该交换机的所有队列,跟routingKey无关

@Configuration
public class SummarizeConfig {
	//交换机NAME
    public String EXCHANGE = "test_exchange";

    //队列NAME
    public String QUEUE = "test_queue";

    //Fanout交换机
    @Bean
    public FanoutExchange getExchange() {
        return ExchangeBuilder.fanoutExchange(EXCHANGE).build();
    }
    //声明队列
    @Bean
    public Queue getQueue() {
        //第二个参数就是durable:true配置队列持久化
        return new Queue(QUEUE, true);
    }
    //队列绑定交换机
    @Bean
    public Binding bindingQueueToExchange() {
        return BindingBuilder.bind(getQueue()).to(getExchange());
    }
}
2. 直连交换机(Direct)

根据routingKey,绑定交换机,根据发送消息时候指定的routingKey发送到指定队列

@Configuration
public class SummarizeConfig {

    //交换机NAME
    public String EXCHANGE = "test_exchange";

    //队列NAME
    public String QUEUE = "test_queue";
    //路由
    public String ROUTINGKEY = "test_routingKey";

    //Fanout交换机
    @Bean
    public DirectExchange getExchange() {
        return ExchangeBuilder.directExchange(EXCHANGE).build();
    }
    //声明队列
    @Bean
    public Queue getQueue() {
        //第二个参数就是durable:true配置队列持久化
        return new Queue(QUEUE, true);
    }
    //队列绑定交换机
    @Bean
    public Binding bindingQueueToExchange() {
        return BindingBuilder.bind(getQueue()).to(getExchange()).with(ROUTINGKEY);
    }
}
3.主题交换机(Topics):

主题交换机的特点就是,routingKey能够匹配通配符:*:代替一个单词、#:代替0个或者多个单词

@Configuration
public class SummarizeConfig {

    //交换机NAME
    public String EXCHANGE = "test_exchange";

    //队列NAME
    public String QUEUE = "test_queue";
    //路由(topic的主要特点就是能够匹配通配符)
    public String ROUTINGKEY = "*.test.#";

    //Fanout交换机
    @Bean
    public TopicExchange getExchange() {
        return ExchangeBuilder.topicExchange(EXCHANGE).build();
    }
    //声明队列
    @Bean
    public Queue getQueue() {
        //第二个参数就是durable:true配置队列持久化
        return new Queue(QUEUE, true);
    }
    //队列绑定交换机
    @Bean
    public Binding bindingQueueToExchange() {
        return BindingBuilder.bind(getQueue()).to(getExchange()).with(ROUTINGKEY);
    }
}
@RestController
public class SummarizeController {

    @Resource
    private RabbitTemplate rabbitTemplate;

    @GetMapping
    public void sendMessage(){
        //这里的routingKey就可以使用.来间隔单词,使用通配符匹配多样的路由,发送到一个或者多个队列
        rabbitTemplate.convertAndSend("test_exchange", "ac.test.stu1", "你好");
    }
}

五、死信队列

死信队列的理解和作用:

由于单纯的队列,可能会出现一些异常情况导致消息不能够被消费,一直存在队列中,就需要配置死信队列去消费这些不能正常消费的消息,缓解正常队列的压力

死信队列的来源:

1.消息TTL过期
2.队列达到队列最大长度(队列满了,之后的队列就会丢失)
3.消息被拒绝

死信队列的配置

就是在正常的队列上绑定一个死信交换机,死信交换机和死信队列也是正常的绑定

@Configuration
public class RabbitMqConfig {

    //声明交换机
    public String X_EXCHANGE = "X";
    public String Y_EXCHANGE = "Y";

    //队列
    public String a_queue = "A";
    public String b_queue = "B";
    public String c_queue = "C";
    //不设置过期时间的队列,能够接受任何过期时间的消息,但是由于队列的关系,先进先出原则,导致后续的消息不能按时的过期
    public String d_queue = "D";

    @Bean
    public DirectExchange x_exchange(){
        return new DirectExchange(X_EXCHANGE);
    }

    @Bean
    public DirectExchange y_exchange(){
        return new DirectExchange(Y_EXCHANGE);
    }

    /**
     * 这个队列就不需要设置过期时间,直接绑定死信队列,接受过期消息(消息的过期时间由发送消息的时候设置)
     */
    @Bean
    public Queue d_queue(){
        //这个队列需要绑定死信交换机
        Map<String, Object> arguments = new HashMap<>();
        arguments.put("x-dead-letter-exchange", Y_EXCHANGE);
        arguments.put("x-dead-letter-routing-key", "YD");
        return QueueBuilder.durable(d_queue).withArguments(arguments).build();
    }
    @Bean
    public Binding queueDBindingExchange(){
        return BindingBuilder.bind(d_queue()).to(x_exchange()).with("XD");
    }

    @Bean
    public Queue a_queue(){
        //这个队列需要绑定死信交换机
        Map<String, Object> arguments = new HashMap<>();
        arguments.put("x-dead-letter-exchange", Y_EXCHANGE);
        arguments.put("x-dead-letter-routing-key", "YD");
        arguments.put("x-message-ttl", 5000);
        return QueueBuilder.durable(a_queue).withArguments(arguments).build();
    }
    @Bean
    public Binding queueABindingExchange(){
        return BindingBuilder.bind(a_queue()).to(x_exchange()).with("XA");
    }

    @Bean
    public Queue b_queue(){
        //这个队列需要绑定死信交换机
        Map<String, Object> arguments = new HashMap<>();
        arguments.put("x-dead-letter-exchange", Y_EXCHANGE);
        arguments.put("x-dead-letter-routing-key", "YD");
        arguments.put("x-message-ttl", 10000);
        return QueueBuilder.durable(b_queue).withArguments(arguments).build();
    }
    @Bean
    public Binding queueBBindingExchange(){
        return BindingBuilder.bind(b_queue()).to(x_exchange()).with("XB");
    }

    //死信队列和交换机绑定
    @Bean
    public Queue c_queue(){
        return QueueBuilder.durable(c_queue).build();
    }
    //注意,死信队列和死信交换机绑定的时候,这里的routingKey一定要和上面正常的队列绑定死信交换机时设置的routingKey一致
    @Bean
    public Binding queueCBindingExchange(){
        return BindingBuilder.bind(c_queue()).to(y_exchange()).with("YD");
    }

}

消息TTL过期,可以在队列设置,可以在发送消息的时候设置(上面配置了队列TTL,也有消息TTL)

@RestController
public class SummarizeController {

    @Resource
    private RabbitTemplate rabbitTemplate;

    @GetMapping
    public void sendMessage(){
        MessageProperties messageProperties = new MessageProperties();
        //设置消息过期时间
        messageProperties.setExpiration("10000");
        rabbitTemplate.convertAndSend("test_exchange", "ac.test.stu1",messageProperties);
    }
}

队列长度

@Bean
public Queue d_queue(){
    //这个队列需要绑定死信交换机
    Map<String, Object> arguments = new HashMap<>();
    arguments.put("x-dead-letter-exchange", Y_EXCHANGE);
    arguments.put("x-dead-letter-routing-key", "YD");
    //设置队列的最大长度
    arguments.put("x-max-length", 6);
    return QueueBuilder.durable(d_queue).withArguments(arguments).build();
}

消息被拒

消费方在消费消息的时候,拒绝消息

@Component
public class SummarizeLister {

	@RabbitListener(queues = "delay_queue")
	public void acceptDealyInfo(Message message, Channel channel) {
	    try {
	        if (true) {
	            System.out.println("接受到的消息是" + new Date().toString() + new String(message.getBody()));
	            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
	        } else {
	            //拒绝消息
	            channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
	        }
	    } catch (IOException e) {
	        throw new RuntimeException(e);
	    }
	}
}

以上三种情况,只要是发送到的正常队列,在配置的时候,绑定了死信交换机,那么出现任何一种情况,消息都会传给死信交换机发送给死信队列,由监听死信队列的消费者进行消费处理

六、延迟队列

延迟队列的作用可能就是在指定时间之后进行一个信息的处理,本质上其实就是使用死信队列和TTL相结合的方式,让消息到了指定时间过期,过期之后放到死信队列中信息下一步消费,正常的队列就不需要再设置对应的消费者,让消息等待直到过期;

消息过期和队列过期的区别

消息过期:存放在队列中,如果队列前面存在还未消费的消息,那么该过期消息不会立即被队列丢弃,要等到消费到该消息的时候才丢弃;
队列过期:队列设置了TTL,那么一旦消息过期就会立马被队列丢弃,如果配置了死信队列,会丢个死信队列

延迟队列的配置

@Component
public class MsgTtlQueueConfig {
	public static final String Y_DEAD_LETTER_EXCHANGE = "Y";
	public static final String QUEUE_C = "QC";
	//声明队列 C 死信交换机
	@Bean("queueC")
	public Queue queueB(){
		Map<String, Object> args = new HashMap<>(3);
		//声明当前队列绑定的死信交换机
		args.put("x-dead-letter-exchange", Y_DEAD_LETTER_EXCHANGE);
		//声明当前队列的死信路由 key
		args.put("x-dead-letter-routing-key", "YD");
		//声明队列的 TTL(注意这里,如果是队列设置了TTL,那么这个队列最好是同一时间过期的消息,如果不设置队列TTL,发送消息的时候设置消息的TTL,那么这个队列就属于一个公共的队列)
		args.put("x-message-ttl", 40000);
		//没有声明 TTL 属性
		return QueueBuilder.durable(QUEUE_C).withArguments(args).build();
	}
	//声明队列 B 绑定 X 交换机
	@Bean
	public Binding queuecBindingX(@Qualifier("queueC") Queue queueC,
		@Qualifier("xExchange") DirectExchange xExchange){
		return BindingBuilder.bind(queueC).to(xExchange).with("XC");
	}
}
注意:如果是全部过期消息都放到了一个队列,就会出现到期不能及时被消费的情况,因为队列会按照顺序进行消费

Rabbitmq 插件实现延迟队列

rabbitMq的安装目录的plgins目录下,安装rabbitmq_delayed_message_exchange插件

安装步骤:

1.进入plugins目录:/usr/lib/rabbitmq/lib/rabbitmq_server-3.8.8/plugins
2.执行插件:rabbitmq-plugins enable rabbitmq_delayed_message_exchange
插件安装完成之后,交换机里面就会多出一种x-delayed-message类型

配置插件延迟交换机

配置
@Configuration
public class DelayRabbitMqConfig {

    //声明延迟交换机名称
    public String DELAY_EXCHANGE = "delay_exchange";
    public String DELAY_QUEUE = "delay_queue";
    public String DELAY_KEY = "delay_key";

    //声明交换机
    @Bean
    public CustomExchange getCustomExchange() {
        Map<String, Object> map = new HashMap<>();
        //自定义交换机的类型
        map.put("x-delayed-type", "direct");
        return new CustomExchange(DELAY_EXCHANGE, "x-delayed-message", true, false, map);
    }

    //声明队列
    @Bean
    public Queue getQueue(){
        return QueueBuilder.durable(DELAY_QUEUE).build();
    }

    //绑定延迟交换机和队列
    @Bean
    public Binding binDing(){
        return BindingBuilder.bind(getQueue()).to(getCustomExchange()).with(DELAY_KEY).noargs();
    }
}
生产者
@RestController
public class DelayQueueController {

    @Resource
    private RabbitTemplate rabbitTemplate;

    /**
     * 延迟插件的实现,延迟的是交换机
     * 这里在设置消息的时候,不能使用expiretion,过期时间,必须使用setDelay()延迟时间
     * 基于插件的延迟交换机就能够实现,先过期的先消费
     * 1.安装delay-message插件
     * 2.配置自定义交换机类型为:x-delayed-message,还需要设置交换机map参数x-delayed-type,direct
     * 3.正常的声明队列,绑定队列,发送消息(发送的时候需要注意的是,设置消息的延迟时间使用的是setDelay,而不是之前的过期时间),	监听消息
     */
    @GetMapping("/delay2/{message}")
    public void sendDelayQueueInfo2(@PathVariable String message){
        rabbitTemplate.convertAndSend("delay_exchange","delay_key","10秒消息" + message, msg -> {
            msg.getMessageProperties().setDelay(10000);
            return msg;
        });

        rabbitTemplate.convertAndSend("delay_exchange","delay_key","5秒消息" + message, msg -> {
            msg.getMessageProperties().setDelay(5000);
            return msg;
        });
    }
}
注意:配置了插件的交换机之后,发送到这个队列的消息就会按照过期时间进行排列,按照顺序进行消费,就不会出现过期不死的情况

七、发布确认高级(确认和回退)

发布确认高级:指在确保消息发送给交换机,交换机发送给队列的时候,进行一个确认,保证消息最终能够达到队列
1.ConfirmCallback:消息确认,返回参数是否为true判断消息是否发送成功
2.ReturnsCallback:消息是否路由成功

发布确认高级需要在配置文件中配置:

spring:
  rabbitmq:
    host: xx.xx.xx.xx
    username: xx
    password: xx
    port: 5672
    # 开启发布消息到交换机确认回调
    publisher-confirm-type: correlated
    # routingKey 错误回调(队列问题)
    publisher-returns: true
NONE:禁用发布确认模式,是默认值
CORRELATED:发布消息成功到交换器后会触发回调方法

Springboot配置

@RestController
public class ConfirmQueueController {

    @Resource
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void init(){
        //这个是发送到交换机的确认(g高级确认就是使用rabbitmq对象的时候,需要设置ConfirmCallBack -> 消息发送到交换机确认;
        // returnCallBack -> 路由到队列失败回退方法;两个方法都可以获取到message,可以后续继续处理)
        rabbitTemplate.setConfirmCallback((var1, var2, var3) -> {
            assert var1 != null;
            System.out.println("收到消息:"+ var1.getId() + "");
            if (var2) {
                System.out.println("消息发送到交换机成功");
            } else {
                System.out.println("消息发送到交换机失败");
            }
        });
        /**
         * true:
         * 交换机无法将消息进行路由时,会将该消息返回给生产者
         * false:
         * 如果发现消息无法进行路由,则直接丢弃
         */
        rabbitTemplate.setMandatory(true);

        //设置队列失败 回退函数(如果同时设置了回退消息,以及备份交换机,那么备份交换机的优先级是要高于回退消息的)
        rabbitTemplate.setReturnsCallback(returnedMessage -> System.out.println("回退的消息:" + new String(returnedMessage.getMessage().getBody()) + " 回退的交换机:" + returnedMessage.getExchange() + "回退routingkey: " + returnedMessage.getRoutingKey()));
    }

    /**
     * 延迟插件的实现,延迟的是交换机
     * 这里在设置消息的时候,不能使用expiretion,过期时间,必须使用setDelay()延迟时间
     * 基于插件的延迟交换机就能够实现,先过期的先消费
     * 1.安装delay-message插件
     * 2.配置自定义交换机类型为:x-delayed-message,还需要设置交换机map参数x-delayed-type,direct
     * 3.正常的声明队列,绑定队列,发送消息(发送的时候需要注意的是,设置消息的延迟时间使用的是setDelay,而不是之前的过期时间),监听消息
     */
    @GetMapping("/confirm/{message}")
    public void sendDelayQueueInfo2(@PathVariable String message){
        CorrelationData correlationData = new CorrelationData();
        correlationData.setId("1");
        rabbitTemplate.convertAndSend("confirm_exchange","confirm_key","正常的队列消息" + message, correlationData);
        rabbitTemplate.convertAndSend("confirm_exchange","confirm_key1","错误routingKey消息" + message, correlationData);
        //rabbitTemplate.convertAndSend("confirm_exchange","confirm_key","回调测试信息" + message);
    }
}

八、备份交换机

备份交换机可以理解为正常交换机的备胎,给一个交换机申明了一个备份交换机之后,如果消息不能路由时,这个消息就会被转发到备份交换机,由备份交换机进行处理

备份交换机配置

配置的原理就是在正常的交换机上面配置一个alternate-exchange属性的交换机,然后交换机和队列就是正常的申明和绑定关系;如果备份交换机和确认和回退同时配置了,那么备份交换机的优先级要高于回退配置

@Configuration
public class ComfirmRabbitMqConfig {

    //声明交换机名称,队列名称,routingKey
    public String CONFIRM_EXCHANGE = "confirm_exchange";
    public String CONFIRM_QUEUE = "confirm_queue";
    public String CONFIRM_KEY = "confirm_key";
    public String BACKUPS_EXCHANGE = "backups_exchange";
    public String BACKUPS_QUEUE = "backups_queue";
    public String WARING_QUEUE = "waring_queue";

    //声明交换机(备份交换机,就是在普通交换机增加了callBack,returnBack,之后,需要单独的设置一个交换机,来作为当前这个交换机的备份交换机,所以普通交换机在创建的时候,需要设置“alternate-exchange”,属性)
    @Bean
    public DirectExchange getConfirmDirectExchange() {
        return ExchangeBuilder.directExchange(CONFIRM_EXCHANGE).durable(true).withArgument("alternate-exchange", BACKUPS_EXCHANGE).build();
    }

    @Bean
    public FanoutExchange getBackupsExchange() {
        return new FanoutExchange(BACKUPS_EXCHANGE);
    }

    //声明队列
    @Bean
    public Queue getConfirmQueue(){
        return QueueBuilder.durable(CONFIRM_QUEUE).build();
    }

    @Bean
    public Queue getBackupsQueue(){
        return QueueBuilder.durable(BACKUPS_QUEUE).build();
    }

    @Bean
    public Queue getWaringQueue(){
        return QueueBuilder.durable(WARING_QUEUE).build();
    }

    //绑定延迟交换机和队列
    @Bean
    public Binding binDingConfirm(){
        return BindingBuilder.bind(getConfirmQueue()).to(getConfirmDirectExchange()).with(CONFIRM_KEY);
    }
    //fanout类型交换机(备份交换机和队列都是正常的配置以及正常的交换机和队列绑定,只是在普通的交换机创建的时候,需要申明属性alternate-exchange,备份交换机)
    @Bean
    public Binding binDingBackups(){
        return BindingBuilder.bind(getBackupsQueue()).to(getBackupsExchange());
    }
    @Bean
    public Binding binDingWaring(){
        return BindingBuilder.bind(getWaringQueue()).to(getBackupsExchange());
    }
}

九、重复消费问题

重复消息作为mq中是重点问题,消费者正常消费了消息,但是由于异常情况不能ack,导致消息再次回到队列中,从而导致消息重复消费

解决方案:使用唯一ID

唯一ID+数据库,每次消费的时候对这个消息的id进行一个数据库查询对比是否已经消费过了,适合低并发的情况
唯一ID+redis,使用redis的原子性redis 执行 setnx 命令,来对比是否已经消费过了,并发高推荐
在消息进行消费的时候,先根据消息的id查询是否已经存在,存在就是重复消息,不存在就正常消费;消费完成回调的时候,如果确认成功,就删除id,确认失败就另行处理

十、优先级队列

优先级队:需要设置队列优先级,消息优先级,消费者需要等待消息已经发送到队列中之后,才去消费(需要对消息进行排序)

1. 配置优先级队列
@Configuration
public class PriorityMqConfig {

    //声明交换机
    public String EXCHANGE = "priority_exchange";

    //队列
    public String a_queue = "priority_queue";

    @Bean
    public DirectExchange exchange(){
        return new DirectExchange(EXCHANGE);
    }
    //优先级队列,队列需要设置优先级,发送消息的时候也需要设置优先级
    @Bean
    public Queue queue(){
        Map<String, Object> arguments = new HashMap<>();
        arguments.put("x-max-priority", 10);
        return QueueBuilder.durable(a_queue).withArguments(arguments).build();
    }
    @Bean
    public Binding getBinding(){
        return BindingBuilder.bind(queue()).to(exchange()).with("priority");
    }
}
2.发送消息
@RestController
@RequestMapping("/priority")
public class PriorityQueueController {

    @Resource
    private RabbitTemplate rabbitTemplate;
    @GetMapping("/confirm/{message}")
    public void sendDelayQueueInfo2(@PathVariable String message){

        for (int i = 0; i < 10; i++) {
            CorrelationData correlationData = new CorrelationData();
            correlationData.setId(String.valueOf(i));

            //如果想要使用到优先级的话,就需要在配置队列的时候,设置x-max-priority参数,然后再发消息的时候,设置setPriority(),数值越大优先级越高,但是消息这边设置的数值不能高于队列配置时设定的数值
            if (i == 5) {
                rabbitTemplate.convertAndSend("priority_exchange", "priority", "priority 队列消息 " + i + " " + message, msg -> {
                    msg.getMessageProperties().setPriority(5);
                    return msg;
                }, correlationData);
            } else {
                rabbitTemplate.convertAndSend("priority_exchange","priority","priority 队列消息 " + i + " " + message, correlationData);
            }
        }
    }
}

十一、惰性队列

惰性队列会尽可能的将消息存入磁盘中,而在消费者消费到相应的消息时才会被加载到内存中,它的一个重要的设计目标是能够支持更长的队列,即支持更多的消息存储;默认情况下消息是存储在内存中的,及时设置了持久化,内存中也有一个备份,也会阻塞队列;惰性队列的内存开销很小,但是最好使用在消息量特别大的情况下。

惰性队列的配置

只需要在申明队列的时候,设置x-queue-mode参数即可

//声明队列
    @Bean
    public Queue getQueue() {
        //第二个参数就是durable:true配置队列持久化
        HashMap<String, Object> aguments = new HashMap<>();
        aguments.put("x-queue-mode", "lazy");
        return new Queue(QUEUE, true,false,false, aguments);
    }

总结

以上是基于RabbitMQ的基本使用
也是学习RabbitMQ的一个汇总

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值