一、安装

使用docker 进行安装

docker pull rabbitmq:management
docker run -d --name rabbitmq89 -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=123456 -p 15672:15672 -p 5672:5672 rabbitmq:management

二、RabbitMQ 消息模型

在进行RabbitMQ消息模型之前需要在父项目中添加依赖。

<!--SpringBoot的父项目-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.8.RELEASE</version>
    </parent>

    <!--统一添加依赖,子项目可以直接使用-->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--SpringBoot集成RabbitMQ-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

1、Work queues

(1)yml配置

spring:
  rabbitmq:
    host: 192.xxx.xx.134
    port: 5672
    virtual-host: /
    username: admin
    password: xxxxxx

(2)编写生产者配置

@Component
public class WorkProducer {
 @Autowired
    private RabbitTemplate rabbitTemplate;

    public void sendMessage(){
        // 发送mq消息到消息队列中
        // 参数1:交换机。 如果是简单模型或工作队列模型,这里是空字符串;使用默认的交换机
        // 参数2:路由key。如果是简单模型或工作队列模型,这里传入队列名称
        // 参数3:消息内容
        rabbitTemplate.convertAndSend("","queue-simple","你好,世界");
    }


    // 创建队列
    @Bean
    public Queue createQueue(){
        return QueueBuilder.durable("queue-simple").build();
    }
    // 如果没有自动创建队列,需要配置RabbitAdmin
    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        rabbitAdmin.setAutoStartup(true);
        return rabbitAdmin;
    }
}

(3)编写消费者

/**
 * 消息的消费者,监听队列中的消息
 * 监听哪个队列?queue-simple
 */
@Component
public class WorkConsumer {
    /**
     * @RabbitListener
     * 1、消息监听器注解,可以监听指定队列中的消息
     * 2、queuesToDeclare 指定要监听的队列,如果队列不存在会自动创建
     * 3、queues 也可以指定监听的队列,如果队列不存在不会自动创建
     */
    @RabbitListener(queuesToDeclare = @Queue(name = "queue-simple"))
    public void handlerMsg(String message) {
        System.out.println("消费者消费消息:" + message);
    }
}

2、fanout 交换机

(1)消费者

/**
 * 消费者
 * 消息模型:Publsh/Subscribe 发布订阅
 */
