RabbitMQ

一、消息队列介绍

1.1同步调用和异步调用的区别

同步调用

Feign客户端可以实现服务间的通信,但是Feign是同步调用,也就是说A服务调用B服务之后,会进入阻塞/等待状态,直到B服务返回调用结果给A服务,A服务才会继续往后执行
在特定的业务场景中:用户注册成功之后,发送短息通知用户〈A服务为用户注册,B服务发送短信)A服务在完成用户注册之后(代码1),调用B服务发送短信,A服务完成B服务调用之后无需等待B服务的执行接口,直接执行提示用户注册从公(代码2),在这种需求下A服务调用B服务如果使用同步调用,必然降低A服务的执行效率,因此在这种场景下A服务需要通过异步调用调用B服务

异步调用:

当A服务调用B服务之后,无需等待B的调用结果,可以继续往下执行;那么服务间的异步通信该如何实现呢?

服务之间可以通过消息队列实现异步调用

  • 同步调用
    • A服务调用B服务,需要等待B服务执行完毕的返回值,A服务才可以继续往下执行。
    • 同步调用可以通过REST和RPC完成
    • REST: ribbon、Feign
    • RPE: Dubbg
  • 异步调用
    • A服务调用B服务,而无需等待B服务的执行结果,也就是说在B服务执行的同时A服务可以继续往下执行。
    • 通过消息队列实现异步调用

1.2消息队列概念

  • MQ全称为Message Queue,消息队列(MQ)是一种应用程序对应用程序的通信方法。应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们。
  • 消息传递指的是程序之间通过在消息中发送数据进行通信,而不是通过直接调用彼此来通信,直接调用通常是用于诸如远程过程调用的技术。排队指的是应用程序通过队列来通信。队列的使用除去了接收和发送应用程序同时执行的要求。

1.3MQ的产品对比

  • RabbitMo稳定可靠,数据一致,支持多协议,有消息确认,基于erlang语言
  • Kafka高吞吐,高性能,快速持久化,无消息确认,无消息遗漏,可能会有有重复消息,依赖于zookeeper,成本高
  • ActiveMQ不够灵活轻巧,对队列较多情况支持不好.
  • RocketMQ性能好,高吞吐,高可用性,支持大规模分布式,协议支持单一

二、RabbitMQ

2.1RabbitMQ

  • RabbitMQ是一个在AMOP基础上完成的,可复用的企业消息系统。他遵循Mozilla Public License开源协议。
  • AMOP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。Erlang中的实现有RabbitMQ等。
  • 主要特性:
    • 保证可靠性:使用一些机制来保证可靠性,如持久化、传输确认、发布确认。
    • 灵活的路由功能
    • 可伸缩性:支持消息集群,多台RabbitMQ服务器可以组成一个集群。
    • 高可用性:RabbitMo集群中的某个节点出现问题时队列任然可用。
    • 支持多种协议
    • 支持多语言客户端。
    • 提供良好的管理界面
    • 提供跟踪机制:如果消息出现异常,可以通过跟踪机制分析异常原因。
    • 提供插件机制:可通过插件进行多方面扩展

2.2安装教程(Linux)

https://note.youdao.com/ynoteshare1/index.html?id=b01d0f9ed0499610fc8f7b2fc76ef17e&type=note

三、工作模式

①简单模式

一个生产者对应一个消费者

请添加图片描述

②工作模式

一个生产者对应多个消费者

请添加图片描述

③订阅者模式

通过交换机来转发消息

请添加图片描述

④路由模式

请添加图片描述

四、Maven工程连接RabbitMQ

①创建maven项目

②添加依赖

<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>4.10.0</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.25</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.9</version>
</dependency>

③在resources下创建log4j.properties日志文件

# 全局日志配置
# 日志的级别:
#  error, warn, info, debug, TRACE按照优先级别越来越低
#   注意:如果设置的优先级别比较高,只能看到高优先级的日志,如果设置的优先级比较低,可以看到本优先级的日志以及高优先级的日志信息
log4j.rootLogger=DEBUG, stdout, F
# MyBatis 日志配置
log4j.logger.com.dalaolao.dao.IUserDao.getAllUsers=TRACE
# 控制台输出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
log4j.appender.F=org.apache.log4j.DailyRollingFileAppender
log4j.appender.F.File=myproj.log
log4j.appender.F.Append=true
log4j.appender.F.Threshold=DEBUG
log4j.appender.F.layout=org.apache.log4j.PatternLayout
log4j.appender.F.layout.ConversionPattern=%-d{yyyy-MM-dd HH\:mm\:ss}-[%p %F\:%L]  %m%n

④创建mq连接类

package dalaolao.util;

import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class RabbitConnection {
    public static Connection getConnection() throws Exception{
//        创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();

        factory.setHost("localhost");
        factory.setPort(5672);
        factory.setVirtualHost("host1");
        factory.setUsername("dalaolao");
        factory.setPassword("a443496487");
        return factory.newConnection();
    }
}

