一、RabbitMQ 简介

以熟悉的电商场景为例,如果商品服务和订单服务是两个不同的微服务,在下单的过程中订单服务需要调用商品服务进行扣库存操作。按照传统的方式,下单过程要等到调用完毕之后才能返回下单成功,如果网络产生波动等原因使得商品服务扣库存延迟或者失败,会带来较差的用户体验,如果在高并发的场景下,这样的处理显然是不合适的,那怎么进行优化呢?这就需要消息队列登场了。

消息队列提供一个异步通信机制,消息的发送者不必一直等待到消息被成功处理才返回,而是立即返回。消息中间件负责处理网络通信,如果网络连接不可用,消息被暂存于队列当中,当网络畅通的时候在将消息转发给相应的应用程序或者服务,当然前提是这些服务订阅了该队列。如果在商品服务和订单服务之间使用消息中间件,既可以提高并发量,又降低服务之间的耦合度。

RabbitMQ是一个开源的消息代理的队列服务器,用来通过普通协议在完全不同的应用之间共享数据。

1.1 RabbitMQ 的特点

开源、性能优秀,速度快,稳定性保障提供可靠性消息投递模式、返回模式与Spring AMQP完美整合,API丰富集群模式丰富,表达式配置,HA模式,镜像队列模型保证数据不丢失的前提做到高可靠性、可用性

1.2 典型应用场景

  • 异步处理:把消息放入消息中间件中,等到需要的时候再去处理。
  • 流量削峰:例如秒杀活动,在短时间内访问量急剧增加,使用消息队列,当消息队列满了就拒绝响应,跳转到错误页面,这样就可以使得系统不会因为超负载而崩溃。
  • 日志处理;(不过一般日志处理都使用Kafka这种消息队列)
  • 应用解耦:假设某个服务A需要给许多个服务(B、C、D)发送消息,当某个服务(例如B)不需要发送消息了,服务A需要改代码再次部署;当新加入一个服务(服务E)需要服务A的消息的时候,也需要改代码重新部署;另外服务A也要考虑其他服务挂掉,没有收到消息怎么办?要不要重新发送呢?是不是很麻烦,使用MQ发布订阅模式,服务A只生产消息发送到MQ,B、C、D从MQ中读取消息,需要A的消息就订阅,不需要了就取消订阅,服务A不再操心其他的事情,使用这种方式可以降低服务或者系统之间的耦合。

1.3 RabbitMQ集群节点之间是如何相互认证的:

  • 通过Erlang Cookie,相当于共享秘钥的概念,长度任意,只要所有节点都一致即可。
  • rabbitmq server在启动的时候,erlang VM会自动创建一个随机的cookie文件。cookie文件的位置是/var/lib/rabbitmq/.erlang.cookie 或者 /root/.erlang.cookie,为保证cookie的完全一致,采用从一个节点copy的方式。

Erlang Cookie是保证不同节点可以相互通信的密钥,要保证集群中的不同节点相互通信必须共享相同的Erlang Cookie。具体的目录存放在/var/lib/rabbitmq/.erlang.cookie。

说明:这就要从rabbitmqctl命令的工作原理说起,RabbitMQ底层是通过Erlang架构来实现的,所以rabbitmqctl会启动Erlang节点,并基于Erlang节点来使用Erlang系统连接RabbitMQ节点,在连接过程中需要正确的Erlang Cookie和节点名称,Erlang节点通过交换Erlang Cookie以获得认证。

1.4 RabbitMQ集群模式

  • 单机模式
  • 普通集群模式(无高可用性)
  • 镜像集群模式(高可用性),最常用的集群模式。

1.5 RabbitMQ集群故障处理机制:

  • rabbitmq broker集群允许个体节点down机,
  • 对应集群的的网络分区问题( network partitions)
    RabbitMQ集群推荐用于LAN环境,不适用WAN环境;

要通过WAN连接broker,Shovel or Federation插件是最佳的解决方案;Shovel or Federation不同于集群。

1.6 RabbitMQ节点类型

  • RAM node:只保存状态到内存。内存节点将所有的队列、交换机、绑定、用户、权限和vhost的元数据定义存储在内存中,好处是可以使得像交换机和队列声明等操作更加的快速。
  • Disk node:将元数据存储在磁盘中。单节点系统只允许磁盘类型的节点,防止重启RabbitMQ的时候,丢失系统的配置信息。

内存节点虽然不写入磁盘,但是它执行比磁盘节点要好。RabbitMQ集群中,只需要一个磁盘节点来保存状态就足够了;如果集群中只有内存节点,那么不能停止它们,否则所有的状态,消息等都会丢失。

