RabbitMq安装与使用

RabbitMq实战

概念

RabbitMq安装与使用_第1张图片

Linux安装

docker run -d --name rabbitmq -p 5671:5671 -p 5672:5672 -p 4369:4369 -p 25672:25672 -p 15671:15671 -p 15672:15672 rabbitmq:management

RabbitMq安装与使用_第2张图片

RabbitMq运行机制

RabbitMq安装与使用_第3张图片

Exchange类型

RabbitMq安装与使用_第4张图片

RabbitMq安装与使用_第5张图片

快速开始-整合SpringBoot

pom.xml

 <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-amqpartifactId>
        dependency>

主启动类

/**
 * 1.引入 
 *             org.springframework.boot
 *             spring-boot-starter-amqp
 *         
 *     给容器中配置了 CachingConnectionFactory RabbitConnectionFactoryBean RabbitTemplate RabbitMessagingTemplate AmqpAdmin
 * 2.配置yaml
 * 3.主启动类 @EnableRabbit
 * 4.监听消息: 使用@RabbitListener,前提是开启@EnableRabbit,并且队列必须存在
 *      @RabbitListener: 用于类上或者方法上(监听哪些队列即可)
 *      @RabbitHandler: 用在方法上(重载区分接收不同的消息)
 *
 */
@SpringBootApplication
@EnableRabbit
public class GulimallOrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(GulimallOrderApplication.class, args);
    }

}

application.yaml

spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest

配置序列化为Json格式

package com.lin.gulimall.order.config;

import org.springframework.amqp.support.converter.Jackson2JavaTypeMapper;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author Created by Lin Weihong
 * @date on 2022/9/11 17:16
 */
@Configuration
public class MyRabbitConfig {
    @Bean
    public MessageConverter messageConverter(){
        //使用json序列化
        return new Jackson2JsonMessageConverter();
    }
}

操作代码

发送消息

@SpringBootTest
@Slf4j
class GulimallOrderApplicationTests {

    @Resource
    AmqpAdmin amqpAdmin;
    @Resource
    RabbitTemplate rabbitTemplate;

    /**
     * 1.如何创建Exchange Queue Binding
     *      1)、AmqpAdmin进行创建
     *      2)、
     * 2.如何收发消息
     */
    @Test
    void testCreateExchange(){
//        public DirectExchange(String name, boolean durable, boolean autoDelete, Map arguments) {
        DirectExchange directExchange = new DirectExchange("hello-java-exchange", true, false);
        amqpAdmin.declareExchange(directExchange);
        log.info("******Exchange创建成功{}", "hello-java-exchange");
    }
    @Test
    void testCreateQueue() {
//        	public Queue(String name, boolean durable,
//        	boolean exclusive //是否排他,若为true,就是只能被一个声明的连接使用,工作中都为false
//        	, boolean autoDelete,@Nullable Map arguments) {
        Queue queue = new Queue("hello-java-queue",true,false,false);
        amqpAdmin.declareQueue(queue);
        log.info("******Queue创建成功{}", "hello-java-queue");
    }

    /**
     * 队列与交换机绑定
     */
    @Test
    void testCreateBinding() {
        /**
         * 	public Binding(
         * 	String destination, 目的地
         * 	DestinationType destinationType,目的地类型
         * 	String exchange,交换机
         * 	String routingKey,路由键
         *  @Nullable Map arguments) 自定义参数
         *  {
         *  将exchange指定的交换机和destination目的地进行绑定,使用routingKey作为路由键
         */
        Binding binding = new Binding("hello-java-queue", Binding.DestinationType.QUEUE, "hello-java-exchange", "hello.java", new HashMap<>());
        amqpAdmin.declareBinding(binding);
//        amqpAdmin.removeBinding(binding);
        log.info("绑定成功exchange和queue");
    }

