索引
背景
开局一只狗,介绍是少不了的,那么对于我这种渣渣来说,看源码,ctry+左键,翻得翻得好像搞明白源码的流程了,再回头yy,咦?我清楚的有那一个步骤来着,是干啥来着???就很难受,不得不面对真实的自己,不哔哔了,淦!
一个故事:异步处理
淦之前还得哔哔一个故事:
脑补这个一个场景,我要注册一个网站的会员,按他们的流程是:用户注册后给发一份邮件、给用户发优惠券、发糖、发小板凳。。。一系列操作
如果我是开发者,是不是有这么几个思想:
1:用户注册后,直接调用发邮件、发糖、发小板凳等等一系列操作,统统一下子都执行,执行完成后给用户true。
2:用户注册后,利用多线程的方式,并发执行操作,操作完告诉用户true。
3:用户注册后,把后续任务写入消息队列成功,给用户true,然后程序默默的执行一系列操作或者多线程执行。
相比之下,那绝对是第三种方式体验贼棒啊,响应速度相比第一种和第二种快了N倍,现在这个流量时代,抓不住用户的喜好,就与用户无缘。
一个故事:应用解耦
订单与库存:订单系统处理完之后直接请求库存系统,进行库存商品自减,像是连体婴
方案:订单系统只管下命令,不直接找库存系统了,而是发到消息队列,库存系统去消息队列取任务,双方谁也不紧张。
一个故事:流量削峰
十万个人同时要抢诺亚方舟的船票,可票只有10张,我的系统不可能判断十万次请求,一个个跑订票的业务逻辑。
消息队列:你尽管抢,我消息队列只存100个请求,剩下的9万9千9百请求直接返回抢票失败,也就不进订票的业务,剩下的100个请求再慢慢研究谁是最后10名幸运儿。
例子有些激进,主要理解消息队列和名词。
概述
消息服务中两个重要概念:
1:消息代理:消息发送者发送消息后,由消息代理接管,消息代理保证消息传递到指定目的地。
2:目的地:消息队列主要有两种形式的目的地,队列(点对点)和主题(发布/订阅)
点对点式:
A发送消息到消息代理,B可以从消息代理取出消息,然后消息代理里的消息就没了。当然不止是B可以拿消息,也可以有C、D…如果A拿走了消息,别人就拿不到了。这个拿走就叫消费。大白话,蟹堡王门口N个人排队,但只有一个蟹黄堡,派大星吃掉这个蟹黄堡后,别人就只有饿肚子。
发布/订阅式:
wx公众号,关注了公众号之后,他发任何消息,关注者都会收到
消息代理规范
JMS (Java Message Service):Java提供的消息服务
AMQP(Advanced Message Queuing Protocol):高级消息队列,兼容JMS,RabbitMQ是AMQP的实现。
RabbitMQ简介
RabbitMQ消息队列嘛,它是erlang开发的AMQP的开源实现,用来解决上边说的几种业务场景。这个erlang就很神奇,之前我有个win虚拟机,给装过RabbitMQ,过了好久,找的时候发现找不见了,怀疑有没有装过,后来才发现一个叫erlang的文件夹。。。
核心概念
1.Message:消息,由消息头和消息体组成。消息体不透明,消息头类似http请求头,不过参数不一样,可以这么理解。其中头里边重要的是routing-key(路由键)、priority(相对于其它消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)
2.Publisher:消息的生产者,也是一个向交换器发布消息的客户端应用程序。
3.Exchange:交换器,用来接收生产者发送的消息,并把这些消息路由给服务器中的队列。它有四种类型:direct(默认)、fanout、topic、headers,direct是点对点式的,其它的是发布/订阅式的,策略不一样。交换器根据消息头的路由键来指定消息该去哪。
4.Queue:消息队列,用来保存消息直接发送给消费者。它是消息的容器,一个消息可以投入一个或多个队列。消息一直在队列里,等待消费者过来把它取走。
5.Binding:绑定,消息队列与交换器之间的关联。就是 根据路由键派发消息到哪个消息队列的规则。绑定规则可以是多对多的关系。
6.Connection:网络连接,比如一个TCP连接。
7.Channel:信道,比如TCP建立连接后,不可能有多条,因为太耗费资源了,所以信道就是说,在一个连接里开辟多条通道,信道才是真正发送消息、接收消息。。。用的
8.Consumer:消息的消费者,从消息队列取消息的
9.Virtual Host:虚拟主机,表示一批交换器、消息队列和相关对象。每个虚拟主机相当于mini版RabbitMQ服务器,相互隔离,有自己完整的一套队列、交换器、绑定、权限机制。必须在连接时指定,RabbitMQ默认的vhost是“/”(斜杠)。也可以这么理解,虚拟主机就是多个相互隔离的RabbitMQ服务器。
10.Broker:代理,就是消息队列服务器实体
关系图

