谷粒商城-分布式高级篇[商城业务-RabbitMQ]

  1. 谷粒商城-分布式基础篇【环境准备】
  2. 谷粒商城-分布式基础【业务编写】
  3. 谷粒商城-分布式高级篇【业务编写】持续更新
  4. 谷粒商城-分布式高级篇-ElasticSearch
  5. 谷粒商城-分布式高级篇-分布式锁与缓存
  6. 项目托管于gitee

参照本人RabbitMQ笔记:项目写完会发布

谷粒商城-分布式高级篇[商城业务-RabbitMQ]_第1张图片

一、环境部署


1、Docker安装RabbitMQ

[root@hgwtencent ~]# 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
[root@hgwtencent ~]# docker update rabbitmq --restart=always
rabbitmq
  • 4369,25672 : Erlang发现&集群端口
  • 5672,5671 :AMQP端口
  • 15672 :web管理后台端口
  • 61613,61614 :STOMP协议端口
  • 1883,8883 :MQTT协议端口

登录 ip:15672 访问 web管理后台端口:

账号: guest
密码: guest

谷粒商城-分布式高级篇[商城业务-RabbitMQ]_第2张图片

2、RabbitMQ演示


这里讲师的意图就是让大家理解明白 交换机的三种模式,不做记录。

  • Direct(直接)[点对点] :(消息中的Routing key)==(交换器和队列绑定的队列中的Routing key)

  • Fanout (扇出)[发布订阅] :消息发送给交换器绑定的所有队列

  • Topic(主题)[发布订阅] :

    • (消息中的Routing key)like(交换器和队列绑定的队列中的Routing key)
    • * (星号)可以代替一个单词
    • # (井号)可以替代零个或多个单词

2.1、交换机、队列的创建,以及两者绑定的演示


  1. 创建一个交换机

  1. 创建一个队列

  1. 交换机绑定队列

谷粒商城-分布式高级篇[商城业务-RabbitMQ]_第3张图片

二、SpringBoot 整合 RabbitMq

2.1、SpringBoot 整合 RabbitMq


  • 使用RabbitMQ

    • 1、引入amqp场景启动器;RabbitAutoConfiguration就会自动生效

    • 2、给容器中自动配置了RabbitTemplate、AmqpAdmin、CachingConnectionFactory、RabbitMessagingTemplate
      所有的属性都是在这绑定的:spring.rabbitmq

      @ConfigurationProperties(prefix = "spring.rabbitmq")
      public class RabbitProperties {}
      
    • 3、给配置文件中配置 spring.rabbitmq 信息

    • 4、@EnableRabbit 开启功能

1、导入amqp场景启动器依赖

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

2、添加配置

spring:
  rabbitmq:
    host: 124.222.223.222
    username: guest
    port: 5672
    virtual-host: /
    password: guest

3、主启动类添加 @EnableRabbit注解

@EnableRabbit
@SpringBootApplication
public class GulimallOrderApplication {

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

}

2.2、AmqpAdmin


使用AmqpAdmin创建Exchange、Queue、Binding

2.2.1、创建交换机

/**
 * 创建交换机
 * TopicExchange
 * FanoutExchange
 * DirectExchange
 */
@Test
public void createExchange(){

    /**
     * String name 交换机名字
     * boolean durable  是否持久化
     * boolean autoDelete 是否自动删除
     * Map arguments 参数
     */
    DirectExchange directExchange = new DirectExchange("hello-java-exchange",true,false);
    amqpAdmin.declareExchange(directExchange);
    log.info("Exchange[{}]创建成功",directExchange.getName());
}

谷粒商城-分布式高级篇[商城业务-RabbitMQ]_第4张图片

2.2.2、创建队列

/**
 * 创建队列
 */
@Test
public void createQueue(){
    /**
     * Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, Map arguments)
     *  String name,
     *  boolean durable,   是否持久化
     *  boolean exclusive,   是否排他:只能同时被一个交换器连接
     *  boolean autoDelete   是否自动删除
     *  Map arguments 携带参数
     */
    Queue queue = new Queue("hello-java-queue",true,false,false);
    amqpAdmin.declareQueue(queue);
    log.info("Queue[{}]创建成功",queue.getName());
}

谷粒商城-分布式高级篇[商城业务-RabbitMQ]_第5张图片

2.2.3、创建绑定

/**
 * 创建绑定
 */
@Test
public void createBinding() {
    /**
     * Binding(String destination, Binding.DestinationType destinationType, String exchange, String routingKey, Map arguments)
     *  String destination, 目的地(队列或主题)
     *  Binding.DestinationType destinationType, 目的地类型(队列或主题)
     *  String exchange, 交换机
     *  String routingKey,  路由键
     *  Map arguments
     * 将exchange指定的交换机和destination目的地进行绑定,使用routingKey作为指定的路由键
     */
    Binding binding = new Binding("hello-java-queue",
            Binding.DestinationType.QUEUE,
            "hello-java-exchange",
            "hello.java",
            null);
    amqpAdmin.declareBinding(binding);
    log.info("Binding创建成功");
}