    /**
     * 测试发送消息
     */
    @Test
    void testSendMessage(){
        //发送对象,会使用序列化机制,必须实现Serializable接口
        OrderReturnReasonEntity orderReturnReasonEntity = new OrderReturnReasonEntity();
        orderReturnReasonEntity.setId(1L);
        orderReturnReasonEntity.setName("你好测试rabbit");
        String message = "hello";
//        rabbitTemplate.convertAndSend("hello-java-exchange","hello.java",message);
//        rabbitTemplate.convertAndSend("hello-java-exchange","hello.java",orderReturnReasonEntity);//默认是JDK序列化,rO0ABXNyADVjb20ubGluLmd1bGltYWxsLm9yZGVyLmVudGl0eS5PcmRlclJldHVyblJlYXNvbkVudGl0eQAAAAAAAAABAgAFTAAKY3JlYXRlVGltZXQAEExq...

        //用Json传递对象,可以手动进行转换,也可以看MyRabbitConfig配置的,默认转化改为json
//        String obj = JSON.toJSONString(orderReturnReasonEntity);
//        rabbitTemplate.convertAndSend("hello-java-exchange","hello.java",obj);

        rabbitTemplate.convertAndSend("hello-java-exchange","hello.java",orderReturnReasonEntity);

    }

}
@RestController
public class RabbitController {
    @Resource
    RabbitTemplate rabbitTemplate;
    @GetMapping("/sendMq")
    public String sendMq(Integer sum){
        for (int i = 0; i < sum; i++) {
            if (i % 2 == 0) {
                OrderReturnReasonEntity orderReturnReasonEntity = new OrderReturnReasonEntity();
                orderReturnReasonEntity.setId(1L);
                orderReturnReasonEntity.setName("你好测试rabbit-->" + i);
                //最后一个参数是设置消息的唯一ID,方便我们消息投递失败的回调展示的Id更容易被我们定位
                rabbitTemplate.convertAndSend("hello-java-exchange", "hello.java", orderReturnReasonEntity,new CorrelationData(UUID.randomUUID().toString()));
            } else {
                OrderEntity orderEntity = new OrderEntity();
                orderEntity.setOrderSn(UUID.randomUUID().toString());
                rabbitTemplate.convertAndSend("hello-java-exchange", "hello.java", orderEntity);
            }
        }
        return "OK";
    }
}

接收消息

  /**
     * queues: 要接听的队列
     *
     *  message.getClass(): org.springframework.amqp.core.Message
     *  T<发送的消息类型> OrderReturnReasonEntity content
     *  Channel channel: 当前传输消息的通道
     */
    @RabbitListener(queues = {"hello-java-queue"})
    public void receiveMessage(Message message, OrderReturnReasonEntity content, Channel channel) {
        byte[] body = message.getBody();
        //消息头信息
        MessageProperties messageProperties = message.getMessageProperties();
//        System.out.println("接收到的消息为: " + message +" ====> " + message.getClass());
        System.out.println("接收到的消息为: " + message +" 内容 " + content);
    }

多个服务器监听同一个Queue场景

RabbitMq安装与使用_第6张图片

生产者发送十条消息:

RabbitMq安装与使用_第7张图片

结果:

RabbitMq安装与使用_第8张图片

RabbitMq安装与使用_第9张图片

还有三条在单元测试里,不影响,实验问题

业务耗时场景

模拟服务端耗时业务

RabbitMq安装与使用_第10张图片

结果:

RabbitMq安装与使用_第11张图片

消息确认机制

RabbitMq安装与使用_第12张图片

ConfirmCallback

RabbitMq安装与使用_第13张图片

配置

application.yml

spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    #    publisher-confirms: true
    #  开启发送端确认,上面这个过时了,用下面这个
    publisher-confirm-type: correlated

MyRabbitConfig.java

package com.lin.gulimall.order.config;

import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JavaTypeMapper;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

/**
 * @author Created by Lin Weihong
 * @date on 2022/9/11 17:16
 */
@Configuration
public class MyRabbitConfig {

    @Resource
    RabbitTemplate rabbitTemplate;

