初识RabbitMQ

文章目录

  • RabbitMQ
    • 概述
      • 什么是消息队列
      • 消息队列的特点
        • 异步消息
        • 流量控制
        • 应用解耦
      • 流程
        • 大体流程
        • 详细流程
    • 安装RabbitMQ
    • Exchange类型
    • RabbitMQ整合Spring Boot
      • 搭建环境
      • 创建交换机
      • 创建消息队列
      • 创建绑定
      • 发送消息
      • 监听消息
      • 可靠传输的确认机制

RabbitMQ

官方文档: https://www.rabbitmq.com/tutorials/tutorial-one-go.html

概述

什么是消息队列

消息队列类似数据结构队列,队列只在内存中(私有),而分布式项目需要有一个公有的队列,服务可以给队列发送消息,服务也可以监听/订阅消息队列来接收/处理消息

市面上有很多种消息队列,主要分为采用JMS(Java Message Service)和AMQP(高级消息队列协议)的,AMQP兼容了JMS,RabbitMQ采用AMQP

初识RabbitMQ_第1张图片

消息队列的特点

异步消息

  • 用处理注册用户后要发送电子邮件,短信等业务来举例
    • 同步:注册用户写入数据库成功后发送电子邮件,电子邮件发送成功后再发送短信,完成后响应
    • 异步: 注册用户写入数据库成功后,因为发送电子邮件和发送短信没有前后关系,所以他们可以同时做,使用异步,它们完成后响应
    • 消息队列的异步:注册用户写入数据库成功后,将消息写入消息队列就响应 ,而发送电子邮件和发短信的服务订阅消息队列,接收到一个消息再去发送,用户不用等它们发送成功

初识RabbitMQ_第2张图片

流量控制

秒杀系统的高并发请求,每接收一个用户请求就放入消息队列,即可响应用户,而生成订单等服务就订阅消息队列,每取一个消息就处理,不至于大量请求直接让服务器宕机

初识RabbitMQ_第3张图片

应用解耦

订单服务,库存服务是2个服务

订单服务通过库存服务提供的API调用库存服务,如果库存服务修改了API,那么订单服务也需要修改调用的API

使用消息队列则可以解耦,订单服务将消息写入消息队列,而库存服务在消息队列中取出消息处理,无论API怎么样变,订单服务再也不用修改原来代码

初识RabbitMQ_第4张图片

流程

大体流程

消息发送者发送消息到消息代理,由消息代理将消息转发到目的地

消息发送者 ==消息=》 消息代理 ==消息=》目的地

消息发送者:某个发送消息的服务 (类似客户端)

消息代理:安装了消息队列的服务(类似服务端)

目的地:接收消息并处理的服务 (类似客户端)

  • 目的地有2种形式:
    1. 队列 (点对点)
      • 一对一,一个发送者,一个目的地(接收者)
    2. 主题 (发布/订阅)
      • 一对多,一个发送者,多个目的地要需要处理消息

详细流程

可以把发送者,接收者理解为生产者,消费者(客户端)

消息代理,安装了消息队列的服务 就是服务端

每个服务即是生产者也是消费者,因为它可能有时发送消息有时接收消息

  1. 客户端与服务端之间会先建立持续连接,连接上分很多信道,发送消息,接收消息在这些信道上(1个客户端只与服务端建立一条连接)

  2. 消息分为消息头,消息体(类似报文),生产者将消息通过信道发送到消息代理后,由消息代理的交换机接收

  3. 交换机绑定了很多消息队列,交换机根据消息头的路由键信息,把消息分发到某个消息队列中

  4. 消息队列再将消息通过信道发送给消费者,并且它可以感知消费者的状态(如果消费者死了就不会把消息发送出去)

消息代理中还可以划分虚拟主机,可以弄一套开发环境,生产环境的交换机与消息队列或是一套让Java语言使用;一套让PHP语言用,互不影响

总结: 生产者==消息=> 交换机 ==消息=> 消息队列 ==消息=> 消费者

初识RabbitMQ_第5张图片

安装RabbitMQ

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