2.3、RabbitTemplate


使用RabbitTemplate 发送、接收消息。

2.3.1、发送消息


/**
* convertAndSend(String exchange, String routingKey, Object object)
* String exchange, 交换器
* String routingKey,   路由值
* Object object    消息,如果发送的消息是对象,我们会使用序列化机制将对象写出去。
 */
@Test
public void sendMsgTest(){
  OrderReturnApplyEntity orderReturnApplyEntity = new OrderReturnApplyEntity();
  orderReturnApplyEntity.setId(1L);
  orderReturnApplyEntity.setCreateTime(new Date());
  orderReturnApplyEntity.setReturnName("哈哈哈");

  //1、发送消息,如果发送的消息是个对象,我们会使用序列化机制,将对象写出去。对象必须实现Serializable
  String msg = "Hello,World!";
  rabbitTemplate.convertAndSend("hello-java-exchange", "hello.java", orderReturnApplyEntity);
  log.info("消息[{}]发送成功!",msg);
}
  1. 发送消息,如果发送的消息是个对象,我们会使用序列化机制,将对象写出去。对象必须实现Serializable
  2. 或者我们想要发送的对象序列化为JSON

注意

配置MyRabbitConfig,让发送的对象类型的消息,可以是一个json

添加“com.atguigu.gulimall.order.config.MyRabbitConfig”类,代码如下:

package com.atguigu.gulimall.order.config;

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;

@Configuration
public class MyRabbitConfig {

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

谷粒商城-分布式高级篇[商城业务-RabbitMQ]_第6张图片

配置MyRabbitConfig后,发送接收到的数据被序列化为JSON

2.3.2、RabbitListener&RabbitHandler接收消息


  • @RabbitListener: 类+方法上(监听哪些队列即可)

  • @RabbitHandler: 标在方法上(重载区分不同的消息)

2.3.2.1、@RabbitListener

监听消息:使用@RabbitListener; 主启动类必须有@EnableRabbit

  • @interface RabbitListener
    String[] queues() default {}; 监听的队列

  • 参数列表:

    • 1、Message message:原生消息详细信息。头+体
    •  2、T<发送的消息类型> :如:OrderReturnReasonEntity content; 指定接收到的消息的类型
      
    •  3、Channel channel :当前传输数据的信道
      
/**
 * @interface RabbitListener
 *     String[] queues() default {}; 监听的队列
 * 参数列表:
 *      1、Message message:原生消息详细信息。头+体
 *      2、T<发送的消息类型> :如:OrderReturnReasonEntity content
 *      3、Channel channel :当前传输数据的信道
 */
@RabbitListener(queues = {"hello-java-queue"})
public void recieveMessage(Message message,
                           OrderReturnReasonEntity content,
                           Channel channel){
    // 消息头属性
    MessageProperties properties = message.getMessageProperties();
    // 消息体属性
    byte[] body = message.getBody();
    System.out.println("接收到消息:" + message +"内容:"+content);
}
2.3.2.2、RabbitHandler接收消息

采用在类上加 @RabbitListener 注解,标识监听哪个消息队列。在方法上添加@RabbitHandler注解,重载区分不同的消息。

  • @RabbitListener: 加在类上监听哪个消息队列

  • @RabbitHandler: 标在方法上(重载区分不同的消息)

添加“com.atguigu.gulimall.order.controller.RabbitController”类,代码如下:

@Slf4j
@RestController
public class RabbitController {
    
    @Autowired
    RabbitTemplate rabbitTemplate;

    @GetMapping("/sendMq")
    public String sendMq(@RequestParam(value = "num",defaultValue = "10") Integer num){
        for (int i = 0; i < num; i++) {
            if (i%2 == 0) {
                OrderReturnApplyEntity orderReturnApplyEntity = new OrderReturnApplyEntity();
                orderReturnApplyEntity.setId(1L);
                orderReturnApplyEntity.setCreateTime(new Date());
                orderReturnApplyEntity.setReturnName("哈哈哈");
                rabbitTemplate.convertAndSend("hello-java-exchange", "hello.java", orderReturnApplyEntity);
            } else {
                OrderEntity entity = new OrderEntity();
                entity.setOrderSn(UUID.randomUUID().toString());
                rabbitTemplate.convertAndSend("hello-java-exchange", "hello.java", entity);
            }
        }
        return "ok";
    }
}

修改“com.atguigu.gulimall.order.service.impl.OrderItemServiceImpl”类,代码如下:

@Service("orderItemService")
@RabbitListener(queues = {"hello-java-queue"})
public class OrderItemServiceImpl extends ServiceImpl<OrderItemDao, OrderItemEntity> implements OrderItemService {

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<OrderItemEntity> page = this.page(
                new Query<OrderItemEntity>().getPage(params),
                new QueryWrapper<OrderItemEntity>()
        );

        return new PageUtils(page);
    }

