RabbitMQ核心笔记学习

一:RabblitMQ应用场景:

1. 异步处理;

2.应用解耦;

3.流量控制;

二:RabblitMQ概述:

1.消息代理和目的地:

当消息发送消息以后,将由消息代理(message Borker)接管,消息代理保证消息传递到指定目的地;

2.消息对列主要有两种形式的目的地:

1)队列:点对点消息通信;

2)主题:发布/订阅 消息通信;

3.点对点式:

1)消息发送者发送消息,消息代理将其放入一个队列种,消息接收者从队列种获取消息内容,消息读取后被移除队列;

2)特点:消息只有唯一的发送者和接受者,但并不是说只能有一个接受者;

4.发布订阅式:

发送消息者发送消息到主题,多个接收者(订阅者)监听订阅这个主题,那么就会在消息到达时同时收到消息;

5.JMS-JAVA消息服务:

基于JVM消息代理的规范;

6.AMQP:

高级消息队列协议,也是一个消息代理的规范,兼容JMS;

7.JMS与AMQP对比:

三:RabbitMQ工作原理:

 四、RabblitMQ安装:

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

docker update rabbitmq --restart=alawys

4369, 25672 (Erlang发现&集群端口)
5672, 5671 (AMQP端口)
15672 (web管理后台端口)
61613, 61614 (STOMP协议端口)
1883, 8883 (MQTT协议端口)

  五、RabbitMQ交换机类型:

  1.AMQP种的消息路由:

         AMQP中消息的路由过程和JMS存在一些差别,AMQP中增加了Exchange和Binding的角色。生产者把消息发布到Exchang上,消费最终到达队列并被消费者接收,而Binding决定交换器的消息应该发送到那个队列;

2. Exchange类型:

Exchange分发消息时根据类型的不同分发策略有区别,目前有四种类型;

1)Direct Exchange:

消息中的路由键(routing key)如果和Binding key 一直,交换机就将消息发送到对应的对列中。路由键与队列列名完全匹配,如果一个队列绑定绑定到交换机要求路由键为“dog”,则只转发routing key 标记为 “dog” 的消息,它是完全匹配、单播的模式。

 2)Fanout Exchange:

每个发到Fanout类型交换器的消息都会分到所有绑定的队列上去。Fanout交换器不处理路由键,只是简单的将队列绑定到交换器上,每个发送到交换器的消息都会被转发到该交换器绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。Fanout类型转发消息是最快的;

 3)Topic Exchange

topic 交换器通过模式匹配分配消息的路由键属性,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上。他将路由键和绑定路由键的字符传切成单词,这也单词之间用点隔开。它同样也会识别两个通配符: 符号“#”和符号“*”。“#”匹配0个或多个单词,*匹配一个单词;

 

 六、整合RabbitMQ整合:

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

1.引入amqp场景:RabbitAutoConfiguration 就会自动生效

2.给容器中配置了:rabbitTemplate、amqpAdmin、rabbitConnectionFactory、rabbitMessagingTemplate:

package com.ck.tysc.order.controller;

import com.ck.tysc.order.entity.OrderEntity;
import com.ck.tysc.order.entity.OrderReturnReasonEntity;
import com.ck.tysc.order.service.MqMessageService;
import com.cy.tysc.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;
import java.util.UUID;

/**
 * MQ消息发送
 */
@Slf4j
@RestController
@RequestMapping(value = "/mq")
public class MQController {

    @Autowired
    private AmqpAdmin amqpAdmin;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private MqMessageService mqMessageService;

    /**
     * 创建交换机
     * @return
     */
    @GetMapping("/createExchange")
    public R createExchange(){
        DirectExchange directExchange = new DirectExchange("hell-java-exchange",true,false);
        amqpAdmin.declareExchange(directExchange);
        log.info("交换机创建成功:{}","hell-java-exchange");
        return R.ok().put("exchangeName","hell-java-exchange");
    }

    /**
     * 创建队列
     * @return
     */
    @GetMapping("/createQueue")
    public R createQueue(){
        Queue queue = new Queue("hello-java-queue",true,false,false);
        amqpAdmin.declareQueue(queue);
        log.info("队列创建成功:{}","hello-java-queue");
        return R.ok().put("queueName","hello-java-queue");
    }

    /**
     * 创建绑定
     * @return
     */
    @GetMapping("/createBinding")
    public R createBinding(){
        //destination:目的地
        //destinationType:目的地类型
        //exchange:交换机
        //routingKey:路由键
        Binding binding = new Binding("hello-java-queue", Binding.DestinationType.QUEUE,"hell-java-exchange","hello.java",null);
        amqpAdmin.declareBinding(binding);
        log.info("绑定创建成功:{}","hello-java-binding");
        return R.ok().put("bindingName",binding);
    }

