概述
RabbitMQ是一种消息中间件,用于处理来自客户端的异步消息。服务端将要发送的消息放入到队列池中。接收端可以根据RabbitMQ配置的转发机制接收服务端发来的消息。
RabbitMQ依据指定的转发规则进行消息的转发、缓冲和持久化操作,主要用在多服务器间或单服务器的子系统间进行通信,是分布式系统标准的配置。
使用场景
虚拟机配置
- 解压出来
- 通过VMware打开
- 内容
- yun 安装源, 扩展源使用了阿里服务器
- 安装了Python, pip, ansible
- 添加了两个脚本文件, 方便配置IP地址
- IP-static- 配置固定IP
- IP-dhcp: 自动获取IP
- 使用
./ip-dhcp
命令自动获取IP - 关机克隆设置名字为 Docker-base 用来安装docker
ifconfig
查看IP地址- 使用MobaXterm连接虚拟机
Docker
下载docker离线包
https://download.docker.com/linux/static/stable/x86_64/docker-20.10.6.tgz
这个地址可以选择自己需要的版本进行下载:
https://download.docker.com/linux/static/stable/
离线安装工具
https://github.com/Jrohy/docker-install/
安装
-
将前面下载的以下文件放入服务器的
/root/docker-install
文件夹下:- [docker-install] - docker-20.10.6.tgz - install.sh - docker.bash
-
执行安装:
# 进入 docker-install 文件夹 cd docker-install # 为 docker-install 添加执行权限 chmod +x install.sh # 安装 ./install.sh -f docker-20.10.6.tgz
镜像加速
由于国内网络问题,需要配置加速器来加速。
修改配置文件 /etc/docker/daemon.json
下面命令直接生成文件 daemon.json
cat <<EOF > /etc/docker/daemon.json
{
"registry-mirrors": [
"https://docker.mirrors.ustc.edu.cn",
"http://hub-mirror.c.163.com"
],
"max-concurrent-downloads": 10,
"log-driver": "json-file",
"log-level": "warn",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"data-root": "/var/lib/docker"
}
EOF
重启服务
# 重新加载docker配置
sudo systemctl daemon-reload
#重启docker服务
sudo systemctl restart docker
测试
docker info
docker run hello-world
Docker运行RabbitMQ
- 克隆Docker虚拟机: RabbitMQ
- 设置IP地址
- MobaX连接虚拟机
RabbitMQ配置
- Rabbit镜像拖入root目录
-
导入镜像docker load < rabbit-image.gz
-
关闭防火墙
systemctl stop firewalld systemctl disable firewalld # 重启 docker 系统服务 systemctl restart docker
-
配置管理员账号密码
mkdir /etc/rabbitmq vim /etc/rabbitmq/rabbitmq.conf # 添加两行配置: default_user = admin default_pass = admin
-
启动Rabbitmq
docker run -d --name rabbit \ -p 5672:5672 \ -p 15672:15672 \ -v /etc/rabbitmq/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf \ -e RABBITMQ_CONFIG_FILE=/etc/rabbitmq/rabbitmq.conf \ --restart=always \ rabbitmq:management
-
访问测试
访问管理控制台 http://192.168.64.4:15672
用户名密码是 admin
rabbitmq六种工作模式
简单模式
准备工作
-
新建空项目
-
添加依赖
<dependencies> <dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>5.4.3</version> </dependency> </dependencies>
生产者发送信息
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
/*连接*/
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.4");
f.setPort(5672);
f.setUsername("admin");
f.setPassword("admin");
Connection con = f.newConnection(); // 创建连接
Channel c = con.createChannel(); // 通信的通道
/*在服务器上创建 helloworld 队列
* 队列如果存在, 不会重复创建*/
c.queueDeclare("helloworld",false,false,false,null); //队列定义/声明
/*向队列发送消息*/
c.basicPublish("","helloworld",null,"Hello World".getBytes()); // 2. 队列名
}
}
- setHost : 为 Rabbitmq服务虚拟机IP
- setPort : Rabbitmq端口号
- setUsername/setPassword : Rabbitmq配置的用户名密码
参数说明
-
队列名
-
布尔值(是否是持久队列)持久: 重启服务器队列存在 (在磁盘储存队列信息)
-
是否是排他队列 (独占队列) (是否可以共享队列)
-
队列能不能被服务器自动删除, (没有消费者时服务器是否自动删除)
-
队列的其他参数属性
-
交换机,空串是默认交换机
-
队列名
-
消息的其他参数属性 (用键值对设置参数属性, 如果没有就用 null )
测试
消费者接受消息
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
/*建立连接*/
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.4");
f.setPort(5672);
f.setUsername("admin");
f.setPassword("admin");
Connection con = f.newConnection(); // 创建连接
Channel c = con.createChannel(); // 通信的通道
/*创建队列*/
c.queueDeclare("helloworld",false,false,false,null); //队列定义/声明
/*创建回调对象*/
DeliverCallback deliverCallback = new DeliverCallback() {
public void handle(String s, Delivery delivery) throws IOException {
byte[] a = delivery.getBody();
String string = new String(a);
System.out.println("收到:"+ string);
}
}; // 处理消息的回调
CancelCallback cancelCallback = new CancelCallback() {
public void handle(String s) throws IOException {
}
}; // 取消消息的回调
/*从队列接收消息, 把消息传递到回调对象处理*/
// c.basicConsume("helloworld", true, 处理消息的回调对象, 取消消息的回调对象);
c.basicConsume("helloworld", true, deliverCallback, cancelCallback);
}
}
参数说明
-
确认方式 ACK – Acknowledgment
-
- true 自动确认
- false 手动确认
工作模式
多个消费者接收消息
生产者发送消息
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
/*创建连接*/
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.4");
f.setPort(5672);
f.setUsername("admin");
f.setPassword("admin");
Connection con = f.newConnection(); // 创建连接
Channel c = con.createChannel(); // 通信的通道
/*创建队列*/
c.queueDeclare("helloworld",false,false,false,null); //队列定义/声明
/*循环输入消息发送*/
while (true) {
System.out.println("请输入消息:");
String s = new Scanner(System.in).nextLine();
c.basicPublish("","helloworld",null,s.getBytes());
}
}
}
消费者接收消息
- 遍历每一个数据
- 当遇到" . " 时就暂停1秒
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.4");
f.setPort(5672);
f.setUsername("admin");
f.setPassword("admin");
Connection con = f.newConnection(); // 创建连接
Channel c = con.createChannel(); // 通信的通道
/*创建队列*/
c.queueDeclare("helloworld",false,false,false,null); //队列定义/声明
/* 回调对象*/
DeliverCallback deliverCallback = (consumerTag, message) -> {
String s = new String(message.getBody());
System.out.println(s);
// 遍历所有字符,遇到 '.' 暂停1秒
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '.') {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
System.out.println("-------------------------------消息处理完成");
};
CancelCallback cancelCallback = consumerTag -> {};
// 接收消息
c.basicConsume("helloworld",true,deliverCallback,cancelCallback);
}
}
测试一
-
Producer发送消息
-
Consumer接收消息
-
查看服务器
测试二
-
启动多个消费者
-
Producer 发送消息
-
查看两个Consumer接收
- 通过" . " 测试, 先发送一堆 " . "
- Consumer接收到 “…” 接着发送 1-9测试
-
接收到"…"的Consumer-2 进入等待
-
另一个 Consumer 打印出一部分信息
-
Consumer-2 等待结束后, 打印出剩下的消息
测试三(手动确认回执)
修改成手动确认回执
- 修改Consumer 手动确认
-
添加回执
-
添加Qos
测试
-
produce 发送 “…” 和 1-9
-
Consumer2接收后进入等待
-
Consumer2接收后进入等待
-
Consumer接收到所有信息
队列持久化
- 将Producer 持久化改成 true, 队列名改成"task_queue"
-
basicPublish其他参数属性改为 持久化属性
-
修改Consumer 创建队列参数
-
修改Consumer 接收信息队列名 (task_queue)
-
添加队列信息测试, 打开RabbitMQ 服务器查看队列信息, 会发现有helloworld 和 task_queue 两个队列
-
重启 RabbitMQ 服务, 再打开RabbitMQ 服务器查看, 没有持久化的helloworld就没有了, 做了持久化的task_queue 还存在
发布订阅模式
生产者
package m3;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
/**
* 生产商
*
* @author 刘杰
* @date 2021/11/22 10:48:59
*/
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
// 连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.4"); // wht6.cn
f.setPort(5672);
f.setUsername("admin");
f.setPassword("admin");
Channel c = f.newConnection().createChannel();
// 创建 Fanout 类型的交换机: logs
// c.exchangeDeclare("logs", "fanout");
c.exchangeDeclare("logs", BuiltinExchangeType.FANOUT);
// 向 logs 交换机发送消息
while (true) {
System.out.print("输入消息:");
String s = new Scanner(System.in).nextLine();
// 第二个参数,对于fanout类型交换机无效
c.basicPublish("logs", "", null, s.getBytes());
}
}
}
消费者
package m3;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.TimeoutException;
/**
* 消费者
*
* @author 刘杰
* @date 2021/11/22 10:08:32
*/
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
// 连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.4"); // wht6.cn
f.setPort(5672);
f.setUsername("admin");
f.setPassword("admin");
Channel c = f.newConnection().createChannel();
// 1.创建随机队列 2.创建交换机 3.绑定 手动命名
String queue = UUID.randomUUID().toString();
c.queueDeclare(queue,false,true,true,null); //手动提供参数
c.exchangeDeclare("logs", BuiltinExchangeType.FANOUT);
// 第三个参数对 fanout 交换机无效
c.queueBind(queue, "logs", "");
// 正常的从随机队列接收处理消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
String s = new String(message.getBody());
System.out.println("收到:"+s);
};
CancelCallback cancelCallback = consumerTag -> {};
c.basicConsume(queue,true,deliverCallback,cancelCallback);
}
}
测试
主题模式
生产者
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.4"); // wht6.cn
f.setPort(5672);
f.setUsername("admin");
f.setPassword("admin");
f.setVirtualHost("/"); //默认空间
Channel c = f.newConnection().createChannel();
c.exchangeDeclare("direct_logs", BuiltinExchangeType.DIRECT);
while (true) {
System.out.println("输入消息: ");
String s = new Scanner(System.in).nextLine();
System.out.println("输入路由键: ");
String k = new Scanner(System.in).nextLine();
/*2: 路由键关键词
* 如果用默认交换机, 路由键就是队列名*/
c.basicPublish("direct_logs", k,null, s.getBytes(StandardCharsets.UTF_8));
}
}
}
消费者
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.4"); // wht6.cn
f.setPort(5672);
f.setUsername("admin");
f.setPassword("admin");
Channel c = f.newConnection().createChannel();
String queue = c.queueDeclare().getQueue();//服务器自动提供参数: 随机名,false,true,true .getQueue(): 得到随机名(因为后面要用)
c.exchangeDeclare("direct_logs", BuiltinExchangeType.DIRECT);
System.out.println("输入绑定键, 用空格隔开: "); // "aa bb cc"
String s = new Scanner(System.in).nextLine();
String[] a = s.split("\\s+");// \s是空白字符, +表示一到多个 (一个或多个都可以)
for (String k : a) {
c.queueBind(queue, "direct_logs", k);
}
DeliverCallback deliverCallback = (consumerTag, message) -> {
java.lang.String ss = new java.lang.String(message.getBody());
System.out.println("收到:" + ss);
};
CancelCallback cancelCallback = consumerTag -> {};
c.basicConsume(queue,true, deliverCallback, cancelCallback);
}
}
测试一
-
消费者输入绑定键
-
生产者输入消息 + 路由键
-
消费者收到消息
测试二
-
生产者随便输入
-
消费者不会受到
virtual host 设置空间
在RabbitMQ中叫做虚拟消息服务器VirtualHost,每个VirtualHost相当于一个相对独立的RabbitMQ服务器,每个VirtualHost之间是相互隔离的。exchange、queue、message不能互通
- 默认空间名为: " / "
查看结果
使用:
Sleuth + Zipkin 链路跟踪
随着系统规模越来越大,微服务之间调用关系变得错综复杂,一条调用链路中可能调用多个微服务,任何一个微服务不可用都可能造整个调用过程失败
spring cloud sleuth 可以跟踪调用链路,分析链路中每个节点的执行情况
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
Zipkin
https://github.com/openzipkin/zipkin
下载
启动服务器
在根目录打开终端控制器
java -jar zipkin-server-2.23.5-exec.jar --zipkin.collector.rabbitmq.uri=amqp://admin:admin@192.168.64.4:5672
如用自己的空间
java -jar zipkin-server-2.12.9-exec.jar --zipkin.collector.rabbitmq.uri=amqp://admin:admin@192.168.64.4:5672 / 空间名
Zipkin访问地址
http://localhost:9411/zipkin
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
配置yml
添加06网关, 三个消费服务
rabbitmq:
port: 5672
host: 192.168.64.4
username: admin
password: admin
virtual-host: /
zipkin:
sender:
type: rabbit # zipkin 向 Rabbit 转发日志
导入商城项目
-
课前资料/elasticsearch/pd-商城项目案例.zip 里面的 pd-web 文件夹,解压到 rabbitmq 工程目录
-
pom文件 SpringBoot版本改为2.3.2.RELEASE
-
配置连接数据库
-
执行启动类后配置启动类
-
在 working directory 中配置 pd-web 模块目录路径 然后重新启动
-
访问 http://localhost
订单发送到 RabbitMQ
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
配置yml
rabbitmq:
host: 192.168.64.4
port: 5672
username: amdin
password: admin
virtual-host: /
配置启动类
- 新建 spring 的 Queue 实例
- 封装队列的参数
- rabbitmq的自动配置类会自动发现这个Queue 实例
- 根据其中的参数自动在服务器上创建队列
@Bean
public Queue orderQueue() {
return new Queue("orderQueue", true, false, false);
}
队列参数解析
1. 持久队列
2. 独占队列
3. 不自动删除
修改 orderServiceImpl
-
注释 64-89行
-
添加 代码
- 注入对象: AmqpTemplate (用来封装发送消息代码的工具)
订单消费者
- 复制 pd-web 项目起名为 pd-web-consumer
- 修改pom文件
新建消费者类: OrderConsumer
-
添加注解
- @RabbitListener 通过注解配置就可以接受消息
- queues = “orderQueue” 队列名
-
创建方法
用注解配置接收消息
收到的订单通过原来的业务方法代码, 存储到数据库