    /**
     * 定制RabbitTemplate
     * 1.在application.yaml里面开启
     * spring:
     *   rabbitmq:
     *     publisher-confirm-type: correlated
     * 2.设置确认回调
     */
    @PostConstruct //MyRabbitConfig对象创建完成以后,执行这个方法
    public void initRabbitTemplate() {
        //设置确认回调

        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /**
             * 1.只要消息到达Broker就会ack=true
             * @param correlationData 当前消息的唯一关联数据(这个是消息的唯一id)
             * @param ack 消息是否成功收到
             * @param cause 失败的原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                System.out.println("确认回调.....["+correlationData+"]==>ack["+ack+"]==>["+cause+"]");
            }
        });
    }
}

效果

即使没有被消费也会触发,因为这是从publish->Exchange的 和消费无关

RabbitMq安装与使用_第14张图片

ReturnCallback

RabbitMq安装与使用_第15张图片

配置

application.yaml

spring:
  rabbitmq:
    publisher-returns: true
    # 只要抵达队列,以异步方式优先回调这个returnConfirm
    template:
      mandatory: true

MyRabbitConfig.java

  //设置消息抵达队列的确认回调
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            /**
             * 注意!!!!
             * 触发时机:只要消息没有投递给指定队列,就会触发这个失败回调
             * @param message 投递失败的消息的详细信息
             * @param replyCode 回复的状态码
             * @param replyText 回复的文本内容
             * @param exchange 当时这个消息发给哪个交换机
             * @param routingKey 当时这个消息用的哪个路由键
             */
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                
                System.out.println("失败的消息.....["+message+"]==>replyCode["+replyCode+"]==>replyText["+replyText+"]==>exchange["+exchange+"]==>routingKey["+routingKey+"]");
            }
        });

效果

Ack消息确认机制

RabbitMq安装与使用_第16张图片

配置

spring:
  rabbitmq:
    # 手动确认ack,不要自动ack
    listener:
      simple:
        acknowledge-mode: manual  //一定要开启,否则都会自动ack
     @RabbitHandler
    public void receiveMessage(Message message, OrderReturnReasonEntity content, Channel channel) throws InterruptedException {
        byte[] body = message.getBody();
        //消息头信息
        System.out.println("接收到的消息为:" + content);
        MessageProperties messageProperties = message.getMessageProperties();
        //channel内按顺序递增
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        System.out.println("deliveryTag ====> " + deliveryTag);
        //签收货物,非批量签收
        try {
            if (deliveryTag % 2 == 0) {//模拟宕机,因为debug强制停止进程以后,还是会走完
                //收货
                channel.basicAck(deliveryTag, false);
                System.out.println("签收了货物: " + deliveryTag);
            } else {
                //退货 requeue如果为false就是丢弃,为true就是发回服务器
//               void basicNack(long deliveryTag, boolean multiple, boolean requeue 是否重新放入队列)
                channel.basicNack(deliveryTag,false,true);
//                void basicReject(long deliveryTag, boolean requeue) throws IOException;
//                channel.basicReject();//这个和上面Nack都可以用,参数区别,下面不能批量操作
                System.out.println("没有签收货物: " + deliveryTag);
            }
        } catch (IOException e) {
            //网络异常
            e.printStackTrace();
        }
        //        Thread.sleep(3000);
//        System.out.println("接收到的消息为: " + message +" ====> " + message.getClass());
        System.out.println("消息处理结束:" + content.getName());
    }
    }

效果

小结

/**
 * 定制RabbitTemplate
 * 1.服务收到消息就回调
 *      1.在application.yaml里面开启
 *          spring:
 *              rabbitmq:
 *                  publisher-confirm-type: correlated
 *      2.设置确认回调
 * 2.消息抵达队列就回调
 *      1.配置yaml
 *          # 开启发送端消息抵达队列的确认
 *          publisher-returns: true
 *          # 只要抵达队列,以异步方式优先回调这个returnConfirm
 *          template:
 *            mandatory: true
 *      2.设置消息抵达队列的确认回调
 *
 * 3.消费端确认 (保证每个消息都被正确消费,此时才可以broker删除这个消息)
 *      1.默认是自动ack,只要消息接收到,客户端回自动确认,服务端就会移除这个消息
 *       问题:如果我们收到了很多消息,这个时候处理完前几条以后,后面的还没处理就宕机了,会导致所有消息都丢失,连同没被消费的
 *       解决:手动确认模式,只要没有明确告诉mq货物被签收,即没有ACK,消息就会一直是unchecked状态,这个时候即使consumer宕机,所有unchecked的消息
 *            都会重新进入队列当中,下次启动新的Consumer连接进来会继续消费这些消息
 *      2.如何签收
 *         签收 业务成功就应该签收 channel.basicAck(deliveryTag, false);
 *         拒签 业务失败 channel.basicNack(deliveryTag,false,true);
 *
 */

你可能感兴趣的:(java-rabbitmq,rabbitmq,java)