本篇文章回顾在华润基于K8S和Docker云设施搭建初步高可用具备failover的RocketMQ集群。RocketMQ版本是5.0.0。

目前现状

采用Dledger模式部署集群,3台namesrv,3台broker,namesrv每台1g的Docker部署,broker每台2g的Docker部署。测试以及生产实践在master发生故障或掉线时,broker集群可以选主出新的master。

RocketMQ集群的几种模式

首先我们看有几种集群的部署模式。官网文档对于模式这块的部署讲述的有点模糊,特别对dledger和controller模式之间的关系基本上没有讲,都是靠网上自己学习资料思考去理解。我说下我的理解,基本上就是以下3种模式。

master-slave模式

其中master-slave是最基本的集群,可以单master,多master,多master-slave,这样提供了水平扩展能力,解决高TPS和高QPS的能力。但是最主要缺陷是master挂了,slave没办法接替其角色继续写入消息,只能给consumer提供消费消息能力,相当于3master的集群,现在变成2master的负载能力。

Dledger模式

那么在此基础上增加了Dledger模式,就是让master-slave组具备自动failover的能力。这个模式会在broker之间建立通信,通过raft协议选出master剩余2个broker节点为其slave。master通过配置可以同步或异步地向slave发送消息。在master宕机后自动选出一个新master。因为基于raft协议所以最低需要3个节点才能进行failover的选主过程,在只有2个节点时不能完成此过程。

controller模式

controller模式是rmq在5.0版本后新推出的一种高可用集群模式。DLedger(Raft)能力从原本的复制链路上移到controller,将选主切换能力上移,单独作为一个选主组件。RIP-44提出增加一个DLedgerControlller的选主组件,它是可选部署的,在无切换架构的基础上,部署后经过配置就可以拥有切换的能力,它可以内嵌在Nameserver中,也可以独立部署。如果内嵌在NameServer中,NameServer本身的能力还是无状态的,比如有三个NameServer都内嵌部署了DLedger Controller,如果宕机两个节点,NameServer仍然存在一个可以提供路由服务,DLedger Controller宕机两个节点后由于达不到Raft多数派的要求无法再协助Broker切换,但是消息集群本身正常的收发服务不会受到影响。

RocketMQ Dledger集群搭建方案

最终在部署模式上我选择了Dledger模式部署。相对master-slave组有failover能力。相对controller模式部署更简单。

编写broker配置

首先需要编写broker配置文件。我们需要修改conf/dledger/broker.conf配置文件。将这份文件copy3份出来,命名为broker0.conf、broker1.conf、broker2.conf。这3份broker文件基本都一致,brokerRole都配置为master,让其自主选出master。只是在brokerIP、dLegerSelfId上有不同。

# 所属集群名称
brokerClusterName = CpmsCluster
#broker名称,master和slave使用相同的名称,表明他们的主从关系
brokerName = brokerA
#表示几点做消息删除动作,默认是凌晨4点
deleteWhen = 04
#在磁盘上保留消息的时长,单位是小时
fileReservedTime = 48
# 这里的ip是此broker部署的节点IP,因为是K8S上部署,所以是一个内网可以解析为IP的工作负载名称
brokerIP1 = rmq-brokerA0
# namesrv也是K8S上内网工作负载
namesrvAddr=rmq-nameserv0:9876;rmq-nameserv1:9876;rmq-nameserv2:9876
#Broker 对外服务的监听端口
listenPort = 30911
# 配置为一个异步复制的master
brokerRole=ASYNC_MASTER
# 消息同步刷盘
flushDiskType=SYNC_FLUSH

#存储路径
storePathRootDir=/rmq/store
#commitLog存储路径
storePathCommitLog=/rmq/store/commitlog
#消费队列存储路径
storePathConsumeQueue=/rmq/store/consumequeue
#索引存储路径
storePathIndex=/rmq/store/index
#checkpoint文件存储路径
storeCheckpoint=/rmq/store/checkpoint
#abort文件存储路径
abortFile=/rmq/store/abort

# 开启Dledger集群
enableDLegerCommitLog=true
# DLedger Raft Group的名字,建议和 brokerName 保持一致
dLegerGroup=brokerA
# 集群内所有broker节点的IP
dLegerPeers=n0-rmq-brokerA0:40911;n1-rmq-brokerA1:40912;n2-rmq-brokerA2:40913
# 节点 id, 必须属于 dLegerPeers 中的一个;同 Group 内各个节点要唯一
dLegerSelfId=n0
# 发送线程个数,建议配置成 Cpu 核数
sendMessageThreadPoolNums=4

编写dockerfile

在3份broker配置准备好后,就可以编写打包namesrv和broker的dockerfile了。这需要一些dockerfile知识。首先编写namesrv。

