RocketMQ容器化最佳实践
# 前言
在上一篇文章深度解析:基于 RocketMQ 实现分布式事务的技术实践与原理探究 (opens new window)我们完成基于消息队列实现分布式事务,为了方便后续的开发和环境统一,我们决定将RocketMQ容器化部署到服务器上。所以这篇文章就来演示一下笔者基于docker-compose完成RocketMQ容器化的过程。
本篇文章为了保证连贯性,所以在部署过程的介绍中不会涉及笔者遇到的问题和排查思路,所以当读者根据本文进行部署遇到问题的时候,可以直接到达问题清单一栏中查阅是否有相同情况,然后根据笔者思路进行排查修复。
# 容器化操作步骤
# 编写docker-compose
需要基于docker-compose开发就需要编写yml文件,查阅网上资料笔者找到了这样一份yml文件,内容比较长,笔者将配置参数含义都加以注释,读者可以根据需要进行修改,完成后将这份配置文件上传到服务器上即可。
# docker-compose 语法版本
version: '3'
services:
# NameServer配置
rocketmq-namesrv:
# 镜像版本
image: foxiswho/rocketmq:4.8.0
# 容器名称
container_name: rocketmq-namesrv
# 重启
restart: always
# 端口映射
ports:
- 9876:9876
volumes:
# 日志文件、存储文件内外部容器卷映射
- /app/cloud/rocketmq/namesrv/logs:/home/rocketmq/logs
- /app/cloud/rocketmq/namesrv/store:/home/rocketmq/store
environment:
# jvm参数配置,调整rocket-mq空间
JAVA_OPT_EXT: "-Duser.home=/home/rocketmq -Xms64M -Xmx64M -Xmn64m"
# 启动命令
command: ["sh","mqnamesrv"]
# 网络连接配置
networks:
rocketmq_net:
aliases:
- rocketmq-namesrv
# broker配置
rocketmq-broker:
image: foxiswho/rocketmq:4.8.0
container_name: rocketmq-broker
restart: always
ports:
- 10909:10909
- 10911:10911
volumes:
# 日志、存储、broker配置文件宿主和容器文件映射
- /app/cloud/rocketmq/broker/logs:/home/rocketmq/logs
- /app/cloud/rocketmq/broker/store:/home/rocketmq/store
- /app/cloud/rocketmq/broker/conf/broker.conf:/home/rocketmq/broker.conf
environment:
JAVA_OPT_EXT: "-Duser.home=/home/rocketmq -Xms64M -Xmx64M -Xmn64m"
# 启动命令配置,指定broker配置地址,指定mq NameServer地址,设置topic自动创建为true
command: ["sh","mqbroker","-c","/home/rocketmq/broker.conf","-n","rocketmq-namesrv:9876","autoCreateTopicEnable=true"]
# 确保等NameServer启动后,在启动broker
depends_on:
- rocketmq-namesrv
networks:
rocketmq_net:
aliases:
- rocketmq-broker
# mq控制台配置
rocketmq-console:
image: styletang/rocketmq-console-ng
container_name: rocketmq-console
restart: always
ports:
- 8180:8080
environment:
JAVA_OPTS: "-Drocketmq.namesrv.addr=rocketmq-namesrv:9876 -Dcom.rocketmq.sendMessageWithVIPChannel=false"
depends_on:
- rocketmq-namesrv
networks:
rocketmq_net:
aliases:
- rocketmq-console
# 自定义rocketmq容器网络配置为桥接模式确保当前容器和其他桥接容器、宿主机都是互通
networks:
rocketmq_net:
name: rocketmq_net
driver: bridge
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
上文我们指定了broker.conf对应宿主机的位置为/app/cloud/rocketmq/broker/conf/broker.conf,所以我们就需要在宿主机上创建这个文件,并添加相应配置,内容如下,读者可以根据提示自行修改:
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# 所属集群名字
brokerClusterName=DefaultCluster
# broker 名字,注意此处不同的配置文件填写的不一样,如果在 broker-a.properties 使用: broker-a,
# 在 broker-b.properties 使用: broker-b
brokerName=broker-a
# 0 表示 Master,> 0 表示 Slave
brokerId=0
# nameServer地址,分号分割
# namesrvAddr=rocketmq-nameserver1:9876;rocketmq-nameserver2:9876
# 启动IP,如果 docker 报 com.alibaba.rocketmq.remoting.exception.RemotingConnectException: connect to <192.168.0.120:10909> failed
# 解决方式1 加上一句 producer.setVipChannelEnabled(false);,解决方式2 brokerIP1 设置宿主机IP,不要使用docker 内部IP
brokerIP1=服务器ip地址
# 在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
# 是否允许 Broker 自动创建 Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
# 是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
# Broker 对外服务的监听端口
listenPort=10911
# 删除文件时间点,默认凌晨4点
deleteWhen=04
# 文件保留时间,默认48小时
fileReservedTime=120
# commitLog 每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
# ConsumeQueue 每个文件默认存 30W 条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
# destroyMapedFileIntervalForcibly=120000
# redeleteHangedFileInterval=120000
# 检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
# 存储路径
# storePathRootDir=/home/ztztdata/rocketmq-all-4.1.0-incubating/store
# commitLog 存储路径
# storePathCommitLog=/home/ztztdata/rocketmq-all-4.1.0-incubating/store/commitlog
# 消费队列存储
# storePathConsumeQueue=/home/ztztdata/rocketmq-all-4.1.0-incubating/store/consumequeue
# 消息索引存储路径
# storePathIndex=/home/ztztdata/rocketmq-all-4.1.0-incubating/store/index
# checkpoint 文件存储路径
# storeCheckpoint=/home/ztztdata/rocketmq-all-4.1.0-incubating/store/checkpoint
# abort 文件存储路径
# abortFile=/home/ztztdata/rocketmq-all-4.1.0-incubating/store/abort
# 限制的消息大小
maxMessageSize=65536
# flushCommitLogLeastPages=4
# flushConsumeQueueLeastPages=2
# flushCommitLogThoroughInterval=10000
# flushConsumeQueueThoroughInterval=60000
# Broker 的角色
# - ASYNC_MASTER 异步复制Master
# - SYNC_MASTER 同步双写Master
# - SLAVE
brokerRole=ASYNC_MASTER
# 刷盘方式
# - ASYNC_FLUSH 异步刷盘
# - SYNC_FLUSH 同步刷盘
flushDiskType=ASYNC_FLUSH
# 发消息线程池数量
# sendMessageThreadPoolNums=128
# 拉消息线程池数量
# pullMessageThreadPoolNums=128
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
笔者在安装过程中,遇到broker启动失败,错误码为253原因后文会详述,解决方式是修改我们宿主机关于RocketMQ的所属者和所属组
# 进入宿主机配置mq配置的文件夹,运行这条命令
chown -R 3000:3000 rocketmq/
2
# 前台启动观察日志
笔者将上文配置文件命名为mq.yml,到mq.yml文件所在位置,运行启动命令,注意由于本次部署为第一次部署,我们建议在前台运行方便观察日志确定服务运行情况。
docker-compose -f mq.yml up
当我们看到这段输出,就说明mq的nameserver启动成功了。