⑤发送端首先创建log4j.properties文件

⑥java代码

package com.dalaolao.produce;

import com.dalaolao.util.RabbitConnection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.nio.charset.StandardCharsets;

public class Produce {
    public static void main(String[] args) throws Exception {
        Connection connection = RabbitConnection.getConnection();

        Channel channel = connection.createChannel();

        //String var1,交换机名名称,如果消息直接发送到队列  那么直接给个空串
        // String var2, 目标消息队列名称
        // BasicProperties var3, 设置当前的消息的参数(过期时间)
        // byte[] var4 消息
        for (int i = 0; i < 10; i++) {
            String msg = "测试消息1,我是dalaolao"+i;
            channel.basicPublish("ex2","b",null,msg.getBytes(StandardCharsets.UTF_8));
        }
        channel.close();
        connection.close();
    }
}

⑦接收端的代码

package dalaolao.Consumer;

import com.rabbitmq.client.*;
import dalaolao.util.RabbitConnection;

import java.io.IOException;

public class ResavingMsg {
    public static void main(String[] args) throws Exception {
        Connection connection = RabbitConnection.getConnection();

        Channel channel = connection.createChannel();

        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//                处理消息的方法。body表示从消息队列接收到的消息
                String msg = new String(body);
                System.out.println("消费者"+msg);
            }
        };

        channel.basicConsume("queue5",true,consumer);
    }
}

五、springboot连接RabbitMQ

5.1发送端

①创建项目添加依赖

请添加图片描述

②配置yml

## 应用名称
#spring.application.name=RabbitMQ-Springboot
## 应用服务 WEB 访问端口
#server.port=8080
server:
  port: 9998
spring:
  application:
    name: producer
  rabbitmq:
    host: localhost
    port: 5672
    username: dalaolao
    password: a443496487
    virtual-host: host1

③发送消息

package com.dalaolao.service.impl;

import com.dalaolao.service.ProducerService;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class ProducerServiceImpl implements ProducerService {
    @Autowired
    private AmqpTemplate amqpTemplate;

    @Override
    public String send(String msg) {
//        发送到队列(简单模式)
        amqpTemplate.convertAndSend("queue1",msg);
//        发送到交换机(订阅模式)
        amqpTemplate.convertAndSend("ex1","",msg);
//        发送到交换机(路由模式)
        amqpTemplate.convertAndSend("ex2","a",msg);
        return "发送成功";
    }
}

5.2接受端

前两步同上

接收端代码:

package com.dalaolao.service;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

@Service
@RabbitListener(queues = {"queue1","queue2","queue3","queue4","queue5","queue6"})
public class Queue1 {
    @RabbitHandler
    public void handlerMsg(String msg){
        System.out.println(msg);
    }
}

六、RabbitMQ发送接受对象

RabbitMQ消息队列,发送/接收都是字符串/字节数组组类型的消息

6.1.使用序列化对象发送

  • 必须实现Serializable接口
  • 对象的包名,类名,属性名必须相同
  • 发送端
package com.dalaolao.service.impl;

import com.dalaolao.service.ProducerService;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class ProducerServiceImpl implements ProducerService {
    @Autowired
    private AmqpTemplate amqpTemplate;

    @Override
    public String send(String msg) {
        amqpTemplate.convertAndSend("queue1",Goods);
    }
}
  • 接收端
@Service
@RabbitListener(queues = {"queue1","queue2","queue3","queue4","queue5","queue6"})
public class Queue1 {
    @RabbitHandler
    public void handlerMsg(String msg){
        System.out.println(msg);
    }
}

6.2使用json串发送消息

要求对象中的属性名、类型相同即可

发送端发送json串,接收端将接收到的json串转换成对象即可

import com.fasterxml.jackson.databind.ObjectMapper;//这个包中有一个ObjectMapper类
//ObjectMapper类有两个方法可以将对象转换成json串/将json串转换成对象
String s = new ObjectMapper().writeValueAsString(goods);//将对象转换成json串
Goods goods1 = new ObjectMapper().readValue(goods, Goods.class);//将json串转换成对象

七、使用Java代码创建队列、交换机

  • 使用Java代码创建队列
//定义队列(使用Java代码在MQ中新建一个队列)
//参数1:定义的队列名称
//参数2:队列中的数据是否持久化(如果选择了持久化)
//参数3:是否排外(当前队列是否为当前连接私有)
//参数4:自动删除(当此队列的连接数为8时,此队列会销毁(无论队列中是否还有数据))
//参数5:设置当前队列的参数
channel. queueDeclare( " queue7" ,false,false, false , null);

  • 使用java代码创建交换机
//定义一个“订阅交换机”
channel.exchangeDeclare( "ex3",BuiltinExchangeType .FANOUT);
//定义一个“路由交换机”
channel.exchangeDeclare( "ex4", BuiltinExchangeType.DIRECT);
  • 绑定队列到交换机
