Spring Boot笔记-Spring Boot与消息(十)

1.概述

1.使用消息中间件的目的:

在大多应用中,可通过消息服务中间件来提升系统异步通信、扩展解耦能力。也就是解耦、异步、削峰。

2.消息服务中两个重要概念:

消息代理(message broker)和目的地(destination)。

当消息发送者发送消息以后,将由消息代理接管,消息代理保证消息传递到指定目的地。

3.消息队列主要有两种形式的目的地:

  1. 队列(queue):点对点消息通信(point-to-point)
  2. 主题(topic):发布(publish)/订阅(subscribe)消息通信

4.点对点式:

  • 消息发送者发送消息,消息代理将其放入一个队列中,消息接收者从队列中获取消息内容,消息读取后被移出队列
  • 消息只有唯一的发送者和接受者,但并不是说只能有一个接收者

5.发布订阅式:

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

6.JMS(Java Message Service)Java消息服务:

基于JVM消息代理的规范。ActiveMQ、HornetMQ是JMS具体的实现。

7.AMQP(Advanced Message Queuing Protocol)

高级消息队列协议,也是一个消息代理的规范,兼容JMS。RabbitMQ是AMQP的实现。

 JMSAMQP
定义Java api网络消息队列协议
跨语言
跨平台
Model

提供两种消息模型:

1.Peer-2-Peer

2.Pub/Sub

提供了五种消息模型:
1.direct exchange
2.fanout exchange
3.topic change
4.headers exchange
5.system exchange
本质来讲,后四种和JMS的pub/sub模型没有太大差别,仅是在路由机制上做了更详细的划分
支持消息类型多种消息类型:
TextMessage
MapMessage
BytesMessage
StreamMessage
ObjectMessage
Message(只有消息头和属性)
byte[]
当实际应用时,有复杂的消息,可以将消息序列化后发送
综合评价JMS 定义了JAVA API层面的标准;在java体系中,多个client均可以通过JMS进行交互,不需要应用修改代码,但是其对跨平台的支持较差AMQP定义了wire-level层的协议标准;天然具有跨平台、跨语言特性

8.Spring支持

  • spring-jms提供了对JMS的支持
  • spring-rabbit提供了对AMQP的支持
  • 需要ConnectionFactory的实现来连接消息代理
  • 提供JmsTemplate、RabbitTemplate来发送消息
  • @JmsListener(JMS)、@RabbitListener(AMQP)注解在方法上监听消息代理发布的消息
  • @EnableJms、@EnableRabbit开启支持

9.Spring Boot自动配置

JmsAutoConfiguration.java、RabbitAutoConfiguration.java

2.RabbitMQ简介

RabbitMQ是一个由erlang开发的AMQP(Advanved Message Queue Protocol)的开源实现。

核心概念

Message

消息,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。

Publisher

消息的生产者,也是一个向交换器发布消息的客户端应用程序。

Exchange

交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。指定消息按什么规则,路由到哪个队列。

Exchange有4种类型:direct(默认),fanout,topic和headers,不同类型的Exchange转发消息的策略有所区别。

Queue

消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。

  • 消息队列,提供了FIFO的处理机制,具有缓存消息的能力。rabbitmq中,队列消息可以设置为持久化,临时或者自动删除
  • 设置为持久化的队列,queue中的消息会在server本地硬盘存储一份,防止系统crash,数据丢失
  • 设置为临时队列,queue中的数据在系统重启之后就会丢失
  • 设置为自动删除的队列,当不存在用户连接到server,队列中的数据会被自动删除

Binding

绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。Exchange和Queue的绑定可以是多对多的关系。

Connection

网络连接,比如一个TCP连接。

Channel

信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内的虚拟连接,AMQP命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁TCP都是非常昂贵的开销,所以引入了信道的概念,以复用一条TCP连接。

Consumer

消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。

Virtual Host

虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个vhost本质上就是一个 mini 版的RabbitMQ服务器,拥有自己的队列、交换器、绑定和权限机制。vhost是AMQP概念的基础,必须在连接时指定,RabbitMQ默认的vhost是/。

  • 在rabbitmq server上可以创建多个虚拟的message broker,又叫做virtual hosts(vhosts)
  • 每一个vhost本质上是一个mini-rabbitmq server,分别管理各自的exchange,和bindings
  • vhost相当于物理的server,可以为不同app提供边界隔离
  • producer和consumer连接rabbit server需要指定一个vhost

Broker

表示消息队列服务器实体。它提供一种传输服务,它的角色就是维护一条从生产者到消费者的路线,保证数据能按照指定的方式进行传输。

3.RabbitMQ运行机制

AMQP中的消息路由

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

Exchange类型

Exchange分发消息时根据类型的不同分发策略有区别,目前共四种类型:direct、fanout、topic、headers。headers匹配AMQP消息的header而不是路由键,headers交换器和direct交换器完全一致,但性能差很多,目前几乎用不到了,所以直接看另外三种类型:

4.RabbitMQ整合

1.前期准备

虚拟机下载RabbitMQ镜像:docker pull rabbitmq:3.7.26-management(选择带有management的,有Web管理界面)。

虚拟机安装并启动RabbitMQ服务:docker run -d -p 5672:5672 -p 15672:15672 docker.io/rabbitmq:3.7.26-management(5672是通信端口,15672是访问Web界面的端口,记得带上自己的tag来启动)。

通过浏览器访问RabbitMQ的管理界面http://192.168.0.123:15672/,输入guest-guest登陆。

访问不到的主页的可以执行:docker exec -it my_rabbitmq_name /bin/bash进入容器,然后执行:rabbitmq-plugins list查看plugins的开启状态,使用命令:rabbitmq-plugins enable rabbitmq_management开启web界面管理插件。

按照下图做配置:

添加Exchange:分别添加exchange.direct、exchange.fanout、exchange.topic。他们的类型分别是:direct,fanout,topic,开启持久化。

添加Queue:分别添加atguigu、atguigu.news、atguigu.emps、gulixueyuan.news。

将Exchange和Queue进行绑定:点击某个Exchange后,会看到Bindings的入口。根据配置图,exchange.direct和exchange.fanout的Routing key和binding的名称是一样的,exchange.topic中,binding的名称带“atguigu”的,Routing key写atguigu.#,binding的名称带“*.news”的,Routing key写*.news。

2.发送消息测试

在Exchange的Bindings下面,可以看到Publish message,通过这个地方可以进行发送消息。

测试exchange.direct发送消息:

输入Routing key和Payload,点击Publish message,即可发送消息,发送成功后,回到Queue标签,点击“atguigu”的Queue,在下面可以通过Get messages获取到消息。这是点对点发送,根据Routing key进行发送。

测试exchange.fanout发送消息:

输入Routing key和Payload,点击Publish message,即可发送消息,发送成功后,回到Queue标签,发现所有的Queue都会收到一条消息,在下面可以通过Get messages获取到消息,此时Routing key就没有什么作用了,fanout类型的Exchange会向它下面绑定的所有Queue发送消息。

测试exchange.topic发送消息:

输入Routing key和Payload,点击Publish message,即可发送消息,发送成功后,回到Queue标签,查看队列中的消息。当Routing key是atguigu.news的时候,所有的binding都会匹配成功,会向每一个Queue里发送一条消息。当Routing key是hello.news的时候,只有atguigu.news和gulixueyuan.news队列能够收到消息。也就是按照前面说的通配符规则:符号“#”匹配零个和多个单词,符号“*”匹配一个单词。

在Get Messages的时候,有一个应答选项,告诉消息队列,我已经获取到消息了,可以在队列中删除了。

3.把RabbitMQ整合进项目

新建Spring Boot项目,选择Spring Web、Spring for RabbitMQ依赖。

查看RabbitAutoConfiguration类,它会创建CachingConnectionFactory(缓存连接工厂)、RabbitTemplate(Rabbit模板,用于给RabbitMQ发送和接收消息,类似于JdbcTemplate,RedisTemplate),AmqpAdmin(RabbitMQ系统管理功能组件)。