问题说明:

  • RabbitMQ要求在集群中至少有一个磁盘节点,所有其他节点可以是内存节点,当节点加入或者离开集群时,必须要将该变更通知到至少一个磁盘节点。
  • 如果集群中唯一的一个磁盘节点崩溃的话,集群仍然可以保持运行,但是无法进行其他操作(增删改查),直到节点恢复。

解决方案:设置两个磁盘节点,至少有一个是可用的,可以保存元数据的更改。

1.7 RabbitMQ集群的节点运行模式:

  • 为保证数据持久性,当前所有node节点跑在disk模式。
  • 如果今后压力大,需要提高性能,考虑采用ram模式。
    R

二、RabbitMQ 集群说明

本案例采用 “镜像模式”,即队列为镜像队列,队列消息存在集群的每个节点上。

2.1、版本说明

因为考虑到较早版本rabbitmq在k8s上的集群部署是使用autocluster插件去调用kubernetes apiserver来获取rabbitmq服务的endpoints,进而获取node节点信息,并自动加入集群,但是现在autocluster已不再更新了,并且只支持3.6.x版本,故而放弃这种方式。

对于3.7.x或更新的版本,现在市场主流是使用 peer discovery subsystem来构建rabbitmq-cluster,参考这里

这边的版本要求: RabbitMQ 3.9.15Erlang 24.3.3

2.2、部署方式

在Kubernetes上搭建RabbitMQ有4种部署方法:

  • IP模式
  • Pod与Server的DNS模式
  • Statefulset 与Headless Service模式
  • hostname模式

这里选择StatefulSet与Headless Service模式部署有状态的RabbitMQ集群。

2.3、使用NFS配置StatefulSet的动态持久化存储

rabbitmq属于有状态的服务,即每个服务上存储的内容都不一样,对于有状态的服务,k8s推荐我们使用StatefulSet控制器。
rabbitmq中的部分信息需要持久化,持久化内容使用nfs进行存储,并使用storageclass动态分配pv。

三、RabbitMQ 集群部署

3.1、思路梳理

使用官方提供的 RabbitMQ Cluster Operator for Kubernetes。

Open source RabbitMQ Cluster Kubernetes Operator by VMware。

3.2、官方文档快速部署

官方提供了快速部署的例子,只需要两步,这里我们仅作参考,后面我们参考内网离线镜像部署的方案。

部署 RabbitMQ Cluster Operator

1
kubectl apply -f https://github.com/rabbitmq/cluster-operator/releases/latest/download/cluster-operator.yml

部署 RabbitMQ Cluster

1
kubectl apply -f https://raw.githubusercontent.com/rabbitmq/cluster-operator/main/docs/examples/hell

3.3、准备离线镜像

此过程为可选项,离线内网环境可用,如果不配置内网镜像,后续的资源配置清单中注意更改容器的 image 为默认值。

在一台能同时访问互联网和内网 镜像仓库的服务器上进行下面的操作。

1
2
3
4
5
6
7
8
9
10
11
# 1、登入远程仓库
docker login --username=xxx 远程仓库地址 回车输入密码
# 2、下载镜像
docker pull rabbitmqoperator/cluster-operator:1.14.0
# 3、重新打 tag
docker tag rabbitmqoperator/cluster-operator:1.14.0 registry.devops.com.cn/rabbitmqoperator/cluster-operator:1.14.0
# 4、推送到私有镜像仓库
docker push registry.devops.com.cn/rabbitmqoperator/cluster-operator:1.14.0
# 4、清理临时镜像
docker rmi rabbitmqoperator/cluster-operator:1.14.0
docker rmi registry.devops.com.cn/rabbitmqoperator/cluster-operator:1.14.0

3.4、资源配置清单

3.4.1、cluster-operator.yml

通过官网获取 RabbitMQ Cluster Operator 部署资源配置清单「cluster-operator.yml」

1
2
3
# 创建项目目录
mkdir -p /data/k8s/rabbitmq/cluster
wget https://github.com/rabbitmq/cluster-operator/releases/latest/download/cluster-operator.yml

修改 RabbitMQ Cluster Operator image 为内网镜像

1
sed -i 's#rabbitmqoperator#远程仓库地址/rabbitmqoperator#g' cluster-operator.yml

如果仓库有密码记得添加key

1
2
imagePullSecrets:
- name: testregistrysecret

3.4.2、rabbitmq-cluster.yaml

RabbitMQ Cluster 部署资源清单「rabbitmq-cluster.yaml」

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
---
apiVersion: rabbitmq.com/v1beta1
kind: RabbitmqCluster
metadata:
namespace: devops # 空间名称自己修改
name: rabbitmq-cluster
labels:
app: rabbitmq-cluster
spec:
replicas: 3
image: 远程仓库地址/rabbitmq:3.9.22-management
resources:
limits:
cpu: 2
memory: 4Gi
requests:
cpu: 100m
memory: 500Mi
rabbitmq:
additionalConfig: |
default_user=admin
default_pass=P@88w0rd
persistence: # 特意把这个参考写出来,需要根据实际情况去修改,更多参数修改,请参照官方文档。
storageClassName: managed-nfs-storage
storage: 10Gi
imagePullSecrets:
- name: testregistrysecret

