SpringBoot 整合 RabbitMQ

RabbitMQ 安装

docker pull rabbitmq

//集群部署添加hostname
docker run -d  --name rabbit -p 15672:15672 -p 5672:5672 rabbitmq

 docker exec -it rabbitmq /bin/bash
 
 rabbitmq-plugins enable rabbitmq_management
 
 // 退出容器   ctrl+p 和 ctrl+q

基础概念

channel:通道 发送/接收消息到mq 的通道

exchange:交换机 消息的路由器

queue: 队列 存储消息

消息模型

基本消息队列(basic queue)

image

直接将消息发送至队列

工作消息队列(work queue)

image

直接将消息发送至队列

多个消费者共同处理消息,消费者先从队列中获取完消息再处理。可以设置一次只允许取一个消息

发布订阅模型,根据交换机不同分为三种

发布订阅都是将消息发送到exchange 再有exchange 转发到队列当中。如果exchange 发送消息到队列失败 ,则消息会丢失

广播 (FanoutExchange)

image

将消息发送至交换机,由交换机发送至绑定的每一个队列


package com.tlj.rabbitmq.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Author: tanglj
 * @Date: 2022/8/19
 * @Description:
 */
@Configuration
public class RabbitConfig {

    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange("msg");
    }

    @Bean
    public Queue queue1() {
        return new Queue("que1");
    }

    @Bean
    public Queue queue2() {
        return new Queue("que2");
    }

    @Bean
    public Binding bingQue1(Queue queue1,FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(queue1).to(fanoutExchange);
    }
    @Bean
    public Binding bingQue2(Queue queue2,FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(queue2).to(fanoutExchange);
    }
}

路由 (DirectExchange)

image

交换机会将消息根据指定规则路由到不同的消息队列当中

步骤 :

  • 每一个队列都会和交换机设置绑定bindingKey

  • 发布者发送消息时指定bingkey

  • exchange将消息路由到bindingKey 和roundingKey 一直的消息队列

样例 :

    @RabbitListener(bindings = @QueueBinding(
                            value = @Queue("que3"), // 队列
                            exchange = @Exchange(name ="change1",type = ExchangeTypes.DIRECT), // 交换机
                            key = {"a","b"})) // 指定bindingKey
    public void receiver3(String msg) {
        System.out.println("msg = " + msg);
    }

主题(TopicExchange)

image

与路由交换机类似 ,区别在于queue 与 exchange 指定bindingKey时 可以使用通配符,以 . 分割

#:代表零个或多个单词
*:指一个单词

样例:

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue("que3"), // 队列
            exchange = @Exchange(name ="change1",type = ExchangeTypes.TOPIC), // 交换机
            key = {"user.#","#.name"})) // 指定bindingKey
    public void receiver4(String msg) {
        System.out.println("msg = " + msg);
    }
	

消息转换器

SpringAMQP中默认消息的序列化和反序列化是用 MessageConverter 实现的(jdk 序列化),可以重新定义MessageConverter 来实现序列化的更改
,但是发送方和接收方 都必须使用相同的 MessageConverter。

消息可靠性

生产者确保消息发送成功

生产者 -> 队列 需要经过两步:

  1. 生产者 到 交换机 ConfirmCallback
  2. 交换机 到 队列 ReturnsCallback
package com.tlj.rabbitmq.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * @Author: login
 * @Date: 2022/8/22
 * @Description:  消息发送到交换机 是否成功
 * 配置文件 添加 :publisher-confirm-type: correlated
 */
@Component
@Slf4j
public class RabbitConfirmConfig implements RabbitTemplate.ConfirmCallback {


    @Autowired
    private  RabbitTemplate   rabbitTemplate ;


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



    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if (ack) {
            log.info("消息发送成功");
        } else {
            log.info("消息发送到交换机失败");
        }
    }
}
package com.tlj.rabbitmq.config;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * @Author: login
 * @Date: 2022/8/22
 * @Description:  消息发送到队列失败返回,比如路由不到队列时触发回调
 * 配置文件添加: publisher-returns: true
 */
@Component
@Slf4j
public class RabbitReturnConfig implements RabbitTemplate.ReturnsCallback {

    @Autowired
    private  RabbitTemplate   rabbitTemplate ;


