SpringCloud学习路线(9)——服务异步通讯RabbitMQ

一、初见MQ

(一)什么是MQ?

MQ(MessageQueue) ,意思是消息队列,也就是事件驱动架构中的Broker。

(二)同步调用

1、概念: 同步调用是指,某一服务需要多个服务共同参与,但多个服务之间有一定的执行顺序,当每一个服务都需要等待前面一个服务完成才能继续执行。

2、存在的问题

  • 耦合度高: 新需求需要改动原代码
  • 性能下降: 调用者需要等待服务提供者相应,如果调用链过长则响应时间等于每次调用的时间之和。
  • 资源浪费: 调用链的每个服务在等待响应过程中,不会释放请求资源,高并发场景下会浪费系统资源。
  • 级联失败: 若服务提供者出现宕机,所有调用者都会因故障而导致整个服务集群故障。

(三)异步调用

1、实现模式: 异步调用常见实现的就是事件驱动模式。

2、事件驱动的优势

  • 服务解耦: 只需要将请求交付给事件管理器进行管理即可完成服务。
  • 性能提升: 与客户交互的服务短时间就能完成,并不需要等待后续服务完成。
  • 服务弱依赖: 其它服务宕机不影响服务集群的使用
  • 流量缓冲: 事件管理器通过任务队列的方式,使得订阅的服务按照自身速度进行执行。

3、事件驱动的缺点

  • 高度依赖Broker的可靠性、安全性、吞吐能力
  • 架构复杂时,业务没有明显的流程线,不便于跟踪管理

(四)MQ常见框架

RabbitMQ(中小企业) ActiveMQ RocketMQ(大型企业) Kafka
公司/社区 Rabbit Apache Alibaba Apache
开发语言 Erlang Java Java Java
协议支持 AMQP,XMPP,SMTP,STOMP OpenWire,STOMP,REST,XMPP,AMQP 自定义协议 自定义协议
可用性 一般
单机吞吐量 一般 极高
消息延迟 微妙级 毫秒级 毫秒级 毫秒以内
消息可靠 一般 一般

二、使用MQ

(一)RabbitMQ概述

RqbbitMQ 是基于Erlang 语言开发的开源消息通讯中间件,官方地址:https://rabbitmq.com/

(二)安装MQ

docker pull rabbitmq:3-management

(三)运行RabbitMQ

#配置 MQ的用户名和密码,容器名和主机名,端口,镜像名 ,注意:15672端口是MQ的控制台访问端口,5672是对外暴露的消息通信端口
docker run -e RABBITMQ_DEFAULT_USER=xxx -e RABBITMQ_DEFAULT_PASS=xxxx --name mq --hostname mq1 -p 15672:15672 -p 5672:5672 -d rabbitmq:3-management

访问MQ的控制台


(4)RabbitMQ的整体结构

(5)RabbitMQ中的几个概念

  • channel: 操作MQ的工具
  • exchange: 路由消息到队列
  • queue: 缓存消息
  • Virtual Host: 虚拟主机,是对queue,exchange等资源进行逻辑分组

(6)常见的MQ模型

  • 基本消息队列(BasicQueue): Publisher ---1:1--- Queue ---1:1--- Customer
  • 工作消息队列(WorkQueue): Publisher ---1:1--- Queue ---1:n--- Customer
  • 发布/订阅(Publish、Subscribe): 根据交换机类型又有三种模型
    • Fanout Exchange: 广播,Publisher---1:1---Exchange---1:n---Queue---1:1---Customer
    • Direct Exchange: 路由,Publisher---1:1---Exchange---1:n---Queue---1:1---Customer
    • Topic Exchange: 主题,
  • RPC
  • 发布者确认

第一种:基本消息队列的基本使用

包含三种角色:publisherqueueconsumer

  • publisher: 消费发布者,将消息发布到队列queue
  • queue: 消息队列,负责接受并缓存消息
  • consumer: 订阅队列,处理队列中的消息