更多配置参数和配置示例,请参考 官方文档

3.4.3、管理页面的外部访问服务 rabbitmq-cluster-external.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
---
apiVersion: v1
kind: Service
metadata:
name: rabbitmq-cluster-external
namespace: devops
labels:
app: rabbitmq-cluster-external
spec:
ports:
- name: management
protocol: TCP
port: 15672
targetPort: 15672
nodePort: 31672
selector:
app.kubernetes.io/name: rabbitmq-cluster
type: NodePort

3.5、部署资源

部署 RabbitMQ Cluster Operator

1
[root@devops-master k8s-yaml]# kubectl apply -f rabbitmq/cluster/cluster-operator.yml

注意 rabbit-system namespec 建出来之后要给这个空间添加访问私有仓库得key,才能下载镜像

1
2
3
4
5
kubectl -n rabbitmq-system create secret \
docker-registry rabbitmq-cluster-registry-access \
--docker-server=DOCKER-SERVER \
--docker-username=DOCKER-USERNAME \
--docker-password=DOCKER-PASSWORD

部署 RabbitMQ Cluster

1
[root@devops-master k8s-yaml]# kubectl apply -f rabbitmq/cluster/rabbitmq-cluster.yaml

部署管理页面外部访问服务

1
[root@devops-master k8s-yaml]# kubectl apply -f rabbitmq/cluster/rabbitmq-cluster-external.yaml

四、验证

4.1、RabbitMQ Cluster Operator Deployment

1
2
3
[root@devops-master k8s-yaml]# kubectl get deployments -n rabbitmq-system -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
rabbitmq-cluster-operator 1/1 1 1 107m operator registry.devops.com.cn/rabbitmqoperator/cluster-operator:1.14.0 app.kubernetes.io/name=rabbitmq-cluster-operator

4.2、RabbitmqClusters

1
2
3
[root@devops-master k8s-yaml]# kubectl get rabbitmqclusters -n devops
NAME ALLREPLICASREADY RECONCILESUCCESS AGE
rabbitmq-cluster False Unknown 23s

4.3、StatefulSet

1
2
3
[root@devops-master k8s-yaml]# kubectl get sts -o wide -n devops
NAME READY AGE CONTAINERS IMAGES
rabbitmq-cluster-server 3/3 74s rabbitmq registry.devops.com.cn/library/rabbitmq:3.9.22-management

4.4、Pods

1
2
3
4
5
[root@devops-master k8s-yaml]# kubectl get pods -o wide -n devops
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
rabbitmq-cluster-server-0 1/1 Running 0 84s 10.233.116.26 ks-k8s-master-2 <none> <none>
rabbitmq-cluster-server-1 1/1 Running 0 83s 10.233.117.28 ks-k8s-master-0 <none> <none>
rabbitmq-cluster-server-2 1/1 Running 0 82s 10.233.87.31 ks-k8s-master-1 <none> <none>

4.5、Services

1
2
3
4
5
[root@devops-master k8s-yaml]# kubectl get svc -n devops -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
rabbitmq-cluster ClusterIP 10.233.56.153 <none> 15692/TCP,5672/TCP,15672/TCP 107s app.kubernetes.io/name=rabbitmq-cluster
rabbitmq-cluster-external NodePort 10.233.63.84 <none> 15672:31672/TCP 74m app.kubernetes.io/name=rabbitmq-cluster
rabbitmq-cluster-nodes ClusterIP None <none> 4369/TCP,25672/TCP 107s app.kubernetes.io/name=rabbitmq-cluster

4.6、图形化管理界面

五、清理资源

清理 RabbitmqClusters

1
[root@devops-master k8s-yaml]# kubectl delete rabbitmqclusters rabbitmq-cluster -n devops

清理管理页面外部服务

1
[root@devops-master k8s-yaml]# kubectl delete svc rabbitmq-cluster-external -n devops

五、客户端访问RabbitMQ集群地址

客户端连接RabbitMQ集群地址:

连接方式:

  • 客户端可以连接RabbitMQ集群中的任意一个节点。如果一个节点故障,客户端自行重新连接到其他的可用节点;
  • 也就是说,RabbitMQ集群有”重连”机制,但是这种集群连接方式对客户端不透明,不太建议这种连接方式。

推荐方式:给客户端提供一个统一的透明的集群连接地址

rabbitmq-cluster-nodes.devops.svc.cluster.local:5672

参考文档