RabbitAutoConfiguration类的属性通过RabbitProperties属性来获取,根据RabbitProperties类上的注解可知,它对应的是配置文件中的spring.rabbitmq前缀。在RabbitProperties中,有一些默认配置,如果不需要改动,可以不在yml里配置,让它走默认的配置也可以。我们需要根据自己的情况,在yml里做一下配置。

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

编写测试方法测试收发消息。

package com.atguigu.amqp;

import com.atguigu.amqp.bean.Person;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

@SpringBootTest
class SpringBootAmqp02ApplicationTests {
    @Autowired
    RabbitTemplate rabbitTemplate;

    @Test
    void contextLoads() {
    }

    // 单播(点对点)
    @Test
    public void unicast() {
        // Message需要自己构造,可以调用Message的构造方法定制消息头和消息体
        // public Message(byte[] body, MessageProperties messageProperties) {
        //     this.body = body;
        //     this.messageProperties = messageProperties;
        // }
        // rabbitTemplate.send(exchange, routeKey, message);

        // object默认当前消息体,只需要传送要发送的对象,会自动做序列化处理(默认使用Java序列化)
        // rabbitTemplate.convertAndSend(exchange, routeKey, object);
        Map<String, Object> map = new HashMap<>();
        map.put("message", "这是第一条消息");
        map.put("data", Arrays.asList("hello world", 123, true));
        rabbitTemplate.convertAndSend("exchange.direct", "atguigu.news", map);
    }

    // 接收消息
    @Test
    public void receive() {
        Object object = rabbitTemplate.receiveAndConvert("atguigu.news");
        System.out.println(object.getClass());
        System.out.println(object);
        Person person = (Person) object;
        System.out.println(person);
    }

    // 广播
    @Test
    public void broadcast() {
        Person person = new Person(1, "王劭阳");
        rabbitTemplate.convertAndSend("exchange.fanout", "", person);
    }
}

在Web管理页面中看到的内容是看不懂的,因为默认使用的是SimpleMessageConverter,也就是采用Java的序列化方式。现在我们需要使用json序列化方式来替代它,需要自己写一个配置类来替换这个MessageConverter。

package com.atguigu.amqp.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 MyAMQPConfig {
    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }
}

再次测试发送和接收,在Web管理界面看到的就是json格式的数据了。如果传输的是对象,在接收数据之后,使用强制类型转换可能会报错,此时,需要检查被强转类型有没有无参构造函数。因为Fastjson需要用到无参构造函数。并且被转换的类不能是内部类,要放在实体类下面。

有些场景下需要用到监听器,监听队列里的消息,此时需要用到@RabbitListener注解和@EnableRabbit。在主启动类上加上@EnableRabbit注解,写一个service来做监听,在service方法上加上@RabbitListener注解,指定监听队列,当监听到队列里有消息的时候,就会调用这个service方法。

package com.atguigu.amqp.service;

import com.atguigu.amqp.bean.Person;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

@Service
public class PersonService {
    @RabbitListener(queues = "atguigu.news")
    public void listen(Person person) {
        System.out.println("收到消息:" + person);
    }

    // 将消息取出之后,根据需要做处理
    @RabbitListener(queues = "atguigu")
    public void listen2(Message message) {
        System.out.println(message.getBody());
        System.out.println(message.getMessageProperties());
    }
}

AmqpAdmin可以帮助我们创建和删除Queue,Exchange,Binding等,添加自动注入AmqpAdmin对象,用于测试方法。

@Test
public void operateRabbitMQ() {
    // 创建Exchange
    amqpAdmin.declareExchange(new DirectExchange("amqpadmin.exchange"));
    // 创建Queue
    amqpAdmin.declareQueue(new Queue("amqpadmin.queue"));
    // 创建绑定规则
    amqpAdmin.declareBinding(new Binding("amqpadmin.queue", Binding.DestinationType.QUEUE, "amqpadmin.exchange", "amqp", null));
    // 删除Exchange
    amqpAdmin.deleteExchange("amqpadmin.exchange");
    // 删除Queue
    amqpAdmin.deleteQueue("amqpadmin.queue");
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值