RabbitMQ运行机制
消息是如何抵达队列的?
首先,生产者创建出一个消息,发给消息队列服务器,但是首先抵达的是队列服务器里的交换器,交换器根据这个消息的路由键,把它派发到某队列,派发规则就叫绑定,而且可以是多对多的关系(一个交换器可以绑定好多队列,一个队列也可以被多个交换器绑定)。那么核心也就可想而知,就是交换器和绑定规则。
交换器(Exchange)
Exchange分发消息时,根据类型的不同分发策略也有区别。目前共有四种类型:direct、fanout、topic、headers。headers匹配的是消息的header而不是路由键,与direct完全一致,但性能差很多,所以直接看另外三种就可以了。
1.Direct Exchange:典型的点对点通信模型(单播),当路由键与绑定的键一模一样的时候派发给相应队列。举个栗子,比如有三个消息队列,分别是cat、cat.eat、cat.sleep,当路由键是cat的时候,只会给cat这个消息队列发送消息。
2.Fanout Exchange:广播模式,还是那三个队列,不管路由键是啥,这三个队列都会收到发来的消息。这个是发消息最快的,也是发布/订阅模型的参考实现。
3.Topic Exchange:还是那三个队列,队列允许模糊匹配,符号是#和*,#匹配0个或多个单词,*匹配一个单词,比如cat.#、#.cat。完成有选择性的广播通信。
RabbitMQ安装
1.移驾centos7,xshell连上
2.查看镜像,是否有RabbitMQ:
docker images
3.没安装过肯定没有,所以去dockerhub一趟,搜下,并确定要安装的版本,推荐安装tag带manager的,因为带管理界面。然后docker pull,找国内镜像加速站加速
//不加速
docker pull rabbitmq:3.8.9-management-alpine
//加速
docker pull 3laho3y3.mirror.aliyuncs.com/library/rabbitmq:3.8.9-management-alpine
4.再看下镜像:这次肯定有了
docker images
5.然后启动运行镜像,-d代表后台运行,带管理界面的RabbitMQ有两个端口,分别是5672和15672,-p暴露端口,把主机的5672映射到docker容器的5672,15672同理,–name是给起个名字,后边跟上容器id。顺便看一下运行没。看到运行了就可以尝试访问管理界面,默认账号密码都是guest。
docker run -d -p 5672:5672 -p 15672:15672 --name myrabbitmq 5c00f781d2c7

RabbitMQ界面操作(创建交换器、消息队列、 绑定关系)
访问成功后,继续看界面,上边一排6个菜单,点到交换器,人家默认有7个交换器,先不管他,自己新建上边说的那三种类型的交换器,注意Durability选项,意思是持久化,如果是持久化,下次重启RabbitMQ这个交换器还在,不持久化重启后就没这个交换器了。


交换器建好后,点到队列建4个消息队列。

交换器和队列都建好之后,点到交换器搞绑定关系,进入dirct,填写to queue,Routing key(路由键)完成绑定,绑定如下图

进入fanout,绑定如下图,因为是广播,所以路由键可有可无。

进入topic,用#、*匹配,绑定如下图。

RabbitMQ消息测试
接下来进行测试。
Drict测试
先进入dirct,写路由键和信息,publish message

因为是点对点,所以完全匹配的绑定规则才会被队列保存。

点进去看一下,get message可以看到测试信息“dirct.exchang.msg.hello”


Fanout测试
因为是广播,所以,无论路由键是什么,有没有路由键,都会被保存,下图可以看出来,publish message之后,所有的队列里都多了一条信息。

Topic测试
因为是匹配广播,所以,只有符合匹配规则的才会被保存

可以点进去核对一遍。

另外:测试的时候因为每次get message只能有一条信息,为了测试方便,把Ack Mode模式改为第二个,就可以逐一看到消息了,不过看完之后自动就删了。这是因为这个模式是给消息队列说:我拿到了,你可以删了。

到这界面测试就完了,下边就搞源码、编码了。
RabbitTemplate发送接收消息测试
1.IDEA新建工程,并引入web模块、RabbitMQ模块

2.看看pom文件里的Rabbitmq模块及重要Bean
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
点进去可以看到有message、rabbit模块,都知道,只要引入starter,springboot就会帮着自动配置,那么,也看一下自动配置原理吧。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>5.2.9.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.2.11.RELEASE</version>
<scope>compile</scope>
</dependency>
自动配置是由RabbitAutoConfiguration这个类做的,IDEA两下shift键,搜一下这个类

感兴趣可以进去看看自动配置做了哪些,每个版本略有不同,本菜鸟一边瞅源码一边实践一边写博客码字有点吃不消。列一下源码里主要的几个东西:
1).CachingConnectionFactory,连接工厂,它实现了ConnectionFactory
2)…RabbitProperties,封装了所有配置
3)…RabbitTemplate,给RabbitMQ发送和接收消息
4)…AmqpAdmin,RabbitMQ系统管理功能组件,可以声明队列、创建交换器等
3.用RabbitTemplate测试direct,为了方便,管理界面我已经把所有消息清空了。