    /**
     * 发送消息
     * @return
     */
    @GetMapping("sendMessage")
    public R sendMessage(@RequestParam(value = "num",defaultValue = "10")Integer num){
        OrderReturnReasonEntity orderReturnReasonEntity = null;
        OrderEntity OrderEntity=null;
        for (int i = 0; i <num ; i++) {
            if (i%2 ==0){
                orderReturnReasonEntity=new OrderReturnReasonEntity();
                orderReturnReasonEntity.setId(1L);
                orderReturnReasonEntity.setName("订单退货!"+i);
                orderReturnReasonEntity.setSort(1);
                orderReturnReasonEntity.setStatus(1);
                orderReturnReasonEntity.setCreateTime(new Date());
                rabbitTemplate.convertAndSend("hell-java-exchange","hello.java",orderReturnReasonEntity);
            }else {
                OrderEntity=new OrderEntity();
                OrderEntity.setId(1L);
                OrderEntity.setOrderSn(UUID.randomUUID().toString());
                OrderEntity.setStatus(1);
                OrderEntity.setCreateTime(new Date());
                rabbitTemplate.convertAndSend("hell-java-exchange","hello.java",OrderEntity);
            }

            log.info("消息发送成功");
        }
        return R.ok().put("msg",null);
    }

}



package com.ck.tysc.order.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ck.tysc.order.dao.MqMessageDao;
import com.ck.tysc.order.entity.MqMessageEntity;
import com.ck.tysc.order.entity.OrderEntity;
import com.ck.tysc.order.entity.OrderReturnReasonEntity;
import com.ck.tysc.order.service.MqMessageService;
import com.cy.tysc.common.utils.PageUtils;
import com.cy.tysc.common.utils.Query;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

import java.util.Map;

@Slf4j
@RabbitListener(queues = {"hello-java-queue"})
@Service("mqMessageService")
public class MqMessageServiceImpl extends ServiceImpl<MqMessageDao, MqMessageEntity> implements MqMessageService {

    /**
     * 接收消息
     * queues: 声明需要监听的所有队列
     * 参数:1.Message: 原生消息详细信息:头+体
     *      2.T<发送消息的类型>
     *      3.Channel channel: 当前传输数据通道
     * queues可以有很多消费者监听,只要收到消息,队列就会删除消息,而且只能有一个收到此消息
     * 场景:1.订单服务启动多个:同一个消息,只能有一个客户端收到
     *      2.只有一个消息处理完,方法运行结束,我们就可以接收到下一个消息
     * @return
     */
    @RabbitHandler
    public void receiveMessage(OrderReturnReasonEntity info, Channel channel) throws InterruptedException {
        log.info("接收到的消息内容:{}",info);
        //log.info("接收到的消息:{}",msg);
    }

    @RabbitHandler
    public void receiveMessage(OrderEntity info,Channel channel) throws InterruptedException {
        log.info("接收到的消息内容:{}",info);
        //log.info("接收到的消息:{}",msg);
    }

}

七:RabbitMQ消息确认机制:

保证消息不丢失,可靠抵达,可以使用事务消息,但性能会下降250倍左右,为此引入确认机制;

publisher confirmCallback 确认模式

publisher returnCallback 未投递到 queue 退回模式

consumer ack机制

1.publisher confirmCallback 确认模式
  1)yml文件中配置开启发送端确认:spring.rabbitmq.publisher-confirms: true
  2)初始化RabbitTemplate,设置确认回调
/**
     * 定制RabbitTemplate
     */
    @PostConstruct //对象创建完成以后执行这个方法
    public void initRabbitTemplate(){
        // 设置确认回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /**
             * 1.只要消息抵达Borker就ack=true
             * @param correlationData 当前消息的唯一关联数据(这个是消息的唯一id)
             * @param ack 消息是否成功收到
             * @param cause 消息投递失败的原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                log.info("confirm-->CorrelationData",correlationData);
                log.info("confirm-->ack",ack);
                log.info("confirm-->cause",cause);
            }
        });
    }
2.publisher returnCallback 未投递到 queue 退回模式
   1) yml配置文件中配置: 
     spring.rabbitmq.publisherreturns=true(开启发送端抵达队列确认)
     spring.rabbitmq.template.mandatory=true(只要抵达对列,以异步方式优先发送回调returns-confirms)
   2)设置消息抵达队列的确认回调