收发消息的过程: 获取连接 》 建立通信通道 》 创建消息队列 》 收发消息 》 释放资源

1、publisher和consumer引入依赖

xml 复制代码
  <dependency>
	<groupId>com.rabbitmq</groupId>
	<artifactId>amqp-client</artifactId>
</dependency>

2、Publisher创建发送消息通道

java 复制代码
@SpringBootTest
class PublisherApplicationTests {
    @Test
    void testSendMessage() throws IOException, TimeoutException {
//        1、建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
//        2、设置连接参数
        connectionFactory.setHost("192.168.92.131");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("root");
        connectionFactory.setPassword("root");
//        3、建立连接
        Connection connection = connectionFactory.newConnection();
//        4、建立通信通道Channel
        Channel channel = connection.createChannel();
//        5、创建队列
        String queueName = "simple.queue";
        channel.queueDeclare(queueName,false,false,false,null);
//        6、发送信息
        String message = "hello,rabbitmq!";
        channel.basicPublish("",queueName,null,message.getBytes(StandardCharsets.UTF_8));
        System.out.println("发送消息成功:【"+message+"】");
//        7、关闭通道和连接
        channel.close();
        connection.close();
    }
}

2、Consumer创建订阅通道

java 复制代码
class ConsumerApplicationTests {
    public static void main(String[] args) throws IOException, TimeoutException {
        //        1、建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //        2、设置连接参数
        connectionFactory.setHost("192.168.92.131");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("root");
        connectionFactory.setPassword("root");
//        3、建立连接
        Connection connection = connectionFactory.newConnection();
//        4、建立通信通道Channel
        Channel channel = connection.createChannel();
//        5、创建队列
        String queueName = "simple.queue";
        channel.queueDeclare(queueName,false,false,false,null);
//        6、订阅消息
        channel.basicConsume(queueName,true,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//                7、处理消息
                String message = new String(body);
                System.out.println("接收到消息:【"+message+"】");
            }
        });
        System.out.println("等待接收消息....");
    }
}

第二种:Work Queue 工作队列

与基本队列的区别在于,它能使用多个订阅队列进行高效的处理请求。(因为一个订阅队列的处理速度是有限的)

使用过程与基本队列几乎一致,只是开启了多个订阅队列。

在使用过程中我们会发现,多个订阅队列对任务的分配是平均的,这就是预取机制

我们需要的是快速处理的订阅队列获取更多的请求,慢速处理的订阅队列获取少量的请求,它如何实现呢?

通过修改配置文件,设置一个 preFetch 值。

xml 复制代码
spring:
  rabbitmq:
    host: 192.168.92.131 #IP
    port: 5672 #端口
    virtual-host: / #虚拟主机
    username: root #用户名
    password: root #密码
    listener:
      simple:
        prefetch: 1 # 每次取 1 个请求,处理完才能取下一个。

第三种:FanoutQueue 广播消息队列

SpringAMQP提供声明交换机、队列、绑定关系的API

主要使用的是Exchange.FanoutExchange类。

实现思路:
1、在consumer服务,声明队列,交换机,并将两者绑定。

java 复制代码
@Configuration
public class FanoutConfig{
	//交换机
	@Bean
	public FanoutExchange fanoutExchange(){
		return new FanoutExchange("com.fanout");
	}

	//队列
	@Bean
	public Queue fanoutQueue1(){
		return new Queue("com.queue1");
	}

	//绑定关系
	@Bean
	public Binding bindingQueue(Queue fanoutQueue1,FanoutExchange fanoutExchange){
		return BindingBuilder.bind(fanoutQueue).to(fanoutExchange);
	}

	//...以相同方式声明第2个队列,并完成绑定

}

2、在consumer服务,编写两个消费者方法,分别监听fanout.queue1和fanout.queue2

java 复制代码
@Component
public class SpringRabbitListener {
	@RabbitListener(queues = "com.queue1")
	public void listenFanoutQueue1(String msg) throws InterruptedException {
		//...处理结果
	}