# 这里指定镜像的适用平台和基础镜像
FROM --platform=linux/amd64 openjdk:8-jdk-alpine3.9

# 容器中建一个目录
RUN mkdir -p /rmq
# 将当前工作目录定到opt
WORKDIR /rmq
# 把宿主机Dockerfile下的文件拷贝到容器/opt/rocketmq
COPY . /rmq
RUN mkdir -p /rmq/logs
RUN echo "Asia/Shanghai" > /etc/timezone

CMD nohup sh ./bin/mqnamesrv

我这里是直接准备好3份dockerfile以方便打包3个broker镜像。有了3个broker镜像,我直接在K8S上部署这3个工作负载就行嘞。这3个dockerfile都基本一致,只不过在最后CMD启动时传的分别是broker-n0.conf,n1,n2等文件。

FROM --platform=linux/amd64 openjdk:8-jdk-alpine3.9

# 容器中建一个目录
RUN mkdir -p /rmq
# 将当前工作目录定到opt
WORKDIR /rmq
# 把宿主机Dockerfile下的文件拷贝到容器/rmq
COPY . /rmq
RUN mkdir -p /rmq/logs
RUN echo "Asia/Shanghai" > /etc/timezone

# 将容器中指定目录挂载到宿主机匿名卷(没有指定宿主机的指定目录就是匿名卷)。双方目录不存在都会自动创建,且目录下的所有文件都持久化
VOLUME ["/rmq/store","/rmq/logs"]
# ${}取环境变量,可以在docker run -e传入
CMD sh /rmq/bin/mqbroker -c conf/dledger/broker-n0.conf

打包镜像上华润云

现在我们准备好了broker配置和dockerfile,根据线上宿主机的实际配置要调整下namesrv和broker的内存大小(部门预算有限,要省着点花)。

修改runserver.sh的jvm内存参数,调整到合适的大小,估计1g就够了

JAVA_OPT="${JAVA_OPT} -server -Xms1g -Xmx1g -Xmn512m -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=128m"

修改runbroker.sh的jvm内存参数

JAVA_OPT="${JAVA_OPT} -server -Xms2g -Xmx2g -Xmn1g" JAVA_OPT="${JAVA_OPT} -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=10 -XX:InitiatingHeapOccupancyPercent=30 -XX:SoftRefLRUPolicyMSPerMB=0"

然后把3个broker镜像和namesrv镜像打包出来了。在K8S中建立3个broker0、1、2的工作负载,namesrv工作负载。

踩得坑

对于broker和namesrv要在K8S上建工作负载时开放指定的端口,否则集群会无法互相通信。其中nameserv要开的端口有:9876。broker在集群模式下端口比较奇怪,listenPort是配置文件中指定的,如30911,还有集群内节点通信的端口40911。此外还有一个端口30909。这个端口不知道干嘛的,但是看其它两个broker的端口分别是30919、30929。好像是各自broker的listenPort-2。所以也都开放了。不开放的话,rmq-dashboard在连接broker时会报错提示。

压测和上线情况

压测方法

  • 编写生产者&消费者程序,多线程并发地发送消息给MQ,同时消费者消费消息。指定并发线程数和消息总数。完整的走了一遍发送消息->存储消息->消费消息的链路。更贴近真实线上业务场景。
  • 官方自带的压测脚本,省去自己编写脚本的工作,走了一遍发送消息->存储消息链路。

目前我们主要对broker集群存储消息的性能做测试,所以选择了第2种方法更加省时省力测试。

 压测场景

并发线程10,50万消息,每个消息512字节

从监控master所在201机器上看CPU、内存、磁盘IO、带宽,基本都处于较低资源使用水平。cpu不到30%,内存基本无变化,磁盘IOPS在400,都比较健康。TPS达到1700左右,最大RT246ms,50万消息10个线程并发耗时2分30秒发送完毕。

并发线程20,100万消息,512字节

cpu使用率比并发10稍高,峰值50%左右。内存平稳。iops稳定600左右。

gc情况良好,没有fgc,基本对象在ygc回收完毕。但是年轻代随着并发量增加,内存使用增加的比并发10要快很多,ygc频率增加,但是每次ygc时间很短。 

并发线程数增加后,吞吐量也增加,还没到拐点。TPS 5700,最大RT 360ms,平均 4ms。3分钟发完。

经过以上压测得出压测报告,TPS达到近6000的消息发送,且最大RT360ms,(实际业务中因为producer发消息给broker,有网络IO,所以这个TPS和RT会高一点)完全可以应付我们的日常业务需求。因此我们只用了3台namesrv每个实例1G,3台broker每个实例2G,小成本机器就搭建了一套初步高可用的线上MQ集群。 目前这套集群在华润万象生活的业务生产中每天收发100万左右的消息,集群状态平稳。

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