Compare commits

...

4 Commits

Author SHA1 Message Date
bunny d0fcac5455 联邦队列完成; 2025-05-20 16:18:13 +08:00
bunny 84c2d06b0c 仲裁队列完成; 2025-05-20 14:35:07 +08:00
bunny 6fcf7a4b75 集群环境-负载均衡完成; 2025-05-20 11:07:36 +08:00
bunny f1e7394450 集群搭建完成; 2025-05-19 22:48:59 +08:00
20 changed files with 1504 additions and 21 deletions

View File

@ -5,14 +5,29 @@
```bash
docker run -d --name rabbitmq_master --restart=always \
-p 5672:5672 -p 15672:15672 \
-v ~/docker/docker_data/rabbitmq/rabbitmq_master/data:/var/lib/rabbitmq_master \
-v ~/docker/docker_data/rabbitmq/rabbitmq_master/conf:/etc/rabbitmq_master \
-v ~/docker/docker_data/rabbitmq/rabbitmq_master/log:/var/log/rabbitmq_master \
-v ~/docker/docker_data/rabbitmq/rabbitmq_master/data:/var/lib/rabbitmq \
-v ~/docker/docker_data/rabbitmq/rabbitmq_master/conf:/etc/rabbitmq \
-v ~/docker/docker_data/rabbitmq/rabbitmq_master/log:/var/log/rabbitmq \
-e RABBITMQ_DEFAULT_VHOST=rabbitmq_master \
-e RABBITMQ_DEFAULT_USER=admin \
-e RABBITMQ_DEFAULT_PASS=admin rabbitmq:3.13.7-management
```
**后续测试联邦队列**
如果后续需要学习联邦队列可以再复制下面的内容,根据自己需求修改下端口号
```bash
docker run -d --name rabbitmq_2 --restart=always \
-p 5673:5672 -p 15673:15672 \
-v ~/docker/docker_data/rabbitmq/rabbitmq2/data:/var/lib/rabbitmq \
-v ~/docker/docker_data/rabbitmq/rabbitmq2/conf:/etc/rabbitmq \
-v ~/docker/docker_data/rabbitmq/rabbitmq2/log:/var/log/rabbitmq \
-e RABBITMQ_DEFAULT_VHOST=rabbitmq2 \
-e RABBITMQ_DEFAULT_USER=admin \
-e RABBITMQ_DEFAULT_PASS=admin rabbitmq:3.13.7-management
```
## 基本示例
**生产者**
@ -615,6 +630,8 @@ void buildExchangeOverflowTest() {
>
> - 最大延迟时间:**2天48小时**
> - 必须匹配RabbitMQ版本
>
> **插件版本必须与RabbitMQ严格匹配如3.13.x需使用v3.13.x插件。**
#### 1、确认docker数据卷
@ -746,8 +763,8 @@ try {
---
### **2. 生产环境推荐方案**
#### **1Confirm 模式(轻量级确认)**
### 生产环境推荐方案
#### 1Confirm 模式(轻量级确认)
- **原理**:异步确认消息是否成功到达 Broker。
- **配置方式**
```java
@ -763,14 +780,14 @@ try {
```
- **优点**:性能接近非事务模式,可靠性高。
#### **2消息补偿 + 幂等设计**
#### 2消息补偿 + 幂等设计
- **步骤**
1. 消息表记录发送状态(如 `status: sending/success/fail`)。
2. 定时任务补偿失败消息。
3. 消费者端做幂等处理(如唯一 ID + 去重表)。
- **适用场景**:订单支付、库存扣减等关键业务。
#### **3本地消息表最终一致性**
#### 3本地消息表最终一致性
```mermaid
sequenceDiagram
participant App
@ -852,6 +869,8 @@ channel.queueDeclare("myLazyQueue", true, false, false, args);
## 优先级队列
**优先级仅在消息堆积时生效(空队列时无意义)**
**优先级范围**
- 通过 `x-max-priority` 参数定义队列支持的最大优先级默认0=无优先级)
@ -930,4 +949,900 @@ void priorityTest() {
public void processMessagePriority(String dataString, Channel channel, Message message) throws IOException, InterruptedException {
log.info("<<优先级队列>>----<priority>{}", dataString);
}
```
```
## 集群搭建
完成示例参考
![image-20250519224811858](./images/image-20250519224811858.png)
![image-20250519224836326](./images/image-20250519224836326.png)
> [!TIP]
>
> 在操作之前可以拍摄虚拟机快照方便回滚要保证这个环境没有RabbitMQ或妨碍搭建的内容。
### 修改Ubuntu网络(可选)
> [!IMPORTANT]
>
> 如果需要修改记得修改网关和IP地址。
>
> 节点间端口要求如4369/25672需互通避免防火墙问题.。
看自己是否有这个需求。
```yaml
network:
ethernets:
ens33:
dhcp4: false
addresses: [192.168.3.144/24]
optional: true
routes:
- to: default
via: 192.168.3.1
nameservers:
addresses: [8.8.8.8]
version: 2
```
应用修改
```bash
sudo netplan apply
# or
sudo reboot
```
### Docker方式
集群环境搭建版本是:
#### 1、配置docker镜像源
```bash
# 创建目录
sudo mkdir -p /etc/docker
# 写入配置文件
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": [
"https://docker-0.unsee.tech",
"https://docker-cf.registry.cyou",
"https://docker.1panel.live"
]
}
EOF
# 重启docker服务
sudo systemctl daemon-reload && sudo systemctl restart docker
```
#### 2、docker-compose.yaml
> [!IMPORTANT]
>
> 执行前确保有权限!!!
>
> ```bash
> # 1. 停止容器
> docker compose stop
>
> # 2. 清理旧目录(可选)
> sudo rm -rf ~/docker/docker_data/rabbitmq
>
> # 3. 重建目录并设置权限
> mkdir -p ~/docker/docker_data/rabbitmq/{rabbit1,rabbit2,rabbit3}/{data,conf,log}
> sudo chown -R 999:999 ~/docker/docker_data/rabbitmq
>
> # 4. 重新启动
> docker compose up -d
> ```
创建文件:`docker-compose.yaml`。
```yaml
name: 'rabbitmq-cluster'
services:
rabbit1:
image: rabbitmq:3.13.7-management
container_name: rabbit1
hostname: rabbit1
ports:
- "5672:5672"
- "15672:15672"
environment:
- RABBITMQ_ERLANG_COOKIE=SECRETCOOKIE
- RABBITMQ_NODENAME=rabbit@rabbit1
- RABBITMQ_DEFAULT_USER=admin
- RABBITMQ_DEFAULT_PASS=admin
volumes:
- ~/docker/docker_data/rabbitmq/rabbit1/data:/var/lib/rabbitmq
- ~/docker/docker_data/rabbitmq/rabbit1/conf:/etc/rabbitmq
- ~/docker/docker_data/rabbitmq/rabbit1/log:/var/log/rabbitmq
networks:
- rabbitmq-cluster
rabbit2:
image: rabbitmq:3.13.7-management
container_name: rabbit2
hostname: rabbit2
ports:
- "5673:5672"
- "15673:15672"
environment:
- RABBITMQ_ERLANG_COOKIE=SECRETCOOKIE
- RABBITMQ_NODENAME=rabbit@rabbit2
- RABBITMQ_DEFAULT_USER=admin
- RABBITMQ_DEFAULT_PASS=admin
volumes:
- ~/docker/docker_data/rabbitmq/rabbit2/data:/var/lib/rabbitmq
- ~/docker/docker_data/rabbitmq/rabbit2/conf:/etc/rabbitmq
- ~/docker/docker_data/rabbitmq/rabbit2/log:/var/log/rabbitmq
networks:
- rabbitmq-cluster
depends_on:
- rabbit1
rabbit3:
image: rabbitmq:3.13.7-management
container_name: rabbit3
hostname: rabbit3
ports:
- "5674:5672"
- "15674:15672"
environment:
- RABBITMQ_ERLANG_COOKIE=SECRETCOOKIE
- RABBITMQ_NODENAME=rabbit@rabbit3
- RABBITMQ_DEFAULT_USER=admin
- RABBITMQ_DEFAULT_PASS=admin
volumes:
- ~/docker/docker_data/rabbitmq/rabbit3/data:/var/lib/rabbitmq
- ~/docker/docker_data/rabbitmq/rabbit3/conf:/etc/rabbitmq
- ~/docker/docker_data/rabbitmq/rabbit3/log:/var/log/rabbitmq
networks:
- rabbitmq-cluster
depends_on:
- rabbit1
- rabbit2
networks:
rabbitmq-cluster:
external: true
```
#### 3、启动docker-compose
```bash
docker compose up -d
```
#### 4、加入集群
> [!IMPORTANT]
>
> 注意你的名称是否和我的一样,直接复制忽略。
```bash
# 将rabbit2加入集群
docker exec -it rabbit2 rabbitmqctl stop_app
docker exec -it rabbit2 rabbitmqctl reset
docker exec -it rabbit2 rabbitmqctl join_cluster rabbit@rabbit1
docker exec -it rabbit2 rabbitmqctl start_app
# 将rabbit3加入集群
docker exec -it rabbit3 rabbitmqctl stop_app
docker exec -it rabbit3 rabbitmqctl reset
docker exec -it rabbit3 rabbitmqctl join_cluster rabbit@rabbit1
docker exec -it rabbit3 rabbitmqctl start_app
```
> 成功的输出:
>
> ```bash
> bunny@bunny:~$ # 将rabbit2加入集群
> docker exec -it rabbit2 rabbitmqctl stop_app
> docker exec -it rabbit2 rabbitmqctl reset
> docker exec -it rabbit2 rabbitmqctl join_cluster rabbit@rabbit1
> docker exec -it rabbit2 rabbitmqctl start_app
>
> # 将rabbit3加入集群
> docker exec -it rabbit3 rabbitmqctl stop_app
> docker exec -it rabbit3 rabbitmqctl reset
> docker exec -it rabbit3 rabbitmqctl join_cluster rabbit@rabbit1
> docker exec -it rabbit3 rabbitmqctl start_app
> Stopping rabbit application on node rabbit@rabbit2 ...
> Resetting node rabbit@rabbit2 ...
> Clustering node rabbit@rabbit2 with rabbit@rabbit1
> Starting node rabbit@rabbit2 ...
> Stopping rabbit application on node rabbit@rabbit3 ...
> Resetting node rabbit@rabbit3 ...
> Clustering node rabbit@rabbit3 with rabbit@rabbit1
> Starting node rabbit@rabbit3 ...
> ```
### 普通方式
#### 先决条件
> [!TIP]
>
> 所有节点使用相同版本的Erlang可通过`erl -version`检查)
- 至少两台Ubuntu服务器建议18.04 LTS或更高版本
- 所有服务器之间网络互通
- 每台服务器上已安装相同版本的RabbitMQ
- 所有节点使用相同版本的Erlang
#### 1、安装RabbitMQ
在所有节点上执行以下步骤:
```bash
# 更新软件包列表
sudo apt update
# 安装必要的依赖
sudo apt install -y curl gnupg
# 移除之前添加的 packagecloud.io 仓库
sudo rm /etc/apt/sources.list.d/rabbitmq.list
# 添加 RabbitMQ 官方仓库密钥
sudo apt-get install curl gnupg apt-transport-https -y
## Team RabbitMQ's main signing key
curl -1sLf "https://keys.openpgp.org/vks/v1/by-fingerprint/0A9AF2115F4687BD29803A206B73A36E6026DFCA" | sudo gpg --dearmor | sudo tee /usr/share/keyrings/com.rabbitmq.team.gpg > /dev/null
## Cloudsmith: modern Erlang repository
curl -1sLf "https://dl.cloudsmith.io/public/rabbitmq/rabbitmq-erlang/gpg.E495BB49CC4BBE5B.key" | sudo gpg --dearmor | sudo tee /usr/share/keyrings/io.cloudsmith.rabbitmq.E495BB49CC4BBE5B.gpg > /dev/null
## Cloudsmith: RabbitMQ repository
curl -1sLf "https://dl.cloudsmith.io/public/rabbitmq/rabbitmq-server/gpg.9F4587F226208342.key" | sudo gpg --dearmor | sudo tee /usr/share/keyrings/io.cloudsmith.rabbitmq.9F4587F226208342.gpg > /dev/null
# 添加仓库源
sudo tee /etc/apt/sources.list.d/rabbitmq.list <<EOF
## Provides modern Erlang/OTP releases
deb [signed-by=/usr/share/keyrings/io.cloudsmith.rabbitmq.E495BB49CC4BBE5B.gpg] https://dl.cloudsmith.io/public/rabbitmq/rabbitmq-erlang/deb/ubuntu $(lsb_release -cs) main
deb-src [signed-by=/usr/share/keyrings/io.cloudsmith.rabbitmq.E495BB49CC4BBE5B.gpg] https://dl.cloudsmith.io/public/rabbitmq/rabbitmq-erlang/deb/ubuntu $(lsb_release -cs) main
## Provides RabbitMQ
deb [signed-by=/usr/share/keyrings/io.cloudsmith.rabbitmq.9F4587F226208342.gpg] https://dl.cloudsmith.io/public/rabbitmq/rabbitmq-server/deb/ubuntu $(lsb_release -cs) main
deb-src [signed-by=/usr/share/keyrings/io.cloudsmith.rabbitmq.9F4587F226208342.gpg] https://dl.cloudsmith.io/public/rabbitmq/rabbitmq-server/deb/ubuntu $(lsb_release -cs) main
EOF
# 更新软件包列表
sudo apt-get update -y
# 安装 Erlang 和 RabbitMQ
sudo apt-get install -y erlang-base \
erlang-asn1 erlang-crypto erlang-eldap erlang-ftp erlang-inets \
erlang-mnesia erlang-os-mon erlang-parsetools erlang-public-key \
erlang-runtime-tools erlang-snmp erlang-ssl erlang-syntax-tools \
erlang-tftp erlang-tools erlang-xmerl
sudo apt-get install rabbitmq-server -y --fix-missing
# 启动RabbitMQ服务
sudo systemctl start rabbitmq-server
sudo systemctl enable rabbitmq-server
# 检查状态
sudo systemctl status rabbitmq-server
```
#### 2、配置主机名和hosts文件
> [!CAUTION]
>
> 所有主机上的`hostname`都必须和配置的一样,否则无法加入集群还会报错。
>
> 查看本机的hostname如果输出的和配置的hosts的不一致需要修改hostname。
>
> ```bash
> # 查看本机的hostname
> hostname
>
> # 永久修改hostname以rabbit1为例
> sudo hostnamectl set-hostname rabbit1
> ```
>
> **例如:**
>
> - 主节点hostname为rabbit1
> - 192.168.3.145节点hostname为rabbit2
> - 192.168.3.146节点hostname为rabbit3
> - 以此类推
在所有节点上编辑`/etc/hosts`文件确保包含所有集群节点的IP和主机名
```bash
sudo vim /etc/hosts
```
添加类似以下内容根据您的实际IP和主机名修改
```bash
192.168.3.144 rabbit1
192.168.3.145 rabbit2
192.168.3.146 rabbit3
192.168.3.147 rabbit4
```
#### 3、设置Erlang Cookie
> [!IMPORTANT]
>
> 普通方式搭建集群时,`.erlang.cookie`的权限必须为`400`
>
> ```bash
> sudo chmod 400 /var/lib/rabbitmq/.erlang.cookie
> ```
RabbitMQ节点使用Erlang cookie进行认证所有节点必须使用相同的cookie。
如果是克隆的虚拟机,可以不检查(最好还是检查下),因为克隆的基本上是一样的。
选择一个节点作为主节点将其cookie复制到其他节点
```bash
# 在主节点上查看cookie
sudo cat /var/lib/rabbitmq/.erlang.cookie
# 在其他节点上停止RabbitMQ服务
sudo systemctl stop rabbitmq-server
# 编辑或创建cookie文件
sudo nano /var/lib/rabbitmq/.erlang.cookie
# 粘贴主节点的cookie内容
sudo chown rabbitmq:rabbitmq /var/lib/rabbitmq/.erlang.cookie
# 然后设置正确的权限
sudo chmod 400 /var/lib/rabbitmq/.erlang.cookie
# 重新启动RabbitMQ服务
sudo systemctl start rabbitmq-server
```
#### 4、加入集群
> [!NOTE]
>
> 加入集群时,必须要是这样的格式`sudo rabbitmqctl join_cluster rabbit@主节点名称`
在非主节点上执行以下命令加入集群:
> [!WARNING]
> `reset`命令会清除该节点的所有数据和配置,生产环境慎用
```bash
# 停止RabbitMQ应用不停止服务
sudo rabbitmqctl stop_app
# 重置节点(如果是新安装可以跳过,生产环境谨慎使用)
sudo rabbitmqctl reset
# 加入集群将rabbit1替换为您的主节点主机名
sudo rabbitmqctl join_cluster rabbit@rabbit1
# 启动RabbitMQ应用
sudo rabbitmqctl start_app
```
> [!CAUTION]
>
> 如果因为别的原因弄乱了,可以删除原来数据
>
> 仔细甄别下面是否有你需要的!!!
>
> ```bash
> # 停止 RabbitMQ
> sudo systemctl stop rabbitmq-server
>
> # 删除旧数据
> sudo rm -rf /var/lib/rabbitmq/mnesia/
>
> # 确保 cookie 与其他节点一致
> sudo cat /var/lib/rabbitmq/.erlang.cookie # 必须与其他节点相同
>
> # 启动服务
> sudo systemctl start rabbitmq-server
>
> # 加入集群(使用新主机名)
> sudo rabbitmqctl stop_app
> sudo rabbitmqctl reset
> sudo rabbitmqctl join_cluster rabbit@rabbit1 # 假设 rabbit1 是主节点
> sudo rabbitmqctl start_app
> ```
#### 5、验证集群状态
在任何节点上执行以下命令检查集群状态:
```bash
sudo rabbitmqctl cluster_status
# OR 查看集群节点列表
sudo rabbitmqctl cluster_status | grep -A10 'running_nodes'
# 检查服务状态
sudo rabbitmqctl status
```
> 为了提高可用性,可以设置镜像队列策略:
>
> ```bash
> sudo rabbitmqctl set_policy ha-all "^" '{"ha-mode":"all"}'
> ```
#### 6、启用管理插件可选
- 如果需要Web管理界面
> [!IMPORTANT]
>
> 如果使用Web界面需要在每个集群上都执行一下否则情况会出现下面情况举个例子。
>
> 如果在主节点上执行那么主节点可以访问之后输入账户密码登录之后可以看到主节点的情况但是其余节点会出现【Node statistics not available】。
>
> 为了避免这种情况,要在每个节点上都执行一下下面的命令。
```bash
sudo rabbitmq-plugins enable rabbitmq_management
```
然后可以通过`http://<node-ip>:15672`访问,默认用户名/密码为`guest/guest`(建议更改)。
如果需要开启防火墙
```bash
# 开放15672端口如果使用防火墙
sudo ufw allow 15672/tcp
```
> [!TIP]
>
> 如果到页面中查看,没有用户或者没有权限可以通过下面方式进行更改或添加。
如果需要添加用户,在你安装管理界面的机器上运行。
```bash
# 如果存在可以先删除
sudo rabbitmqctl delete_user 用户名
# 添加用户
sudo rabbitmqctl add_user 用户名 密码
#设置用户操作权限
sudo rabbitmqctl set_user_tags 用户名 administrator
```
**示例**
```bash
# 如果存在可以先删除
sudo rabbitmqctl delete_user admin
# 添加用户
sudo rabbitmqctl add_user admin admin
#设置用户操作权限
sudo rabbitmqctl set_user_tags admin administrator
```
- **修改配置文件**(如 `/etc/hosts`、防火墙规则等)需在所有节点操作。
- **重启 RabbitMQ 服务**(如果修改了核心配置):
```bash
sudo systemctl restart rabbitmq-server
```
### 常见问题
Q打开主界面没有用户可以登录。
A添加或者创建新用户参考【普通方式->第六点】
Q打开之后显示【⚠ All stable feature flags must be enabled after completing an upgrade. 】
```bash
# 查看当前 Feature Flags 状态
sudo rabbitmqctl list_feature_flags
# 启用所有稳定的 Feature Flags这会启用所有 稳定stable 的功能标志(不包括实验性功能)。
sudo rabbitmqctl enable_feature_flag all
# 检查是否成功启用
sudo rabbitmqctl list_feature_flags
# 重启 RabbitMQ可选
sudo systemctl restart rabbitmq-server
```
Q出现下面情况【Node statistics not available】
![image-20250519224201895](./images/image-20250519224201895.png)
A解决方法
**情况一 管理插件未在所有节点启用**
```bash
sudo rabbitmq-plugins enable rabbitmq_management
sudo systemctl restart rabbitmq-server
```
**情况二 **
如果节点之间无法正常通信(如防火墙、网络策略阻止),管理界面无法获取其他节点的统计信息。
**检查方法**
在任意节点执行:
```bash
sudo rabbitmqctl cluster_status
```
**检查防火墙**(确保 `4369`、`25672`、`5672`、`15672` 端口开放):
```bash
sudo ufw allow 4369/tcp # Erlang 分布式通信端口
sudo ufw allow 25672/tcp # RabbitMQ 集群通信端口
sudo ufw allow 5672/tcp # AMQP 协议端口
sudo ufw allow 15672/tcp # 管理界面端口
sudo ufw reload
```
**情况三 Cookie 不一致**
在所有节点执行:
```bash
sudo cat /var/lib/rabbitmq/.erlang.cookie
```
1. 停止 RabbitMQ
```bash
sudo systemctl stop rabbitmq-server
```
2. 修改 Cookie复制主节点的 Cookie 到其他节点):
```bash
echo "YOUR_MASTER_NODE_COOKIE" | sudo tee /var/lib/rabbitmq/.erlang.cookie
```
3. 设置权限:
```bash
sudo chown rabbitmq:rabbitmq /var/lib/rabbitmq/.erlang.cookie
sudo chmod 400 /var/lib/rabbitmq/.erlang.cookie
```
4. 重启 RabbitMQ
```bash
sudo systemctl start rabbitmq-server
```
**情况四 管理界面用户权限问题**
如果用户没有 **跨节点访问权限**,管理界面可能无法获取其他节点的数据。
**解决方案**
确保用户在所有节点都有 **管理员权限**
```bash
sudo rabbitmqctl set_user_tags <username> administrator
sudo rabbitmqctl set_permissions -p / <username> ".*" ".*" ".*"
```
例如:
```bash
sudo rabbitmqctl set_user_tags admin administrator
sudo rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"
```
**情况五 管理界面缓存问题**
浏览器可能缓存了旧数据,导致显示异常。
### 集群环境-负载均衡
#### 1. 安装 HAProxy
```bash
sudo apt update
sudo apt install haproxy -y
```
#### 2. 配置 HAProxy
编辑 HAProxy 的配置文件 `/etc/haproxy/haproxy.cfg`
```bash
sudo vim /etc/haproxy/haproxy.cfg
```
添加以下内容(假设 RabbitMQ 节点为 `rabbit1:5672``rabbit2:5672`
> [!IMPORTANT]
>
> 绑定的端口号(`bind *:`)不能重复!!!
```ini
frontend rabbitmq_front
bind *:22222
mode tcp
default_backend rabbitmq_back
backend rabbitmq_back
mode tcp
balance roundrobin
# 在之前设置了hosts文件所以这里没有写端口号 == server rabbit1 192.168.3.144:5672 check
server rabbit1 rabbit1:5672 check
server rabbit2 rabbit2:5672 check
server rabbit3 rabbit3:5672 check
server rabbit4 rabbit4:5672 check
# 可选:启用 RabbitMQ 管理界面的负载均衡(默认端口 15672
frontend rabbitmq_admin
bind *:12222
mode http
default_backend rabbitmq_admin_back
backend rabbitmq_admin_back
mode http
balance roundrobin
# 在之前设置了hosts文件所以这里没有写端口号 == server rabbit1 192.168.3.144:15672 check
server rabbit1 rabbit1:15672 check
server rabbit2 rabbit2:15672 check
server rabbit3 rabbit3:15672 check
server rabbit4 rabbit4:15672 check
```
#### 3. 重启 HAProxy
> [!TIP]
>
> 重启报错可以看下报错内容: journalctl -xeu haproxy.service
>
> 如果使用一些远程软件修改时可能会出现多行配置变一行问题需要手动通过vim查看。
```bash
sudo systemctl restart haproxy
```
### 集群环境的连接
> [!NOTE]
>
> 如果是新建环境,需要将之前写的监听内容注释,否则启动容易报错。
使用Java程序进行连接。如果搭建了集群环境还配置了负载均衡就是用负载均衡的方式进行连接。
负载均衡配置的是端口是:`5672`对应上面的文件是`22222`。所以在连接时候是`22222`这个 端口参考【2. 配置 HAProxy】
#### Java配置
```yaml
# application.yaml
rabbitmq:
host: ${bunny.rabbitmq.host}
port: ${bunny.rabbitmq.port}
username: ${bunny.rabbitmq.username}
password: ${bunny.rabbitmq.password}
virtual-host: ${bunny.rabbitmq.virtual-host}
# application-dev.yaml
bunny:
rabbitmq:
host: 192.168.3.144
# port: 5672
# 集群环境的端口号
port: 22222
virtual-host: /
username: admin
password: admin
```
#### 创建环境
**交换机:**exchange.cluster.test
![image-20250520105125396](./images/image-20250520105125396.png)
**队列:**queue.cluster.test
![image-20250520105109623](./images/image-20250520105109623.png)
**路由键:**routing.key.cluster.test
![image-20250520105149567](./images/image-20250520105149567.png)
## 集群中的队列
### 镜像队列
最新版本中已经退出历史舞台了,就不说了。
### 仲裁队列
当我们在集群中创建队列时如【集群环境的连接】那一节可以看到当前的队列是位于第一个节点上的。如果这时候服务器宕机或者其他情况这时这个节点上数据没了其它j节点时访问不到的我们理想的情况是如果一台服务器宕机这台服务器上的所有内容在别的节点上都是可以看到的。
![image-20250520141530322](./images/image-20250520141530322.png)
#### 创建仲裁队列
**交换机:**exchange.quorum.test
**队列:**queue.quorum.test
**l路由键**routing.key.quorum.test
![image-20250520141821885](./images/image-20250520141821885.png)
#### 测试Code
**生产者**
```java
/* 仲裁队列发送消息 */
@Test
void clusterQuorumTest2() {
final String EXCHANGE = "exchange.quorum.test";
final String ROUTING_KEY = "routing.key.quorum.test";
for (int i = 0; i <= 10; i++) {
rabbitTemplate.convertAndSend(EXCHANGE, ROUTING_KEY, "仲裁队列发送消息-" + i);
}
}
```
**消费者**
```java
/* 仲裁队列监听 */
@RabbitListener(queues = "queue.quorum.test")
public void processMessage(String dataString, Channel channel, Message message) throws IOException, InterruptedException {
log.info("<<仲裁队列监听下消息>>----{}", dataString);
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
```
### 联邦队列
Federation插件的设计目标是使RabbitMQ在不同的Broker节点之间进行消息传递而无须建立集群。
它可以在不同的管理域中的Broker或集群间传递消息这些管理域可能设置了不同的用户和vhost也可能运行在不同版本的RabbitMQ和Erlang上。Federation基于AMQP0-9-1协议在不同的Broker之间进行通信并且设计成能够容忍不稳定的网络连接情况。
- 各节点操作:启用联邦插件
- 下游操作:
- 添加上游连接端点
- 创建控制策略
> [!NOTE]
>
> 因为联邦队列不是集群需要两个不同rabbit比如说一个服务器在上海一个在杭州两个不同的服务器且不是集群环境。
>
> 所以在创建时候不要加入集群。
#### 创建docker容器
**设置权限**
```bash
sudo chown -R 999:999 ~/docker/docker_data/rabbitmq/rabbitmq_master
sudo chown -R 999:999 ~/docker/docker_data/rabbitmq/rabbitmq_2
```
**创建容器**
```bash
# 第一个
docker run -d --name rabbitmq_master --restart=always \
-p 5672:5672 -p 15672:15672 \
-v ~/docker/docker_data/rabbitmq/rabbitmq_master/data:/var/lib/rabbitmq \
-v ~/docker/docker_data/rabbitmq/rabbitmq_master/conf:/etc/rabbitmq \
-v ~/docker/docker_data/rabbitmq/rabbitmq_master/log:/var/log/rabbitmq \
-e RABBITMQ_DEFAULT_VHOST=rabbitmq_master \
-e RABBITMQ_DEFAULT_USER=admin \
-e RABBITMQ_DEFAULT_PASS=admin rabbitmq:3.13.7-management
# 第二个
docker run -d --name rabbitmq_2 --restart=always \
-p 5673:5672 -p 15673:15672 \
-v ~/docker/docker_data/rabbitmq/rabbitmq_2/data:/var/lib/rabbitmq \
-v ~/docker/docker_data/rabbitmq/rabbitmq_2/conf:/etc/rabbitmq \
-v ~/docker/docker_data/rabbitmq/rabbitmq_2/log:/var/log/rabbitmq \
-e RABBITMQ_DEFAULT_VHOST=rabbitmq_2 \
-e RABBITMQ_DEFAULT_USER=admin \
-e RABBITMQ_DEFAULT_PASS=admin rabbitmq:3.13.7-management
```
#### 启动联邦插件
> [!NOTE]
>
> 联邦插件并不需要下载启动即可。
> [!IMPORTANT]
>
> 创建时候需要重新设置Virtual Hosts否则设置了内容会显示`not_allow`不能使用默认的Virtual Hosts。
```bash
# 执行下面命令进行开启
rabbitmq-plugins enable rabbitmq_federation
rabbitmq-plugins enable rabbitmq_federation_mangement
```
不进入docker容器直接运行方式
```bash
# 开启全部插件防止警告
docker exec -it rabbitmq_master rabbitmqctl enable_feature_flag all
docker exec -it rabbitmq_2 rabbitmqctl enable_feature_flag all
docker exec -it rabbitmq_master rabbitmq-plugins enable rabbitmq_federation
docker exec -it rabbitmq_master rabbitmq-plugins enable rabbitmq_federation_management
docker exec -it rabbitmq_2 rabbitmq-plugins enable rabbitmq_federation
docker exec -it rabbitmq_2 rabbitmq-plugins enable rabbitmq_federation_management
```
![image-20250520145425061](./images/image-20250520145425061.png)
启用成功后会有下面两个选项
![image-20250520145937321](./images/image-20250520145937321.png)
在此页面中添加
![image-20250520150530165](./images/image-20250520150530165.png)
上图输入框中的内容
```bash
# 连接名称
bunny.upstream
# 连接地址
amqp://admin:admin@172.17.0.1:5672
```
创建Policies
![image-20250520150907385](./images/image-20250520150907385.png)
上图输入框内容
```bash
name: policy.federation.exchange
Pattern: ^federation\.
Apply to: Exchanges
Priority: 10
# 这里的federation-upstream 是我们之前创建的联邦名称,详细看上面【上图的,输入框中的内容】
federation-upstream bunny.upstream
```
#### 创建交换机
> [!IMPORTANT]
>
> 创建联邦交换机时,需要匹配上面写的`Pattern`,如果按照文档创建,那么名称需要是:`federation.xxx.xxxxx`这种格式,反正肯定是以`federation`开头的。
>
> 其次路由键要一致,队列名称可以不一样。
| 所在机房 | 交换机名称 | 路由键 | 队列名称 |
| ----------------------------- | ------------------------ | --------------------- | ---------------------- |
| rabbitmq_master你的上游MQ | federation.exchange.demo | routing.key.demo.test | queue.normal.master |
| rabbitmq2你的下游MQ | federation.exchange.demo | routing.key.demo.test | queue.normal.rabbitmq2 |
> [!NOTE]
>
> 但此时发现下游节点中联邦队列并没有接收到消息,这是为什么呢?这里就体现出了联邦队列和联邦交换机工作逻辑的区别。
> 对联邦队列来说,如果没有监听联邦队列的消费端程序,它是不会到上游去拉取消窶的!
> 如果有消费端监听联邦队列,那么首先消费联邦队列自身的消息;如果联邦队列为空,这时候才会到上游队列节点中拉取消息。
> 所以现在的测试效果需要消费端程序配合才能看到:

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -0,0 +1,20 @@
package cn.bunny.mq.mqdemo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping
public class IndexController {
@GetMapping("")
public String index() {
return "index";
}
@GetMapping("dialog")
public String dialog() {
return "dialog";
}
}

View File

@ -0,0 +1,23 @@
package cn.bunny.mq.mqdemo.mq.listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class ClusterEnvironmentListener {
// /* 测试优先级队列 */
// @RabbitListener(queues = "queue.cluster.test")
// public void processMessagePriority(String dataString, Channel channel, Message message) throws IOException, InterruptedException {
// log.info("<<集群环境下消息>>----<cluster>{}", dataString);
// channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
// }
//
// /* 仲裁队列监听 */
// @RabbitListener(queues = "queue.quorum.test")
// public void processMessage(String dataString, Channel channel, Message message) throws IOException, InterruptedException {
// log.info("<<仲裁队列监听下消息>>----{}", dataString);
// channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
// }
}

View File

@ -7,8 +7,6 @@ import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
@Component
@Slf4j
@ -81,17 +79,24 @@ public class MessageListenerOrder {
// channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
// }
/* 测试延迟消息 */
@RabbitListener(queues = "queue.test.delay")
public void processMessageDelay(String dataString, Channel channel, Message message) throws IOException, InterruptedException {
log.info("<延迟消息>----消息本身{}", dataString);
log.info("<延迟消息>----当前时间{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
// /* 测试延迟消息 */
// @RabbitListener(queues = "queue.test.delay")
// public void processMessageDelay(String dataString, Channel channel, Message message) throws IOException, InterruptedException {
// log.info("<延迟消息>----消息本身{}", dataString);
// log.info("<延迟消息>----当前时间{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
// }
//
// /* 测试优先级队列 */
// @RabbitListener(queues = "queue.test.priority")
// public void processMessagePriority(String dataString, Channel channel, Message message) throws IOException, InterruptedException {
// log.info("<<优先级队列>>----<priority>{}", dataString);
// }
/* 测试优先级队列 */
@RabbitListener(queues = "queue.test.priority")
/* 测试联邦队列消息 */
@RabbitListener(queues = "queue.normal.rabbitmq2")
public void processMessagePriority(String dataString, Channel channel, Message message) throws IOException, InterruptedException {
log.info("<<优先级队列>>----<priority>{}", dataString);
log.info("<<测试联邦队列消息>>----<priority>{}", dataString);
}
}

View File

@ -3,8 +3,14 @@ server:
bunny:
rabbitmq:
host: 192.168.3.144
port: 5672
# # 集群环境
# host: 192.168.3.144
# port: 22222 # 集群环境的端口号
# 联邦队列---需要下游的
host: 192.168.3.148
port: 5673
virtual-host: /
username: admin
password: admin

View File

@ -0,0 +1,480 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title>优雅的Bootstrap弹窗表单</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
<style>
:root {
--primary-color: #4e73df;
--secondary-color: #2e59d9;
--success-color: #1cc88a;
--danger-color: #e74a3b;
--light-color: #f8f9fc;
--dark-color: #5a5c69;
}
body {
font-family: 'Nunito', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background-color: #f8f9fc;
}
/* 自定义弹窗样式 */
.custom-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1050;
overflow-y: auto;
}
.custom-modal-dialog {
max-width: 500px;
margin: 1.75rem auto;
transition: transform 0.3s ease-out;
}
.custom-modal-content {
border: none;
border-radius: 0.5rem;
box-shadow: 0 0.15rem 1.75rem 0 rgba(58, 59, 69, 0.15);
overflow: hidden;
}
.custom-modal-header {
padding: 1.5rem;
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
color: white;
position: relative;
}
.custom-modal-title {
font-weight: 700;
font-size: 1.25rem;
}
.btn-close-custom {
position: absolute;
top: 1rem;
right: 1rem;
font-size: 1.5rem;
color: rgba(255, 255, 255, 0.8);
background: none;
border: none;
cursor: pointer;
transition: color 0.2s;
}
.btn-close-custom:hover {
color: white;
}
.custom-modal-body {
padding: 1.5rem;
background-color: white;
}
.custom-modal-footer {
padding: 1rem 1.5rem;
background-color: var(--light-color);
border-top: 1px solid #e3e6f0;
display: flex;
justify-content: flex-end;
}
/* 表单样式 */
.form-label {
font-weight: 600;
color: var(--dark-color);
margin-bottom: 0.5rem;
}
.form-control-custom {
border: 1px solid #d1d3e2;
border-radius: 0.35rem;
padding: 0.75rem 1rem;
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
.form-control-custom:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 0.2rem rgba(78, 115, 223, 0.25);
}
/* 验证样式 */
.is-invalid {
border-color: var(--danger-color);
}
.is-valid {
border-color: var(--success-color);
}
.invalid-feedback {
color: var(--danger-color);
font-size: 0.875rem;
margin-top: 0.25rem;
}
.valid-feedback {
color: var(--success-color);
font-size: 0.875rem;
margin-top: 0.25rem;
}
/* 按钮样式 */
.btn-primary-custom {
background-color: var(--primary-color);
border-color: var(--primary-color);
padding: 0.5rem 1.25rem;
font-weight: 600;
transition: all 0.2s;
}
.btn-primary-custom:hover {
background-color: var(--secondary-color);
border-color: var(--secondary-color);
transform: translateY(-1px);
}
.btn-outline-secondary-custom {
border-color: #d1d3e2;
color: var(--dark-color);
padding: 0.5rem 1.25rem;
font-weight: 600;
transition: all 0.2s;
}
.btn-outline-secondary-custom:hover {
background-color: #f8f9fc;
border-color: #d1d3e2;
}
/* 动画效果 */
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideInDown {
from {
transform: translateY(-50px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.modal-show {
display: block;
animation: fadeIn 0.3s;
}
.modal-show .custom-modal-dialog {
animation: slideInDown 0.3s;
}
/* 浮动标签效果 */
.form-floating-custom {
position: relative;
margin-bottom: 1.5rem;
}
.form-floating-custom label {
position: absolute;
top: 0.75rem;
left: 1rem;
color: #6e707e;
transition: all 0.2s;
pointer-events: none;
}
.form-floating-custom input:focus ~ label,
.form-floating-custom input:not(:placeholder-shown) ~ label {
top: -0.75rem;
left: 0.75rem;
font-size: 0.75rem;
background-color: white;
padding: 0 0.25rem;
color: var(--primary-color);
}
/* 密码强度指示器 */
.password-strength {
height: 4px;
background-color: #e3e6f0;
margin-top: 0.5rem;
border-radius: 2px;
overflow: hidden;
}
.password-strength-bar {
height: 100%;
width: 0;
transition: width 0.3s ease, background-color 0.3s ease;
}
</style>
</head>
<body>
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-lg-8 text-center">
<h1 class="display-4 mb-4">优雅的Bootstrap弹窗示例</h1>
<p class="lead mb-5">点击下方按钮查看带有表单验证的现代化弹窗</p>
<button class="btn btn-primary btn-lg px-4 py-2" id="openModalBtn">
<i class="bi bi-box-arrow-in-down-right me-2"></i>打开注册弹窗
</button>
</div>
</div>
</div>
<!-- 自定义弹窗 -->
<div class="custom-modal" id="customModal">
<div class="custom-modal-dialog">
<div class="custom-modal-content">
<div class="custom-modal-header">
<h5 class="custom-modal-title">
<i class="bi bi-person-plus-fill me-2"></i>用户注册
</h5>
<button class="btn-close-custom" id="closeModalBtn" type="button">
<i class="bi bi-x-lg"></i>
</button>
</div>
<div class="custom-modal-body">
<form id="registrationForm" novalidate>
<div class="form-floating-custom mb-4">
<input class="form-control form-control-custom" id="username" placeholder="用户名" required
type="text">
<label for="username">用户名</label>
<div class="invalid-feedback">请输入有效的用户名4-20个字符</div>
<div class="valid-feedback">用户名可用</div>
</div>
<div class="form-floating-custom mb-4">
<input class="form-control form-control-custom" id="email" placeholder="电子邮箱" required
type="email">
<label for="email">电子邮箱</label>
<div class="invalid-feedback">请输入有效的电子邮箱地址</div>
<div class="valid-feedback">邮箱格式正确</div>
</div>
<div class="form-floating-custom mb-4">
<input class="form-control form-control-custom" id="password" minlength="8" placeholder="密码"
required type="password">
<label for="password">密码</label>
<div class="invalid-feedback">密码至少需要8个字符</div>
<div class="password-strength">
<div class="password-strength-bar"></div>
</div>
</div>
<div class="form-floating-custom mb-4">
<input class="form-control form-control-custom" id="confirmPassword" placeholder="确认密码"
required type="password">
<label for="confirmPassword">确认密码</label>
<div class="invalid-feedback">密码不匹配</div>
</div>
<div class="mb-4">
<div class="form-check">
<input class="form-check-input" id="terms" required type="checkbox">
<label class="form-check-label" for="terms">
我已阅读并同意 <a class="text-primary" href="#">服务条款</a><a class="text-primary"
href="#">隐私政策</a>
</label>
<div class="invalid-feedback">必须同意条款才能继续</div>
</div>
</div>
</form>
</div>
<div class="custom-modal-footer">
<button class="btn btn-outline-secondary-custom me-2" id="cancelBtn" type="button">
取消
</button>
<button class="btn btn-primary-custom" id="submitBtn" type="button">
<i class="bi bi-check-lg me-1"></i> 提交注册
</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// 弹窗控制
const modal = document.getElementById('customModal');
const openModalBtn = document.getElementById('openModalBtn');
const closeModalBtn = document.getElementById('closeModalBtn');
const cancelBtn = document.getElementById('cancelBtn');
const submitBtn = document.getElementById('submitBtn');
const form = document.getElementById('registrationForm');
// 打开弹窗
openModalBtn.addEventListener('click', () => {
modal.classList.add('modal-show');
document.body.style.overflow = 'hidden';
});
// 关闭弹窗
function closeModal() {
modal.classList.remove('modal-show');
document.body.style.overflow = 'auto';
form.reset();
resetValidation();
}
closeModalBtn.addEventListener('click', closeModal);
cancelBtn.addEventListener('click', closeModal);
// 点击弹窗外部关闭
modal.addEventListener('click', (e) => {
if (e.target === modal) {
closeModal();
}
});
// 表单验证
function resetValidation() {
const inputs = form.querySelectorAll('input');
inputs.forEach(input => {
input.classList.remove('is-invalid', 'is-valid');
});
}
// 实时验证
form.addEventListener('input', (e) => {
const input = e.target;
validateInput(input);
// 密码强度检测
if (input.id === 'password') {
updatePasswordStrength(input.value);
}
// 确认密码验证
if (input.id === 'confirmPassword' || input.id === 'password') {
const password = document.getElementById('password').value;
const confirmPassword = document.getElementById('confirmPassword').value;
if (confirmPassword && password !== confirmPassword) {
document.getElementById('confirmPassword').classList.add('is-invalid');
document.getElementById('confirmPassword').classList.remove('is-valid');
} else if (confirmPassword) {
document.getElementById('confirmPassword').classList.add('is-valid');
document.getElementById('confirmPassword').classList.remove('is-invalid');
}
}
});
function validateInput(input) {
if (input.id === 'username') {
const isValid = input.value.length >= 4 && input.value.length <= 20;
updateValidationState(input, isValid);
} else if (input.id === 'email') {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const isValid = emailRegex.test(input.value);
updateValidationState(input, isValid);
} else if (input.id === 'password') {
const isValid = input.value.length >= 8;
updateValidationState(input, isValid);
} else if (input.id === 'terms') {
updateValidationState(input, input.checked);
}
}
function updateValidationState(input, isValid) {
if (input.value === '' && input.required) {
input.classList.remove('is-invalid', 'is-valid');
return;
}
if (isValid) {
input.classList.add('is-valid');
input.classList.remove('is-invalid');
} else {
input.classList.add('is-invalid');
input.classList.remove('is-valid');
}
}
// 密码强度检测
function updatePasswordStrength(password) {
const strengthBar = document.querySelector('.password-strength-bar');
let strength = 0;
// 长度检测
if (password.length >= 8) strength += 20;
if (password.length >= 12) strength += 20;
// 复杂度检测
if (/[A-Z]/.test(password)) strength += 20;
if (/[0-9]/.test(password)) strength += 20;
if (/[^A-Za-z0-9]/.test(password)) strength += 20;
// 更新UI
strengthBar.style.width = `${strength}%`;
if (strength < 40) {
strengthBar.style.backgroundColor = '#e74a3b'; // 红色
} else if (strength < 80) {
strengthBar.style.backgroundColor = '#f6c23e'; // 黄色
} else {
strengthBar.style.backgroundColor = '#1cc88a'; // 绿色
}
}
// 提交表单
submitBtn.addEventListener('click', () => {
let isValid = true;
const inputs = form.querySelectorAll('input[required]');
inputs.forEach(input => {
validateInput(input);
if (input.classList.contains('is-invalid')) {
isValid = false;
}
});
// 检查密码匹配
const password = document.getElementById('password').value;
const confirmPassword = document.getElementById('confirmPassword').value;
if (password !== confirmPassword) {
document.getElementById('confirmPassword').classList.add('is-invalid');
isValid = false;
}
if (isValid) {
// 模拟提交
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span> 提交中...';
submitBtn.disabled = true;
setTimeout(() => {
alert('注册成功!');
closeModal();
submitBtn.innerHTML = '<i class="bi bi-check-lg me-1"></i> 提交注册';
submitBtn.disabled = false;
}, 1500);
} else {
// 滚动到第一个错误
const firstInvalid = form.querySelector('.is-invalid');
if (firstInvalid) {
firstInvalid.scrollIntoView({behavior: 'smooth', block: 'center'});
}
}
});
</script>
</body>
</html>

View File

@ -0,0 +1,34 @@
package cn.bunny.mq.mqdemo;
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;
@SpringBootTest
public class ClusterEnvironmentTest {
@Autowired
private RabbitTemplate rabbitTemplate;
/* 集群环境下消息 */
@Test
void clusterTest() {
for (int i = 0; i <= 10; i++) {
rabbitTemplate.convertAndSend("exchange.cluster.test",
"routing.key.cluster.test",
"集群环境下消息-" + i);
}
}
/* 仲裁队列发送消息 */
@Test
void clusterQuorumTest2() {
final String EXCHANGE = "exchange.quorum.test";
final String ROUTING_KEY = "routing.key.quorum.test";
for (int i = 0; i <= 10; i++) {
rabbitTemplate.convertAndSend(EXCHANGE, ROUTING_KEY, "仲裁队列发送消息-" + i);
}
}
}