//绑定队列
//参数1:队列名称
//参数2:目标交换机
//参数3:如果绑定订阅交换机参数为"",如果绑定路由交换机则表示设置队列的key
channel.queueBind( "queue7" , "ex4" , "k1" );
channel.queueBind( "queue8" , "ex4" , "k2" );

八、spring boot创建队列、交换机、绑定

主要是生产端的创建队列、创建交换机、绑定交换机与队列;消费端只需监听即可

package com.dalaolao.config;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {
 //创建队列 
    @Bean(name = "queue8")
    public Queue Queue8() {
        return new Queue("queue8");
    }
    @Bean(name = "queue9")
    public Queue Queue9() {
        return new Queue("queue9");
    }
    @Bean(name = "queue10")
    public Queue Queue10() {
        return new Queue("queue10");
    }
    @Bean(name = "queue11")
    public Queue Queue11() {
        return new Queue("queue11");
    }
  //创建订阅交换机
    @Bean
    public FanoutExchange ex3(){
        return new FanoutExchange("ex3");
    }
  //创建路由交换机
    @Bean
    public DirectExchange ex4(){
        return new DirectExchange("ex4");
    }
  
    @Bean
    public Binding binding1(FanoutExchange ex3,Queue queue8){
        //将queue8绑定到订阅交换机ex3
        return BindingBuilder.bind(queue8).to(ex3);
    }
    @Bean
    public Binding binding2(FanoutExchange ex3,Queue queue9){
        //将queue9绑定到订阅交换机ex3
        return BindingBuilder.bind(queue9).to(ex3);
    }
    @Bean
    public Binding binding3(DirectExchange ex4,Queue queue10){
        //将queue10绑定到路由交换机ex4并设置key为a
        return BindingBuilder.bind(queue10).to(ex4).with("a");
    }
    @Bean
    public Binding binding4(DirectExchange ex4,Queue queue9){
        //将queue10绑定到路由交换机ex4并设置key为b
        return BindingBuilder.bind(queue9).to(ex4).with("b");
    }
}

九、消息可靠性

请添加图片描述

9.1springboot生产段开启消息确认机制、return机制

①配置yml

#在rabbitmq配置下追加以下配置
publisher-confirm-type: simple #开启消息确认机制
publisher-returns: true #开启return机制

②配置消息确认的监听类

  package com.dalaolao.config;

  import org.springframework.amqp.core.AmqpTemplate;
  import org.springframework.amqp.rabbit.connection.CorrelationData;
  import org.springframework.amqp.rabbit.core.RabbitTemplate;
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.stereotype.Component;

  import javax.annotation.PostConstruct;

  @Component
  public class MyConfirmListener implements RabbitTemplate.ConfirmCallback {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private AmqpTemplate amqpTemplate;


    @PostConstruct//在创建完对象后加载此方法
    public void setConfirm() {
      rabbitTemplate.setConfirmCallback(this);
    }

    @Override
    public void confirm(CorrelationData correlationData, boolean b, String s) {
      //        参数b 表示消息确认结果
      //        参数s 表示发送的消息
      if (b) {
        System.out.println("消息发送到交换机成功");
      } else {
        System.out.println("消息发送到交换机失败");
        amqpTemplate.convertAndSend("queue1",s);
      }
    }
  }

③配置return的监听类

package com.dalaolao.config;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
public class MyReturnListener implements RabbitTemplate.ReturnCallback {

  @Autowired
  private RabbitTemplate rabbitTemplate;

  @PostConstruct
  public void setReturn() {
    rabbitTemplate.setReturnCallback(this);
  }

  /**
     * @param message 消息主体
     * @param i       replyCode
     * @param s       错误描述
     * @param s1      发送到的交换机
     * @param s2      路由交换机的key
     */
  @Override
  public void returnedMessage(Message message, int i, String s, String s1, String s2) {
    System.out.println("分发到队列失败");
  }
}

9.2消费端手动ACK

消费端给队列的反馈

请添加图片描述

package com.dalaolao.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.dalaolao.dto.Goods;
import com.rabbitmq.client.Channel;
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;

@Service
@RabbitListener(queues = {"queue1"})
public class Queue1 {
  @RabbitHandler
  public void handlerMsg(String goods, Message message, Channel channel) throws IOException {
    try {
      Goods goods1 = new ObjectMapper().readValue(goods, Goods.class);//将json串转换成对象
      System.out.println(goods1);
      //参数1 :当前消息的索引
      //参数2 :是否开启批处理
      channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    } catch (Exception e) {
      //参数1 :当前消息的索引
      //参数2 :是否开启批处理
      //参数3 :是否重新加入队列
      channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
    }

  }
}

9.3消息的幂等性问题

消息消费的幂等性――多次消费的执行结果时相同的(避免重复消费)

解决方案:处理成功的消息setnx到redis

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值