package com.ck.tysc.order.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

/**
 * RabbitMQ配置类
 */
@Slf4j
@Configuration
public class MyRabbitConfig {

    @Autowired
    private RabbitTemplate rabbitTemplate;

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

    /**
     * 定制RabbitTemplate
     */
    @PostConstruct //对象创建完成以后执行这个方法
    public void initRabbitTemplate(){
        // 设置确认回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /**
             * 1.只要消息抵达Borker就ack=true
             * @param correlationData 当前消息的唯一关联数据(这个是消息的唯一id)
             * @param ack 消息是否成功收到
             * @param cause 消息投递失败的原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                log.info("confirm-->CorrelationData:{}",correlationData);
                log.info("confirm-->ack:{}",ack);
                log.info("confirm-->cause:{}",cause);
            }
        });
        // 设置消息抵达队列的确认回调
        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) {
                log.info("Fail Message:{}",message);
                log.info("replyCode:{}",replyCode);
                log.info("replyText:{}",replyText);
                log.info("exchange:{}",exchange);
                log.info("routingKey:{}",routingKey);
            }
        });
    }

}

3.consumer ack机制
  1)消费端确认(保证每个消息被正确消费,此时Broker才可以删除这条消息)
  2)消费端默认是自动确认的,只有消息接收到,服务端就会移除这个消息
  问题:收到很多消息,自动回复给服务器ack,只有一个消息处理成功,宕机了
  解决:1.消费者设置手动确认模式:只要我们没有明确告诉MQ,没有Ack,消息就一直是unacked状态,即使consumer宕机,消息不会丢失,会重新变为Ready状态,下一次有新的Consumer连接进来就发给它;
package com.ck.tysc.order.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ck.tysc.order.dao.MqMessageDao;
import com.ck.tysc.order.entity.MqMessageEntity;
import com.ck.tysc.order.entity.OrderEntity;
import com.ck.tysc.order.entity.OrderReturnReasonEntity;
import com.ck.tysc.order.service.MqMessageService;
import com.cy.tysc.common.utils.PageUtils;
import com.cy.tysc.common.utils.Query;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.Map;

@Slf4j
@RabbitListener(queues = {"hello-java-queue"})
@Service("mqMessageService")
public class MqMessageServiceImpl extends ServiceImpl<MqMessageDao, MqMessageEntity> implements MqMessageService {

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

        return new PageUtils(page);
    }

    /**
     * 接收消息
     * queues: 声明需要监听的所有队列
     * 参数:1.Message: 原生消息详细信息:头+体
     *      2.T<发送消息的类型>
     *      3.Channel channel: 当前传输数据通道
     * queues可以有很多消费者监听,只要收到消息,队列就会删除消息,而且只能有一个收到此消息
     * 场景:1.订单服务启动多个:同一个消息,只能有一个客户端收到
     *      2.只有一个消息处理完,方法运行结束,我们就可以接收到下一个消息
     * @return
     */
    @RabbitHandler
    public void receiveMessage(Message message,OrderReturnReasonEntity info, Channel channel) throws InterruptedException {
        log.info("接收到的消息内容:{}",info);
        //channel内安顺序自增的
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        log.info("deliveryTag:{}",deliveryTag);
        //log.info("接收到的消息:{}",msg);
        //ack应答
        //ack应答
        try {
            if (deliveryTag%2==0){
                // 接收并响应Ack应答
                channel.basicAck(deliveryTag,false);
                log.info("MQ响应了Ack应答:{}");
            }else {
                // long deliveryTag
                // boolean multiple
                // boolean requeue false: 丢弃 true: 发回发服务器,重新入队
                // 拒绝接收
                channel.basicNack(deliveryTag,false,true);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @RabbitHandler
    public void receiveMessage(Message message,OrderEntity info,Channel channel) throws InterruptedException {
        log.info("接收到的消息内容:{}",info);
        //channel内安顺序自增的
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        log.info("deliveryTag:{}",deliveryTag);
        //log.info("接收到的消息:{}",msg);
        //ack应答
        try {
            if (deliveryTag%2==0){
                // 接收并响应Ack应答
                channel.basicAck(deliveryTag,false);
                log.info("MQ响应了Ack应答:{}");
            }else {
                // long deliveryTag
                // boolean multiple
                // boolean requeue false: 丢弃 true: 发回发服务器,重新入队
                // 拒绝接收
                channel.basicNack(deliveryTag,false,true);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}








 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值