端口说明

初识RabbitMQ_第6张图片

IP:15672进入后台管理系统,默认账号密码为guest

Exchange类型

AMQP相比于JMS增加了exchange和bindings,exchange可以绑定不同的queues,根据消息的路由键将消息转发到不同的队列中

初识RabbitMQ_第7张图片

exchange有headers,direct,fanout,topic类型,对应着不同的转发策略

headers类型匹配的不是路由键而是整个消息头,与direct功能类似,但是性能差很多,现在不用

  • direct 路由键完全匹配,单播,一对一

初识RabbitMQ_第8张图片

  • fanout 不使用路由键匹配,exchange将消息广播到所有绑定它的队列

初识RabbitMQ_第9张图片

  • topic路由键模糊匹配,可以使用#(匹配0或多个单词)或*(匹配一或多个单次)进行匹配,主题(发布/订阅模式)

初识RabbitMQ_第10张图片

RabbitMQ整合Spring Boot

搭建环境

  1. 导入依赖后自动配置了很多组件

    AmqpAdmin创建交换机,消息队列,绑定等

    RabbitTemplate发送消息

    		<dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-amqpartifactId>
            dependency>
    
  2. 编写配置文件

    spring.rabbitmq.host=xx.xx.xx.xx
    spring.rabbitmq.port=5672
    spring.rabbitmq.virtual-host=/
    
  3. 开启功能@EnableRabbit

创建交换机

@Autowired
AmqpAdmin amqpAdmin;


    /**
     * 创建交换机
     */
    @Test
    public void createExchange() {
//        DirectExchange(String name 交换机名,
//        boolean durable 是否持久化,
//        boolean autoDelete 是否自动删除,
//        Map arguments参数 )
        DirectExchange directExchange = new DirectExchange("exchange.direct.java", true, false, null);
        amqpAdmin.declareExchange(directExchange);
        log.info("创建交换机,{}", directExchange);
    }

创建消息队列

 /**
     * 创建队列
     */
    @Test
    public void createQueue() {
//        Queue(String name队列名,
//        boolean durable是否持久化,
//        boolean exclusive是否排它(独占:只能有一个消费者),
//        boolean autoDelete是否自动删除,
//        Map arguments参数)
        Queue queue = new Queue("tcl.java", true, false, false, null);
        amqpAdmin.declareQueue(queue);
        log.info("创建队列,{}", queue);
    }

创建绑定

	/**
     * 绑定交换机和队列
     */
    @Test
    public void createBinding(){
//        Binding(String destination 目的地名,
//        DestinationType destinationType 目的地类型(交换机或队列),
//        String exchange 要绑定的交换机,
//        String routingKey 设置路由键,
//			Map arguments 参数)
        Binding binding = new Binding("tcl.java", Binding.DestinationType.QUEUE, "exchange.direct.java", "tcl.java", null);
        amqpAdmin.declareBinding(binding);
        log.info("创建绑定,{}", binding);
    }

发送消息

	 @Autowired
    RabbitTemplate rabbitTemplate;
	/**
     * 发送消息
     */
    @Test
    public void sendMessage(){
        OrderEntity orderEntity = new OrderEntity();
        orderEntity.setCreateTime(new Date());
        orderEntity.setAutoConfirmDay(1);
        orderEntity.setCouponId(2L);
        //convertAndSend(String exchange交换机名, String routingKey路由键, Object object消息)
        //会先对消息进行转换,如果消息是对象默认使用Java序列化,可以向容器中添加组件换成JSON序列化
        rabbitTemplate.convertAndSend("exchange.direct.java","tcl.java",orderEntity);
        log.info("发送消息,{}",orderEntity);
    }

添加JSON序列化组件

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

监听消息

在类上使用@RabbitListener标识要监听的队列

在方法上使用@RabbitHandler标识该队列的消息可能是多种类型(类似重载,区分类型)

@Service("rabbitMQService")
@RabbitListener(queues = "tcl.java")
//在类上使用@RabbitListener标识要监听的队列
//在方法上使用@RabbitHandler标识该队列的消息可能是多种类型(类似重载,区分类型)
public class RabbitMQServiceImpl implements RabbitMQService {