    /**
     * @interface RabbitListener
     *     String[] queues() default {}; 监听的队列
     * 参数列表:
     *      1、Message message:原生消息详细信息。头+体
     *      2、T<发送的消息类型> :如:OrderReturnReasonEntity content
     *      3、Channel channel :当前传输数据的信道
     *
     * Queue:可以很多人来监听,但一条消息只能被一个消费者接收。
     * 场景:
     *      1)、订单服务启动多个:同一个消息,只能有一个客户端收到
     *      2)、只有一个消息完全处理完,方法运行结束,我们就可以接收到下一个消息
     */
    @RabbitHandler
    public void recieveMessage(OrderReturnApplyEntity content){
        System.out.println("接收到的消息:" + content);
    }

    @RabbitHandler
    public void recieveMessage2(OrderEntity content){
        System.out.println("接收到的消息:" + content);
    }
}

谷粒商城-分布式高级篇[商城业务-RabbitMQ]_第7张图片

2.4.1、可靠投递-发送端确认


  • 服务器收到消息 p->b:ConfirmCallback
    1. pring.rabbitmq.publisher-confirms=true
    2. 设置确认回调 ConfirmCallback
  • 消息抵达队列就回调 e->q:ReturnCallback
    1. spring.rabbitmq.publisher-returns: true
    2. spring.rabbitmq.template.mandatory: true
    3. 设置确认回调 ReturnCallback

第一步、修改配置文件

spring:
  rabbitmq:
    publisher-confirms: true  # 开启发送端确认
    publisher-returns: true   # 开启发送端消息抵达队列的确认
    template:
      mandatory: true         # 只要抵达队列以异步方式优先回调这个returnconfirm

第二步、定制 RabbitTemplate

package com.atguigu.gulimall.order.config;

@Configuration
public class MyRabbitConfig {

    @Autowired
    RabbitTemplate rabbitTemplate;

    /**
     * 使用JSON序列化机制,进行消息转换
     * @return
     */
    @Bean
    public MessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }


    /**
     * 定制 RabbitTemplate
     *  1、服务器收到消息就会回调
     *      1)、spring.rabbitmq.publisher-confirms=true
     *      2)、设置确认回调 ConfirmCallback
     *  2、消息正确抵达队列进行回调
     *      1)、spring.rabbitmq.publisher-returns: true
     *      2)、spring.rabbitmq.template.mandatory: true
     *      3)、设置确认回调 ReturnCallback
     *  3、消费端确认(保证每个消息被正确消费,此时才可以Broker删除这个消息)
     */
    @PostConstruct  //在 MyRabbitConfig 对象创建完成以后,执行这个方法
    public void initRabbitTemplate() {
        // 设置消息确认回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /**
             * 1、只要消息抵达Broker就会返回ack=true
             * @param correlationData 当前消息的唯一关联数据(这个是消息的唯一id)
             * @param b 消息是否成功还是失败
             * @param s 失败的原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean b, String s) {
                System.out.println("confirm...[correlationData:"+correlationData+"];[ack="+b+"];[cause="+s+"]");
            }
        });

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

2.4.2、可靠投递-消费端确认


谷粒商城-分布式高级篇[商城业务-RabbitMQ]_第8张图片

消费端确认(保证每个消息被正确消费,此时才可以保证Broker删除这个消息)

  • 1)、默认是自动确认的,只要消息接收到,客户端会自动确认,服务端就会移除这个消息

    • 问题:
      •          1、收到消息,但没成功处理自动回复给服务器ACK,发生消息丢失。
        
      •          2、我们收到很多消息,自动回复给服务器ack,只有一个消息处理成功,宕机了。发生消息丢失
        
    • 解决:手动确认,每次处理完手动确认返回。
  • 手动确认收货(ack)

    1. spring.rabbitmq.listener.simple.acknowledge-mode=manual
    2. 手动确认返回
      • channel.basicAck(deliveryTag,false);签收;业务成功完成就应该签收
      • channel.basicNack(deliveryTag,false,true);拒签:业务失败,拒签

**第一步、**修改配置文件,修改为:手动确认

spring:
  rabbitmq:
    listener:
      simple:
        acknowledge-mode: manual  # 开启手动ack消息模式

**第二步、**业务代码中进行手动签收

@RabbitHandler
public void recieveMessage(Message message,OrderReturnApplyEntity content, Channel channel){
    System.out.println("接收到的消息:" + content);
    long deliveryTag = message.getMessageProperties().getDeliveryTag();
    System.out.println("消息处理完成!");
    try {
        if (deliveryTag%2==0) {
            // 签收,返回Ack;业务成功完成就应该签收
            channel.basicAck(deliveryTag,false);
            System.out.println("签收了货物..."+deliveryTag);
        } else {
            /**
             * deliveryTag:当前消息派发的标签,一串数字
             * multiple:是否批量处理
             * requeue:
             *      true:重新入队
             *      false: 丢弃消息
             */
            channel.basicNack(deliveryTag,false,true);
            System.out.println("拒签了货物..."+deliveryTag);
        }
    } catch (IOException e) {
        // 网络中断
    }
}
  • 谷粒商城-分布式高级篇[商城业务-订单服务]

你可能感兴趣的:(部署小demo,教育电商,rabbitmq)