@Component
public class SeatConsumer {
    /**
     * 监听队列消息
     * 创建队列:queue-fanout-seat  (如果不存在就创建;如果存在不会更新)
     * 创建交换机:exchange-fanout
     * 把queue-fanout-seat队列绑定到exchange-fanout交换机
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "queue-fanout-seat",durable = "true"),
            exchange = @Exchange(name = "exchange-fanout",type = ExchangeTypes.FANOUT)
    ))
    public void seat(String message) {
        System.out.println("消费者锁定座位:" + message);
    }
}
@Component
public class MemberConsumer {
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "queue-fanout-member"),
            exchange = @Exchange(name = "exchange-fanout",type = ExchangeTypes.FANOUT)
    ))
    public void handlerMessage(String msg) {
        System.out.println("消费者添加积分:"+msg);
    }
}

(3)生产者

@Component
public class FanoutProducer {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    public void sendMessage(String userId){
        rabbitTemplate.convertAndSend("exchange-fanout","","用户"+userId+"买了票");
    }
}

3、direct 交换机

(1)消费者

@Component
public class ErrorConsumer {
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "queue-direct-error"),
            exchange = @Exchange(name = "direct-log",type = ExchangeTypes.DIRECT),
            key = "error"
    ))
    public void handlerMessage(String msg) {
        System.out.println("消费者处理错误日志消息:"+msg);
    }
}

(2)生产者

@Component
public class DirectProducer {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    public void sendLog(String exchange,String routingKey,String msg){
        rabbitTemplate.convertAndSend(exchange,routingKey,msg);
    }
}

4、topic 交换机

(1)消费者

@Component
public class C1 {
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "Q1"),
            exchange = @Exchange(name = "exchange-topic",type = ExchangeTypes.TOPIC),
            key = "*.orange.*"
    ))
    public void handlerMsg(String msg) {
        System.out.println("消费者Q1:" + msg);
    }
}
@Component
public class C2 {
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "Q2"),
            exchange = @Exchange(name = "exchange-topic",type = ExchangeTypes.TOPIC),
            key = {"*.*.rabbit","lazy.#"}
    ))
    public void handlerMsg(String msg) {
        System.out.println("消费者Q2:" + msg);
    }
}

(2)生产者

@Component
public class TopicProducer {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    public void handlerMessage(String exchange,String routingKey,String msg) {
        rabbitTemplate.convertAndSend(exchange,routingKey,msg);
    }
}

5、RPC

(1)服务端

@Component
public class Server {
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "queue-rpc"),
            exchange = @Exchange(name = "exchange-rpc",type = ExchangeTypes.TOPIC),
            key = "rpc"
    ))
    public String handlerMsg(String msg) {
        System.out.println("服务端获取的数据:" + msg);
        return "服务端返回的数据";
    }
}

(2)客户端

@Component
public class ClientProducer {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    public void sendMsg(String exchange,String routingKey,String msg) {
        Object serverReturn = rabbitTemplate.convertSendAndReceive(exchange, routingKey, msg);
        System.out.println("serverReturn = " + serverReturn);
    }
}

6、PC

(1)服务端消息可靠性保证

1.yml配置

spring:
  rabbitmq:
    host: 192.xxx.xx.134
    port: 5672
    virtual-host: /
    username: admin
    password: xxxxxx
    publisher-confirm-type: correlated

2.配置类

PostConstruct 注释用于在依赖关系注入完成之后需要执行的方法上,以执行任何初始化。此方法必须在将类放入服务之前调用。因为我在这个类中注入了其它服务,因此我使用这个注解在其它服务加载完成后进行初始化.

@Component
public class ConfirmProducer {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private RabbitConfirm rabbitConfirm;
    @PostConstruct
    public void init(){
        rabbitTemplate.setConfirmCallback(rabbitConfirm);
        // true 表示消息未到达队列时会把消息返回给生产者;false会直接丢弃消息
        rabbitTemplate.setMandatory(true);
        rabbitTemplate.setReturnCallback(rabbitConfirm);
    }
    public void producerConfirm() {
        rabbitTemplate.convertAndSend("exchange-topic-confirm","confirm.msg","确认消息");
    }
}
@Component
public class RabbitConfirm implements
        RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        System.out.println("correlationData = " + correlationData);
        System.out.println("ack = " + ack); //false
        System.out.println("cause = " + cause);
        if (ack) {
            System.out.println("消息发送到交换机确认成功!");
        } else {
            System.out.println("消息发送到交换机失败,接下来根据业务做处理,比如:重发");
        }
    }
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        // 消息无法路由到队列时候,再这里可以获取消息内容
        System.out.println("=======消息回退:ReturnCallback=======");
        System.out.println("消息内容 = " + new String(message.getBody()));
        System.out.println("响应编码 = " + replyCode);
        System.out.println("消息无法路由的原因 = " + replyText);
        System.out.println("交换机 = " + exchange);
        System.out.println("路由key = " + routingKey);
    }
}

(2)消费端消息可靠性保证

1.yml配置

spring:
  rabbitmq:
    host: 192.168.12.134
    port: 5672
    username: admin
    password: 123456
    virtual-host: /
    listener:
      simple:
        retry:
          # 开启重试次数的限制,解决消费者出现异常死循环问题
          enabled: true
          # 最大重试次数
          max-attempts: 3
        # 开启消费者手动确认模式 (channel.bacisAck)
        acknowledge-mode: manual

2.配置类

死信配置:

@Component
public class DLQConsumer {
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "queue-dlx"),
            exchange = @Exchange(name = "exchange-dlx",type = ExchangeTypes.TOPIC),
            key = "dlx.#"
    ))
    public void handlerMsg(String msg,Channel channel, Message message) throws IOException {
        System.out.println("死信队列处理消息:" + msg);
        // 消费后消息还在,因为没有确认。
    }
}

消费者配置:

@Component
public class ConfirmConsumer {
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "queue-confirm",arguments = {
                    @Argument(name = "x-dead-letter-exchange",value = "exchange-dlx"),
                    @Argument(name = "x-dead-letter-routing-key",value = "dlx.ooo"),
                    @Argument(name = "x-message-ttl",value = "100000",type = "java.lang.Long"),
                    @Argument(name = "x-max-length",value = "3",type = "java.lang.Long"),
            }),
            exchange = @Exchange(name = "exchange-confirm",type = ExchangeTypes.TOPIC),
            key = "confirm.*"
    ))
    public void handlerMsg(String msg, Channel channel, Message message) throws IOException {
        try {
            //int i=1/0;
            System.out.println("消息:" + msg);
            // 消费者手动确认消息消费; 参数1:消息id;参数2:是否批量确认
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
        } catch (Exception e) {
            e.printStackTrace();
            // 需求:消息消费失败了,希望把消息重投到队列中,再次进行消费,如果还是消费失败就拒绝消息
            // 获取消息是否是重投的标记
            Boolean redelivered = message.getMessageProperties().getRedelivered();
            if (redelivered) {
                // 重投消息:可以拒绝不处理..
                channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
                System.out.println("消息重投后消费消息失败,拒绝处理");
            } else {
                // 未重投过:重投
                channel.basicNack(message.getMessageProperties().getDeliveryTag(),true,true);
                System.out.println("消息消费异常,进行重投!");
            }
        }
    }
}

三、MQ实现延迟消息

MQ实现延迟消息有很多种方法,这里就介绍最简单的一种,那就是通过死信队列实现延迟消息。实现思路:创建普通队列和私信队列两个队列,然后创建一个死信消费者,但是不创建普通队列消费者。这样过期之间一到普通队列里的消息就会跑到死信队列中,便可以实现延迟消费了。

(1)生产者

1、添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

2、yml配置

spring:
  rabbitmq:
    host: 192.168.12.134
    port: 5672
    username: admin
    password: 123456
    virtual-host: /

3、配置类

配置类里面有死信队列和普通队列,在后面写消费者的时候绑定的交换机必须是这里写的死信交换机。

package com.wnxy.sk.product.config;

@Configuration
public class RabbitConfig {

    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }

    // 交换机
    @Bean("orderExchange")
    public Exchange orderExchange() {
        return new TopicExchange("order-exchange", true, false);
    }
    // 队列
    @Bean("ttlOrderQueue")
    public Queue ttlOrderQueue() {
        Map params = new HashMap();
        params.put("x-message-ttl", 20000);
        params.put("x-dead-letter-exchange", "order-dead-letter-exchange");
        params.put("x-dead-letter-routing-key", "dead.letter.order");
        return new Queue("ttl-order-queue", true, false, false, params);
    }
    // 绑定
    @Bean
    public Binding bindTTLOrderQueue(
            @Qualifier("ttlOrderQueue") Queue queue,
            @Qualifier("orderExchange") Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange)
                .with("order.#").noargs();
    }

    // 死信交换机
    @Bean("orderDeadLetterExchange")
    public Exchange orderDeadLetterExchange() {
        return new TopicExchange("order-dead-letter-exchange", true, false);
    }
    // 死信队列
    @Bean("orderDeadLetterQueue")
    public Queue orderDeadLetterQueue() {
        return new Queue("order-dead-letter-queue", true, false, false);
    }
    // 死信绑定
    @Bean
    public Binding bindOrderDeadLetterQueue(
            @Qualifier("orderDeadLetterQueue") Queue queue,
            @Qualifier("orderDeadLetterExchange") Exchange exchange) {
        return BindingBuilder.bind(queue)
                .to(exchange)
                .with("dead.letter.order").noargs();
    }

    @Bean
    public RabbitAdmin rabbitAdmin(
            ConnectionFactory factory,
            Exchange orderExchange,
            Exchange orderDeadLetterExchange,
            Queue ttlOrderQueue,
            Queue orderDeadLetterQueue) {
        RabbitAdmin rabbitAdmin = new RabbitAdmin(factory);
        rabbitAdmin.setAutoStartup(true);
        rabbitAdmin.declareExchange(orderExchange);
        rabbitAdmin.declareExchange(orderDeadLetterExchange);
        rabbitAdmin.declareQueue(ttlOrderQueue);
        rabbitAdmin.declareQueue(orderDeadLetterQueue);
        return rabbitAdmin;
    }

}

4、消费者

这里的消费者发送的队列和交换机还是普通的队列和交换机

@Component
public class OrderProducer {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void sendOrder(OrderPayDto orderDto){
        rabbitTemplate.convertAndSend(
                "order-exchange","order."+orderDto.getOrderSn(),orderDto);
    }
}

(2)消费者

1、依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

2、yml配置

spring:
  rabbitmq:
    host: 192.168.12.134
    port: 5672
    username: admin
    password: 123456
    virtual-host: /
    listener:
      simple:
        retry:
          max-attempts: 3 #设置最大尝试消费次数
          enabled: true #开启重试
        # 开启消费者手动确认模式 (channel.bacisAck)
        acknowledge-mode: manual

3、配置类

@Bean
public MessageConverter messageConverter(){
    return new Jackson2JsonMessageConverter();
}

4、消费者

这里绑定的队列和交换机是死信队列和死信交换机

@Component
@Slf4j
public class OrderConsumer {
    @Autowired
    private IOrderService orderService;
    @Autowired
    private RedisTemplate redisTemplate;

    @RabbitHandler
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = "order-dead-letter-queue", durable = "true",
                    autoDelete = "false", exclusive = "false"),
            exchange = @Exchange(value = "order-dead-letter-exchange", durable = "true",
                    autoDelete = "false", type = ExchangeTypes.TOPIC),
            key = "dead.letter.order")
    )
    public void handleOrder(@Payload OrderPayDto orderPayDto, Channel channel, Message message) {
        log.info("消费死信队列中的超时消息 {}",orderPayDto);
        //判断从mq中获取到的超时数据,订单的状态是否为待支付,如果为待支付状态,则更新订单失效。
        OrderVo orderVo = orderService.findByOrderSn(orderPayDto.getOrderSn());
        if (orderVo.getStatus().equals(OrderConstant.ORDER_STATUS_UNPAY)) {//0待付款
            log.info("订单失效,修改订单状态为5,无效订单");
            LambdaUpdateWrapper<Order> updateWrapper = Wrappers.lambdaUpdate();
            updateWrapper.set(Order::getStatus, OrderConstant.ORDER_STATUS_INVALID);//已失效
            updateWrapper.eq(Order::getOrderSn, orderPayDto.getOrderSn());
            orderService.update(updateWrapper);
            //将redis中扣减的库存,还原
            redisTemplate.opsForList().leftPush(
                    "seckill:count:" + orderPayDto.getPromotionId(),
                    orderPayDto.getProductId());
        }
        try {
            //手动应答
            log.info("channel.basicAck start");
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            log.info("channel.basicAck end");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

四、其它配置

1、生产者创建交换机、队列

(1)配置类

@Configuration
public class RabbitConfig {
    /**
     * 创建队列: queue-confirm
     */
    @Bean("confirmQueue")
    public Queue createConfirmQueue() {
        Map<String,Object> params = new HashMap<>();
        params.put("x-message-ttl",50000);
        params.put("x-dead-letter-exchange","exchange-dlx");
        params.put("x-dead-letter-routing-key","dlx.ooo");
        return new Queue("queue-confirm",true,false,false,params);
    }
    /**
     * 创建交换机:exchange-confirm
     */
    @Bean("exchangeConfirm")
    public Exchange exchangeConfirm(){
        return new TopicExchange("exchange-confirm",true,false);
    }
    /**
     * 绑定:把队列queue-confirm绑定到exchange-confirm
     * @Qualifier
     *   1、只会根据名称去容器中查找改名称对应的对象
     *   2、默认会根据类型去容器中查找
     */
    @Bean
    public Binding bindConfirmQueue(
            @Qualifier("exchangeConfirm") Exchange exchange,
            @Qualifier("confirmQueue") Queue queue) {
        return BindingBuilder
                .bind(queue)
                .to(exchange).with("confirm.#").noargs();
    }
    /**
     * 死信交换机:exchange-dlx
     */
    @Bean("dlxExchange")
    public Exchange dlxExchange(){
        return new TopicExchange("exchange-dlx",true,false);
    }
    /**
     * 死信队列:queue-dlx
     */
    @Bean("dlxQueue")
    public Queue createDLXQueue() {
        return new Queue("queue-dlx",true,false,false);
    }
    /**
     *  绑定:把死信队列queue-dlx绑定到死信交换机exchange-dlx
     */
    @Bean
    public Binding bindDlxQueue(
            @Qualifier("dlxExchange") Exchange exchange,
            @Qualifier("dlxQueue") Queue queue) {
        return BindingBuilder
                .bind(queue)
                .to(exchange).with("dlx.#").noargs();
    }
}

2、发送接收对象

(1)生产者

1.配置类

@Configuration
public class RabbitMQConfig {

    // 发送对象时自动转为json的配置类
    @Bean
    public MessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }


}

(2)消费者

下面这个注解写在要接收的对象上面

@Payload

添加配置类

@Configuration
public class RabbitMQConfig {

    // 发送对象时自动转为json的配置类
    @Bean
    public MessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }


}