上测试代码:
@Autowired
RabbitTemplate rabbitTemplate;
/*
*
* @Author zo10010
* @Description //TODO
* @Date 0:41 2020/10/18
* @Param 1.单播,点对点
* @return
**/
@Test
void contextLoads() {
//message需要自己构造一个;定义消息内容和消息头
//rabbitTemplate.send(exchange,routeKey,message);
//object默认当成消息体,只需要传入要发送的对象,自动序列化给rabbitmq
//rabbitTemplate.convertAndSend(exchange,routeKey,message);
Map map = new HashMap<String,Object>();
map.put("msg","第一条消息");
map.put("data", Arrays.asList("hello",123,true));
rabbitTemplate.convertAndSend("exchange.direct","niupi",map);
}
现在可以看到,只有路由键是niupi的有消息,而且是一堆不认识的东西。content_type是说用java的序列化搞的

下来接收数据,上代码
@Test
void test() {
//receiveAndConvert里的参数是消息队列的名称
Object o = rabbitTemplate.receiveAndConvert("niupi");
//打印接收来对象的类型
System.out.println(o.getClass());
//打印接收来对象
System.out.println(o);
}
打印结果,并且回管理界面看,消息队列里已经没了,完成
class java.util.HashMap
{msg=第一条消息, data=[hello, 123, true]}
测fanout
Map map = new HashMap<String,Object>();
map.put("msg","第二条消息");
map.put("data", Arrays.asList("hello",123,true));
rabbitTemplate.convertAndSend("exchange.fanout","",map);
测topic
Map map = new HashMap<String,Object>();
map.put("msg","第三条消息");
map.put("data", Arrays.asList("hello",123,true));
rabbitTemplate.convertAndSend("exchange.fanout","niupi.map",map);
RabbitTemplate序列化改为JSON
因为上边测试的消息里的东西有点看不懂(java序列化),那搞一下搞成json格式的,之所以是java序列化,是因为RabbbitTemplate里有个MessageConverter默认用的是java的,所以把MessageConverter换掉就行了,那么MessageConverter有哪些实现能直接用?不慌,鼠标指在它上边ctry+t,找吧,看到json相关的Jackson2JsonMessageConverter,就用它。新建自己的配置类。配置会自动生效,因为源码里是判空用默认,非空就用用户配置,搞定它。

import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 〈一句话功能简述〉<br>
* 〈〉
*
* @author Administrator
* @create 2020/10/18
* @since 1.0.0
*/
@Configuration
public class MyAMQPConfig {
@Bean
public MessageConverter MessageConverter1(){
return new Jackson2JsonMessageConverter();
}
}
RabbitMQ监听
yy一下订单系统和库存系统,订单系统要把订单信息放到消息队列,库存系统去监听队列并取出消息,然后进行处理。就涉及到监听。
代码描述:好比下边代码的service就是一个普通的service,给需要接收订单信息的方法上加上@RabbitListener注解,queues的值是监听哪个消息队列,然后主程序需要加上@EnableRabbit注解,启动主程序就开始监听了,可以自行测试。这里的监听,只要拿到数据,消息队列里就会删除这个数据。
@Service
public class Mapservice {
@RabbitListener(queues = "niupi")
public void receive(Map<String,Object> map){
System.out.println("接收到消息了:" + map.toString());
}
}
@EnableRabbit
@SpringBootApplication
public class AmqptestApplication {
public static void main(String[] args) {
SpringApplication.run(AmqptestApplication.class, args);
}
}
如果有一些定制的消息,接收的这边需要获取到头信息,那就用amqp.core包里的Message。
@RabbitListener(queues = "niupi.news")
public void receive02(Message message){
MessageProperties messageProperties = message.getMessageProperties();
System.out.println(message.getBody());
System.out.println(message.getMessageProperties());
}
Rabbit的AmqpAdmin管理组件
它的作用:创建和删除Queue、Exchange、Binding,毕竟要用代码代替界面操作,直接上说明和代码:
首先在测试类注入AmqpAdmin,为什么能直接注入,因为自动配置类里有写入这个组件。
@Autowired
AmqpAdmin amqpAdmin;
下边代码分别创建了交换器、队列、绑定,关于参数,可阅读源码选择自己要的类型
@Test
void test2() {
//创建Exchange
//amqpAdmin.declareExchange(new DirectExchange("amqpadmin.exchange"));
//System.out.println("创建完成");
//创建Queue
//amqpAdmin.declareQueue(new Queue("amqpadmin.queue",true));
//创建Binding
//amqpAdmin.declareBinding(new Binding("amqpadmin.queue", Binding.DestinationType.QUEUE, "amqpadmin.exchange", "amqpadmin.hehe",null));
}
结果

结
本篇只做入门、上手使用,具体业务场景、方式方法等东西需ctry+左键浏览源码
2144



被折叠的 条评论
为什么被折叠?