按照启动顺序,我们再来看看broker,当我们在控制台看到boot success. serializeType=JSON and name server is rocketmq-namesrv:9876就说明broker也启动成功了。

而mq-console则是一个控制台管理应用,看到spring boot项目启动成功的文本也就说明启动成功了。

此时我们可以查看docker进程就会发现这些项目端口映射是否和yml配置的一致。
[root@xxx ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e9851c9bdd48 foxiswho/rocketmq:4.8.0 "sh mqbroker -c /hom…" 48 minutes ago Up 40 minutes 0.0.0.0:10909->10909/tcp, :::10909->10909/tcp, 9876/tcp, 10912/tcp, 0.0.0.0:10911->10911/tcp, :::10911->10911/tcp rocketmq-broker
2120e73fafd1 styletang/rocketmq-console-ng "sh -c 'java $JAVA_O…" 48 minutes ago Up 40 minutes 0.0.0.0:8180->8080/tcp, :::8180->8080/tcp rocketmq-console
6e8d0c48a533 foxiswho/rocketmq:4.8.0 "sh mqnamesrv" 48 minutes ago Up 40 minutes 10909/tcp, 0.0.0.0:9876->9876/tcp, :::9876->9876/tcp, 10911-10912/tcp rocketmq-namesrv
2
3
4
5
6
# 尝试打开RocketMQ控制台
确定完所有进程都正常启动,我们服务器必须先放行8180端口,然后就可以访问mq-console了,我们对着浏览器键入mq控制台的地址:
http://ip:8180/#/
这时我们就会看到控制台主界面,注意初次进入时界面为英文,读者可以右上角点击更换语言修改为中文,最终效果如下所示:

我们可以在集群一栏查看broker是否和broker.conf配置名字一致,同时也能监控查看近期消费情况,其余的主题、生产者、消费者等控制台信息笔者就不一一演示了,读者可以自行点击查看了解。

# 启动本地服务测试可用性
为了实现本地服务可以连接mq进行正常消费测试,我们的服务器必须放行10911、10909、9876这几个端口,完成后修改各个spring boot应用关于mq的配置。
rocketmq:
name-server: 服务器ip:9876
producer:
group: cloud-group
2
3
4
我们尝试将项目启动,并运行我们的测试代码,以笔者为例,运行的就是上一篇文章实现分布式事务的案例,完成后核服务控台、数据库数据是否正常修改。
确认上述步骤无误,我们再去观察mq控制台可以发现此时消费主题也多了一个我们自定义的消费主题:

我们配置的两个Java进程服务消费者也出现在控制台上,自此我们的应用消费、mq控制台监控容器化部署配置就完成了

确保服务部署无误之后,将docker容器改为后台启动即可
docker-compose -f mq.yml up -d
# 问题清单
# The compose file ‘./docker-compose.yaml‘ is invalid because:networks.rocketmq_net
在笔者启动docker-compose时,服务器输出了这样一段错误,查阅网上资料发现是docker-compose版本过低,导致无法识别这段语法,遇到这个问题我们升级docker-compose版本就好了,以笔者为例,参考StackOverflow的回答将其升级为1.28.5就解决了这个问题。
curl -L "https://github.com/docker/compose/releases/download/1.28.5/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
# broker文件不存在
在启动过程中笔者控制台输出了这样一段错误,原因是笔者第一版mq的yml配置指定容器内部的broker文件在etc目录下,这个文件夹下并不存在这个文件,最后我们将broker.conf改到home目录下,配置内容为/app/cloud/rocketmq/broker/conf/broker.conf:/home/rocketmq/broker.conf问题得以解决。
rocketmq-broker | java.io.FileNotFoundException: /etc/rocketmq/broker.conf (No such file or directory)
rocketmq-broker | at java.io.FileInputStream.open0(Native Method)
rocketmq-broker | at java.io.FileInputStream.open(FileInputStream.java:195)
rocketmq-broker | at java.io.FileInputStream.<init>(FileInputStream.java:138)
rocketmq-broker | at java.io.FileInputStream.<init>(FileInputStream.java:93)
rocketmq-broker | at org.apache.rocketmq.broker.BrokerStartup.createBrokerController(BrokerStartup.java:128)
rocketmq-broker | at org.apache.rocketmq.broker.BrokerStartup.main(BrokerStartup.java:58)
2
3
4
5
6
7
# broker启动时输出exited with code 253
这个问题比较棘手,一开始笔者在控制台没有发现任何不正常的地方,排查半天网络问题都得不到解决,最终从网上得知是权限问题,最终在宿主机下所有关于mq的文件夹下修改所属者和所属组问题得以解决
chown -R 3000:3000 rocketmq/
本着求知的欲望笔者开始推断这个命令的作用,查阅宿主机下没有这个uid和gid为3000的用户,猜测有没有可能是在容器中存在一个需要操作宿主机权限的用户呢?对此笔者定位到mq容器id进入内部查看果然和猜测一致,在容器中确实存在一个名为rocketmq且uid和gid都为3000的用户:
# 进入容器
[root@xxxx ~]# docker exec -it e9851c9bdd48 bash
# 查看容器中是否存在uid和gid为3000的用户
[rocketmq@e9851c9bdd48 bin]$ cat /etc/passwd |grep 3000
rocketmq:x:3000:3000::/home/rocketmq:/bin/bash
2
3
4
5
6
# 本地服务无法注册到mq上
这个问题较好定位,我们本机运行mq进行测试时没有问题,而部署到生产就连接不上,很大概率是网络连接不通,确定我们mq进程的所有端口号尝试按需放行即可。所以我们平时遇到这类问题优先和之前运行成功的样本进行比对猜测错误的原因加以调试。
# 小结
本次mq容器化部署整体过程相较之前遇到的问题会更多一些,从笔者的排查思路中看到,排查过程总是利用控制台输出关键字结合Google(这也是笔者部署之初采用前台启动的原因),注意是Google搜索引擎,该引擎定位问题速度相比百度和bing会更准确一些。
结合网上的种种方案推断解决思路加以调试,对于不懂的解决方式我们一定要吃透,就例如chown 这条命令,网上都没有解释问题的原因,对此笔者查阅了这条命令的作用查阅服务器关于用户信息反推到问题的源头,确保下次遇到类似问题解决速度会更快一些。
# 参考文献
docker-compose安装RocketMQ (opens new window)
Deploy broker with docker-compose returns 253 error #17 (opens new window)
Linux chown命令:修改文件和目录的所有者和所属组 (opens new window)
Docker 搭建部署 RocketMQ 遇到broker 253问题 (opens new window)
Docker 搭建部署 RocketMQ 遇到broker 253问题 (opens new window)
docker run and get exited (253) (opens new window)
Unable to give network name in docker-compose (opens new window)