    @PostConstruct
    public void init(){
        rabbitTemplate.setReturnsCallback(this);
    }

    /**
      *
      * @desc:
     *   处理方法一:  可以先将消息存到数据库,有定时任务拉取重复发送,将异常信息保存到数据库
      */
    @Override
    public void returnedMessage(ReturnedMessage returned) {
        int replyCode = returned.getReplyCode();
        Message message = returned.getMessage();
        Object parse = JSON.parseObject(new String(message.getBody()), Object.class);;
        log.info("消息发送至队列失败{}",parse);
    }
}

该方法对性能影响待考证

消费者手动确认消息

    @RabbitListener(bindings = @QueueBinding(
                            value = @Queue("que3"), // 队列
                            exchange = @Exchange(name ="change1",type = ExchangeTypes.TOPIC), // 交换机
                            key = {"a","b"}),
                    ackMode = "MANUAL"
                ) // 指定bindingKey
    public void receiver3(@Payload String msg, Channel channel, Message message) {
        try {
            /**
              *
              * @desc: 成功确认,使用此回执方法后,消息会被rabbitmq broker 删除。
             * deliveryTag:表示消息投递序号,每次消费消息或者消息重新投递后,deliveryTag都会增加。deliveryTag(唯一标识 ID):当一个消费者向 RabbitMQ 注册后,会建立起一个 Channel ,RabbitMQ 会用 basic.deliver 方法向消费者推送消息,这个方法携带了一个 delivery tag, 它代表了 RabbitMQ 向该 Channel 投递的这条消息的唯一标识 ID,是一个单调递增的正整数,delivery tag 的范围仅限于 Channel
             * multiple:是否批量确认,值为 true 则会一次性 ack所有小于当前消息 deliveryTag 的消息
              */
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);

            /**
              *
              * @desc: 失败确认
             * requeue:值为 true 消息将重新入队列
              */
            channel.basicNack(message.getMessageProperties().getDeliveryTag(),true,false);

            /**
              *
              * @desc: 拒绝消息
             * 
              */
            channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
	

论业务逻辑是否处理成功,最终都要将消息手动签收,MQ的使命不是保证客人进店了必须消费,不消费就不让走,而是客人能进来就行,哪怕是随便看看也算任务完成。区别于 自动确认:可以获取到异常日志信息。

因为MQ是中间件,本身就是辅助工具。如果强加给MQ过多压力,只会造成本身业务的畸形。使用MQ的目的就是解耦和转发,不再做多余的事情,保证MQ本身是流畅的、职责单一的即可。

消息重复消费

发送消息时携带一个唯一id,消费者消费成功后将消息唯一id存放redis或者其他中间件,再次消费消息时查看redis是否有这个key

延时队列

需要安装rabbitmq的插件,下载地址:
https://www.rabbitmq.com/community-plugins.html

    //进入容器
	docker exec -it rabbit /bin/bash
	// 查看插件目录
	rabbitmq-plugins directories -s
	

image

退出容器后,将插件拷贝到插件目录

image

进入容器开启插件

rabbitmq-plugins enable rabbitmq_delayed_message_exchange

重启容器

docker restart rabbit

使用案例:

定义队列交换机和绑定关系:

    /**
     * 定义一个延迟交换机
     */
    @Bean
    public CustomExchange customExchange() {
        Map<String, Object> map = new HashMap<>();
        map.put("x-delayed-type", "direct");
        return new CustomExchange("delay.exchange", "x-delayed-message", true, false, map);
    }
    
    /**
     * 创建延迟队列1
     */
    @Bean()
    public Queue delayQue() {
        return QueueBuilder.durable("delay.que").build();
    }

    @Bean
    public Binding bingDelayQue(Queue delayQue,CustomExchange customExchange) {
        return BindingBuilder.bind(delayQue).to(customExchange).with("a").noargs();
    }

发送消息:

    @Test
    void sendDelayMsg() {
        String exchange = "delay.exchange";
        String msg = "******************";
        rabbitTemplate.convertAndSend(exchange,"a",msg,(message)->{
            message.getMessageProperties().setHeader(MessageProperties.X_DELAY,1000L);
            return message;
        });
    }
posted @ 2022-08-19 16:31  原来是晴天啊  阅读(1301)  评论(0编辑  收藏  举报