问:什么是消息总线?
答:在微服务架构中,通常使用轻量级的消息代理来构建一个公用的消息主题,以便让系统中所有的微服务实例连接上来,该主题中产生的消息会被所有实例监听和消费,称之为消息总线。
我们经常用到消息代理的场景:
1、将消息路由到一个或者多个目的地
2、将消息转化成其它的表现方式
3、将消息聚集、分解,并将结果发送到目的地,然后重新组合响应返回给消息用户。
4、响应事件或错误
5、使用发布-订阅模式来提供内容或基于主题的消息路由。
目前 SpringCloud Bus 支持两款比较常用的中间件产品:RabbitMQ 和 Kafka。
RabbitMQ 实现消息总线
RabbitMQ 是一款实现了高级消息队列协议(AMQP:Advanced Message Queuing Protocol)的开源消息代理软件,也称为面向消息的中间件。SpringCloud Bus 中包含了对 Rabbit 的自动化默认配置。
基本概念
1、Broker:消息队列服务器的实体,负责接收消息生产者的消息,然后将消息发送至接收者或其他 Broker。
2、Exchange:消息交换机,消息到达的第一个地方,消息通过它的路由规则,分发到不同的消息队列中去。
3、Queue:消息队列,消息被发送和路由最终到达的地方,到达 Queue 的消息即进入逻辑上等待消费的状态。
4、Binding:绑定,作用是将 Exchange 和 Queue 按照路由规则绑定起来,相当于虚拟连接。
5、Routing Key:路由关键字,Exchange 根据这个关键字进行消息投递。
6、Virtual host:虚拟主机,它是对 Broker 的虚拟划分,将消费者、生产者和协议等进行隔离,是为了安全考虑,可设置多个虚拟主机。
7、Connection:连接,代表生产者、消费者、Broker 之间进行通信的物理网络。
8、Channel:消息通道,用于连接生产者、消费者的逻辑结构。在客户端的每个连接里,可以建立多个 Channel,每个 Channel 代表一个会话任务,可以隔离同一个连接中不同的交互内容。
9、Producer:消息生产者,制造消息并发送消息的程序。
10、Consumer:消息消费者,接收消息并处理消息的程序。
消息投递到队列的整个过程大致如下:
1、客户端连接到消息队列服务器,打开一个通道 Channel
2、客户端声明一个 Exchange,并设置相关属性规则。
3、客户端声明一个消息队列 Queue,并设置相关属性。
4、客户端使用 Routing Key,在 Exchange 和 Queue 之间建立好绑定关系。
5、客户端投递消息到 Exchange。
6、Exchange 接收到消息后,根据消息的 Key 和已经设置的 Binding,进行消息路由,将消息投递到一个或者多个Queue。
Exchange 有几种类型:
1、Direct交换机:完全根据 Key 进行投递。比如绑定了 Routing Key 为 abc123,那么客户端提交的消息,只有设置了 Key 为 abc123 的才会被投递到队列。
2、Topic交换机:对 Key 进行模式匹配后进行投递,可以使用符号 # 匹配一个多个词,符号 * 匹配一个词。比如:abc.# 可以匹配 abc.edf.nice abc.* 只能匹配 abc.beautiful 。注意是词,不是单个字符。
3、Fanout交换机:不需要任何 Key,采用广播式,一个消息进来时,投递到所有绑定的队列中。
RabbitMQ 支持消息持久化,也是将数据写到磁盘上。为了安全考虑,多数情况下都会选择持久化。包含3个部分:
1、Exchange 持久化,在声明时指定 durable =>1
2、Queue持久化,在声明时指定 durable => 1
3、消息持久化,在投递时指定 delivery_mode => 2(如果是1,则是非持久化)
如果 Exchange 和 Queue 都是持久化,则他们之间的 Binding 也是持久化的。如果有一个不是持久化,则不允许建立绑定。
OK,理论少说,我们直接动手。
1、先下载 Erlang 和 RabbitMQ。我们是在 windows 下学习的,所以安装 windows 版。
先下载 Erlang。打开链接:https://www.erlang.org/downloads 下载 OTP22.0 (一般电脑都是 64 位了,32位是老人机了吧?)。
安装 Erlang。注意安装路径不要起中文名字,这是做一个程序员最基本的要求(除非是国产的东西,才对中文客气)。
安装过程跟一般的软件一样,这里不赘述。如果是正常的安装,Erlang 会自动帮我们配置好环境变量:
然后,下载 RabbitMQ,打开链接:https://www.rabbitmq.com/news.html 下载 RabbitMQ 3.7.20 release 版本。它要求 Erlang 最低版本在 21.3,我们下载的是 22.0,兼容低版本。
接下来,安装 RabbitMQ。注意会弹出一个防火墙的访问设置,点击允许即可。
RabbitMQ Server 安装完成之后,会自动注册为服务,并且使用默认的配置。我们可以通过访问配置文件、Web页面进行Rabbit 管理。推荐使用 Web 进行管理。注意:RabbitMQ 默认监听的端口号是 5672,web端的端口是 15672
安装完毕之后,我们浏览器地址访问:http://localhost:15672/ 发现无法访问。我们需要执行一段命令,来开启 Web 管理。
1、首先,我们找到 RabbitMQ 的安装路径,找到安装目录下的 sbin 文件夹,在 sbin 文件夹里找到 rabbitmq-queues.bat
2、对着文件 rabbitmq-queues.bat 鼠标右键,找到“属性”,把 rabbitmq-queues.bat 所在的路径复制出来,如截图:
3、打开电脑的菜单键——》输入cmd(然后按Enter键确定)——》然后按住键盘的 Shift + Insert 组合键,把刚才复制的路径粘贴到命令行中,然后敲一个空格(切记),再复制下面的命令(开启 Web 插件),粘贴到命令行中,然后回车。
enable rabbitmq_management
完整的形式如下(这时候一般会报错,因为 CMD 命令行不是管理员权限):
因为 CMD 不是管理员权限,我们需要做一些修改。具体方法如下:
也可以参考我的博客,里面有详细的方法:https://blog.csdn.net/BiandanLoveyou/article/details/84677787
解决办法:先关闭命令提示符,然后打开电脑的系统盘C盘。
打开路径:C:\Windows\System32 然后在右上角的搜索框输入cmd.exe
把这个 cmd.exe 可执行文件放到桌面快捷方式
然后,到桌面找到它,对着快捷方式,鼠标右键,选择属性。找到“快捷方式”,选择“高级”
勾选“用管理员身份运行”,确定。
然后双击打开桌面的快捷方式,再重试刚才的操作:先复制 rabbitmq-queues.bat 的路径,Shift + Insert 粘贴,再空格,再粘贴 enable rabbitmq_management。成功的截图如下:
接下来,我们重启一下 RabbitMQ Server 服务。在有管理员权限的命令行输入如下命令(复制,然后 Shift + Insert 粘贴):
net stop RabbitMQ && net start RabbitMQ
以后就可以使用命令 net start RabbitMQ 来开启 RabbitMQ Server 服务了。
OK,大功告成。浏览器地址栏输入:http://localhost:15672/ 显示登录页面,账号、密码都是:guest
这时候,可以进入到 Web 管理页面了
可以注意到,顶部菜单选项卡正是我们之前介绍的专业术语
接下来,我们新增一个用户。点击 Admin 选项卡,找到“Add a user”
我们新增一个用户,在 Tags 选项,我们直接选择 Admin(为方便学习)
说明:Tags 标签是 RabbitMQ 中的角色分类,有以下几种
1、None:不能访问 management plugin
2、Impersonator:演示者
3、Management:普通管理者,除了可以通过 AMQP 做的事外加如下内容
①列出自己可以通过 AMQP 登入的 virtual hosts
②查看自己的 virtual hosts 中的 queues、exchange和 bindings。
③查看和关闭自己的 channels 和 connections
④查看有关自己的 virtual hosts 的“全局”统计信息。
4、Policymaker:策略制定者,除了 management 可以做的任何事,还可以查看、创建和删除自己的 virtual hosts 所属的 policies 和 parameters。
5、Monitoring:监控者,除了可以做 management 任何事,还包括
①列出所有的 virtual hosts,包括它们不能登录的 virtual hosts
②查看其他用户的 connections 和 channels。
③查看节点级别的数据,如 clustering(集群) 和 memory(内存) 的 使用情况。
④查看真正的关于所有 virtual hosts 的全局的统计信息。
5、Admin:超级管理员,除了 policymaker 和 monitoring 可以做的事,还包括
①创建和删除 virtual hosts
②查看、创建、删除 users
③查看、创建、删除 permissions
④关闭其他用户的 connections。
接下来,我们学习如何整合 RabbitMQ 到 SpringCloud 架构中,实现一个简单的发送、接收消息例子。
首先,我们创建一个 SpringBoot 模块:rabbitmq-bus
pom.xml 配置文件中,增加依赖:spring-cloud-starter-bus-amqp,完整的配置如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>MyProject</artifactId>
<groupId>com.study</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>rabbitmq-bus</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<!-- 添加 SpringBoot 测试类的依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>net.minidev</groupId>
<artifactId>asm</artifactId>
<version>1.0.2</version>
</dependency>
</dependencies>
</project>
新增 application.yml 配置文件,完整配置信息如下(这里的用户名、密码使用默认的 guest):
# 这是客户端服务的配置节点
server:
port: 8050
# 服务的名字
spring:
application:
name: eureka-rabbitmq-bus
# rabbitmq 的配置信息
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
接下来,我们创建启动的主类:RabbitMQBusApplication
完整的 RabbitMQBusApplication 代码如下:
package com.study;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author biandan
* @signature 让天下没有难写的代码
* @create 2019-11-02 上午 11:53
*/
@SpringBootApplication
public class RabbitMQBusApplication {
public static void main(String[] args) {
SpringApplication.run(RabbitMQBusApplication.class,args);
}
}
接下来,我们创建消息生产者类:MessageSender,注入 AmqpTemplate 接口的实例来实现消息的发送。AmqpTemplate 接口定义了一套针对 AMQP 协议的基本操作。
先创建一个 package 用来存放 MessageSender 类
完整的 MessageSender 代码如下:
package com.study.sender;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author biandan
* @signature 让天下没有难写的代码
* @create 2019-11-02 下午 12:05
*/
@Component
public class MessageSender {
@Autowired
private AmqpTemplate amqpTemplate;
//格式化时间
private static SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/**
* 发送消息的方法
*/
public void sendMessage() {
String message = "让天下没有难写的代码:" + SDF.format(new Date());
System.out.println("将要发送的消息是:" + message);
this.amqpTemplate.convertAndSend("messageKey", message);
}
}
接下来,我们创建消息消费者类:MessageReceiver,通过 @RabbitListener 注解定义该类对 messageKey(即消息生产者中定义的队列 key) 队列的监听。并使用 @RabbitHandler 注解来指定对消息的处理方法。
先创建一个 package 包,receive
MessageReceiver 完整代码如下:
package com.study.receive;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @author biandan
* @signature 让天下没有难写的代码
* @create 2019-11-02 下午 12:31
*/
@Component
@RabbitListener(queues = "messageKey")
public class MessageReceiver {
@RabbitHandler
public void receiveMessage(String message) {
System.out.println("接收到的消息是:" + message);
}
}
接下来,我们还要创建 RabbitMQ 的配置类 RabbitConfig(可以起别的自定义名字),用来配置队列、交换器、路由等高级信息。
完整的 RabbitConfig 代码如下(注意:Queue 导入的是 org.springframework.amqp.core.Queue):
package com.study.config;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author biandan
* @signature 让天下没有难写的代码
* @create 2019-11-02 下午 12:40
*/
@Configuration
public class RabbitConfig {
@Bean
public Queue queueConfig(){
return new Queue("messageKey");
}
}
接下来,我们创建单元测试类:RabbitMQTest。单元测试,是每个程序员都应该懂的技巧。
完整的 RabbitMQTest 代码如下:
package com.study.test;
import com.study.RabbitMQBusApplication;
import com.study.sender.MessageSender;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author biandan
* @signature 让天下没有难写的代码
* @create 2019-11-02 下午 12:49
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = RabbitMQBusApplication.class)
@SpringBootConfiguration
public class RabbitMQTest {
@Autowired
private MessageSender messageSender;
@Test
public void sendTest(){
messageSender.sendMessage();;
}
}
注意:
1、SpringBoot 2.0 之后,对 Junit 测试类的 pom 依赖有所变化,请按照上面的代码添加依赖。
2、@SpringBootTest(classes = RabbitMQBusApplication.class):classes 使用的是启动类名
接下来,我们先保证 RabbitMQ Server 处于启动状态,然后运行启动类:SpringBootApplication
这时候,我们查看控制台的输出信息:
Created new connection: rabbitConnectionFactory#4eb166a1:0/SimpleConnection@8641b7d
[delegate=amqp://guest@127.0.0.1:5672/, localPort= 51344]
说明程序创建了一个访问 127.0.0.1:5672(localhost)的 SpringCloud 连接,我们再去浏览器端查看 Connections 和 Channels:
已经有我们的连接条数。
接下来,运行测试类的 sendTest() 方法:对着方法名鼠标右键,Debug 运行
如果运行报错,会出现红色的信息,如果运行正常,会出现绿色信息:
说明消息生产者已经把消息发送到 RabbitMQ Server 的 messageKey 队列了,并且被监听器监听到,输出了消息内容。
在整个生产、消费的过程中,生产者和消费者是一个异步的操作,这也是在分布式系统中使用消息代理的重要原因,我们可以使用通信来解耦业务逻辑。
接下来,我们测试一下异步的操作:把 MessageReceiver 类的 @RabbitHandler 注解先注释掉,让消息无消费者消费。
然后,我们重复运行测试类的 sendTest() 方法 3 次,让消息生产者生产 3 条消息,无消费者消费。
我们再去 Web 端查看 Queues 选项卡,多了 3 条信息。
这时候,再把 MessageReceiver 类的 @RabbitHandler 注释去掉,再运行测试类的 sendTest() 方法。
我们观察到,消息监听器监听到之前有 3 条消息未被消费,直接去消费了。然后新增1条消息,也消费了。
再去 Web 端查看 Queues 选项卡,已经没有待消费的消息了。这就实现了消息的异步处理。