	@RabbitListener(queues = "com.queue2")
	public void listenFanoutQueue2(String msg) throws InterruptedException {
		//...处理结果
	}
}

3、在publisher编写测试方法,向交换机发送信息

java 复制代码
@Test
public void sendFanoutExchange() {
	//1、交换机
	String exchangeName = "com.fanout";
	//2、消息
	String message = "Hello Fanout";
	//3、发送消息
	rabbitTemplate.covertAndSend(exchangeName, "", message);
}

第四种:路由信息队列

路由模式的流程: 即设置密钥的绑定关系,只有携带相应的密钥才能进入相应的队列

  • 每一个 QueueExchange 设置一个 BindingKey
  • 发布者发送消息时,需要指定消息的 RoutingKey
  • Exchange根据消息路由到 BindingKeyRoutingKey 一致的队列

实现思路:
1、利用 @RabbitListener 声明Exchange、Queue、RoutingKey

java 复制代码
@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "direct.queue1"), exchange = @Exchange(name = "com.exchange", type = ExcahngeTypes.DIRECT), key = {"red","blue"}))
public void listenRoutingQueue1(String msg) throws InterruptedException {
	//...处理结果
}

@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "direct.queue2"), exchange = @Exchange(name = "com.exchange", type = ExcahngeTypes.DIRECT), key = {"red","green"}))
public void listenRoutingQueue2(String msg) throws InterruptedException {
	//...处理结果

2、发送消息实现

java 复制代码
//指定队列处理
@Test
public void sendRoutingExchange1(){
	//交换机,消息
	String exchangeName = "com.exchange";
	String message = "Hello,RoutingMQ";
	//发送消息
	rabbitTemplate.covertAndSend(exchangeName, "blue", message);
}

//多队列处理
@Test
public void sendRoutingExchange2(){
	//交换机,消息
	String exchangeName = "com.exchange";
	String message = "Hello,RoutingMQ";
	//发送消息
	rabbitTemplate.covertAndSend(exchangeName, "red", message);
}

第五种:主题信息队列(通配key)

TopicExchange 与 DirectExchange 的区别: routingkey必须是多个单词的列表,并且以,分割。并且Queue与Exchange指定的BindingKey时可使用通配符:

  • **#:**代指 0 / n 个单词
  • *: 代指一个单词

实现思路:
1、通过 @RabbitListener 声明Exchange、Queue、RoutingKey

java 复制代码
@RabbitListener(bingdings = @QueueBinding(exchange = @Exchange(name = "com.exchange", type = ExchangeTypes.TOPIC), queue = @Queue(name = "com.queue1"), key = {"china.#"}))
public void listenTopicQueue1(String msg) {
	//处理代码....
}

@RabbitListener(bingdings = @QueueBinding(exchange = @Exchange(name = "com.exchange", type = ExchangeTypes.TOPIC), queue = @Queue(name = "com.queue2"), key = {"#.news"}))
public void listenTopicQueue2(String msg) {
	//处理代码....
}

2、在publisher服务中,向交换机发送消息

java 复制代码
@Test
public void sendTopicMessage(){
	//交换机,消息
	String exchangeName = "com.exchange";
	String message = "Hello,Topic";
	rabbitTemplate.convertAndSend(exchangeName,"china.call",message);
}

四、SpringAMQP

(一)概念

  • AMQP: Advanced Message Queuing Protocol 传递消息队列协议,是用于在应用程序或之间传递业务消息的开放标准。该协议与语言及平台无关,更符合为服务中独立性的要求。
  • Spring AMQP: Spring AMQP是基于AMQP协议定义的一套API规范,提供了模板来发送和接收消息。其中 spring-amqp是基础抽象,spring-rabbit是底层的默认实现。

(二)实现基础消息队列

1、引入spring-amqp依赖

xml 复制代码
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

2、publisher服务中利用RabbitTemplate发送消息到任务队列

  • 配置mq连接信息
yml 复制代码
spring:
  rabbitmq:
    host: 192.168.92.131 #IP
    port: 5672
    virtual-host: /
    username: root
    password: root
  • 编写发送方法
java 复制代码
	@Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void sendMessage(){
        String queueName = "simple.queue";
        String message = "Hello World";
        rabbitTemplate.convertAndSend(queueName,message);
    }

3、在consumer服务中编写消费逻辑,绑定simple.queue队列

  • 配置mq连接信息
yml 复制代码
spring:
  rabbitmq:
    host: 192.168.92.131 #IP
    port: 5672
    virtual-host: /
    username: root
    password: root
  • 编写发送方法1
java 复制代码
	@Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void getMessage(){
        String queueName = "simple.queue";
        // receive 表示接收方法,接收到的信息会封装到Message,可以看receive的返回值
        Message message = rabbitTemplate.receive(queueName);
        // Message.getBody 是 byte[]
        System.out.println(new String(message.getBody()));
    }
  • 编写发送方法2
    • 创建一个监听类
java 复制代码
// 注册成 Bean 对象
@Component
public class SpringRabbitListener {

	// 监听器注释,queues = 订阅队列,并将返回值注入参数列表中	
    @RabbitListener(queues = "simple.queue")
    public void ListenSimpleQueueMessage(String msg){
        System.out.println("Spring 消费者接收到消息:【" + msg + "】");
    }
}

(三)消息转换器

为了让我们能够自由识别consumer发送的消息,则需要使用的是消息转换器

消息转换器如何使用?

Spring对消息对象的处理是由org.springframework.amqp.support.converter.MessageConverter来处理,默认实现的是SimpleMessageConverter,基于ObjectObjectOutputStream完成序列化。

我们只需要定义一个 MessageConverter 类型的Bean即可,推荐使用JSON序列化

1、publisher引入依赖

xml 复制代码
<!-- 接收消息需要使用jackson的转换依赖 -->
<dependency>
	<groupId>com.fasterxml.jackson.dataformat</groupId>
	<artifactId>jackson-dataformat-xml</artifactId>
	<version>2.9.10</version>
</dependency>

<!-- 发送消息需要使用jackson的核心依赖 -->
<dependency>
	<groupId>com.fasterxml.jackson.core</groupId>
	<artifactId>jackson-databind</artifactId>
</dependency>

2、publisher启动类,声明MessageConverter

java 复制代码
@Bean
public MessageConverter jsonMessageConverter(){
	return new Jackson2JsonMessageConverter();
}

3、consumer启动类,声明MessageConverter

java 复制代码
@Bean
public MessageConverter jsonMessageConverter(){
	return new Jackson2JsonMessageConverter();
}

4、监听队列消息

java 复制代码
@RabbitListener(queues = "object.queue")
public void listenObjectMessage(Object msg) {
	//处理数据....
}
相关推荐
一颗知足的心2 小时前
SpringCloud Alibaba五大组件之——Sentinel
spring·spring cloud·sentinel
ImomoTo3 小时前
HarmonyOS学习(十三)——数据管理(二) 关系型数据库
数据库·学习·harmonyos·arkts·鸿蒙
wang_book5 小时前
Gitlab学习(007 gitlab项目操作)
java·运维·git·学习·spring·gitlab
weixin_455446176 小时前
Python学习的主要知识框架
开发语言·python·学习
spiker_6 小时前
RabbitMQ 常见使用模式详解
分布式·rabbitmq
不能再留遗憾了6 小时前
RabbitMQ 高级特性——持久化
分布式·rabbitmq·ruby
成为大佬先秃头6 小时前
解决RabbitMQ设置TTL过期后不进入死信队列
分布式·中间件·rabbitmq·java-rabbitmq
一个诺诺前行的后端程序员7 小时前
springcloud微服务实战<1>
spring·spring cloud·微服务
Ylucius8 小时前
动态语言? 静态语言? ------区别何在?java,js,c,c++,python分给是静态or动态语言?
java·c语言·javascript·c++·python·学习
LvManBa8 小时前
Vue学习记录之六(组件实战及BEM框架了解)
vue.js·学习·rust