    /**
     * 接收消息可以使用的参数
     * 1. Message 原生的消息对象  消息头+体  org.springframework.amqp.core
     * 2. T 消息类型
     * 3. Channel 消息传输的信道  com.rabbitmq.client;
     */
    @RabbitHandler
    public void receiveMessageOrder(Message message,
                               OrderEntity orderEntity,
                               Channel channel){
        System.out.println("接收消息成功,消息头:"+message.getMessageProperties()+"消息体:"+orderEntity);
    }

    @RabbitHandler
    public void receiveMessageMqMessage(Message message,
                               MqMessageEntity mqMessageEntity,
                               Channel channel){
        System.out.println("接收消息成功,消息头:"+message.getMessageProperties()+"消息体:"+mqMessageEntity);
    }
}
  • 接收消息可以使用的参数

    1. Message 原生的消息对象 消息头+体 org.springframework.amqp.core
    2. T 发送的消息类型(Spring 自动转换)
    3. Channel 消息传输的信道 com.rabbitmq.client;
  • 一个消息只会由一个客户端(消费者)处理

  • 消费者处理完成消息才会去再取一个消息处理

可靠传输的确认机制

消息的传输可能丢失,如果开启事物机制,性能将下降很多,此时确认机制可以保证可靠的传输

初识RabbitMQ_第11张图片

  • 生产者==消息=>消息代理: 确认回调

    1. 开启确认回调

      spring.rabbitmq.publisher-confirms=true
      
    2. 设置确认回调方法

  • 交换机==消息=> 消息队列: 返回回调

    1. 开启返回回调

      spring.rabbitmq.publisher-returns=true
      #消息抵达队列时,优先异步回调returenconfim
      spring.rabbitmq.template.mandatory=true
      
    2. 设置返回回调方法

确认回调 和 返回回调方法

	@PostConstruct //创建完GulimallRabbitConfig对象执行@PostConstruct方法
    public void initRabbitTemplate() {
        //设置确认回调 (生产者->消息代理)
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /**
             * 每次收到消息就会执行该方法
             * @param correlationData 消息唯一id (发送时可以设置这个唯一id)
             * @param ack 是否成功受到
             * @param cause 失败原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                System.out.println("correlationData:" + correlationData + " ack:" + ack + " 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) {
                System.out.println("message:"+message+" replyCode:"+replyCode+" replyText:"+ replyText+" exchange:"+exchange+" routingKey:"+routingKey);
            }
        });
    }
  • 消息队列==消息=> 消费者: ack手动确认

    • 默认开启自动确认,消费者拿到消息,无论消费者是否处理成功都自动确认,消费者如果此时正好宕机未处理也会自动确认
    1. 开启ack手动确认

      spring.rabbitmq.listener.simple.acknowledge-mode=manual
      
    2. 消费者处理成功手动确认,处理失败拒绝确认

      • basicAck(long deliveryTag确认收货的消息标签, boolean multiple 是否连续确认)

      • channel.basicReject();也是拒绝确认 不能连续

      • basicNack(long deliveryTag确认收货的消息标签, boolean multiple是否连续拒绝, boolean requeue是否放回队列)

		//获取收货标签 收获标签自增
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            if (deliveryTag % 2 != 0) {
                //手动确认
                //basicAck(long deliveryTag确认收货的消息标签, boolean multiple 是否连续确认)
                channel.basicAck(deliveryTag, false);
                System.out.println("手动确认,标签:"+deliveryTag);
            } else {
                //拒绝确认
                //channel.basicReject();也是拒绝确认 不能连续
                //basicNack(long deliveryTag确认收货的消息标签, boolean multiple是否连续拒绝, boolean requeue是否放回队列)
                channel.basicNack(deliveryTag,false,true);
                System.out.println("拒绝确认,标签:"+deliveryTag);
            }
        } catch (IOException e) {
            //网络中断
            e.printStackTrace();
        }

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