RocketMQ
概念
移步 rocketmq.apache.org/zh/docs/
抽象图
主要角色
- 生产者
- 消费者
- 消息
- 消息队列
- 主题
- 消费组
- 订阅关系
主要服务
-
namesrv:namesrv相当于微服务中的注册发现中心
-
broker:broker是消息管理的主要对象
-
proxy:代理控制,支持一些特定协议,权限控制,统一入口等
-
dashborad:控制面板
-
client:客户端(消息生产者、消费者)
版本区别
- 4.x版本,主要是namesrv + broker,
- 5.x版本,多了个proxy
实战
演示为在同一个机器的docker容器,使用docker-compose编排工具,暂未设置主从
rocket-mq-namesrv.yml
version: '3.8'
services:
namesrv:
image: apache/rocketmq:5.3.1
container_name: namesrv
ports:
- 9876:9876
volumes:
- ./namesrv-conf/plain_acl.yml:/home/rocketmq/rocketmq-5.3.1/conf/plain_acl.yml
- ./namesrv-conf/broker.conf:/home/rocketmq/rocketmq-5.3.1/conf/broker.conf
command: sh mqnamesrv -n 192.168.17.69:9876 -c /home/rocketmq/rocketmq-5.3.1/conf/broker.conf
networks:
- rocketmq-net
networks:
rocketmq-net:
name: rocketmq-net
driver: bridge
namesrv-conf的broker.conf
brokerClusterName = DefaultCluster
brokerName = broker-a
brokerId = 0
deleteWhen = 04
fileReservedTime = 48
brokerRole = ASYNC_MASTER
flushDiskType = ASYNC_FLUSH
namesrvAddr=192.168.17.69:9876
brokerIP1=192.168.17.69
#权限
aclEnable=false
#acl配置文件路径
aclConfigFile=/home/rocketmq/rocketmq-5.3.1/conf/acl.conf
namesrv-conf的plain_acl.conf
globalWhiteRemoteAddresses:
- 10.10.103.*
- 192.168.0.*
accounts:
- accessKey: RocketMQ
secretKey: 12345678
whiteRemoteAddress:
admin: false
defaultTopicPerm: DENY
defaultGroupPerm: SUB
topicPerms:
- topicA=DENY
- topicB=PUB|SUB
- topicC=SUB
groupPerms:
# the group should convert to retry topic
- groupA=DENY
- groupB=PUB|SUB
- groupC=SUB
- accessKey: rocketmq2
secretKey: 12345678
whiteRemoteAddress: 192.168.0.*
# if it is admin, it could access all resources
admin: true
rocket-mq-broker.yml
version: '3.8'
services:
# 实例1
broker1:
image: apache/rocketmq:5.3.1
container_name: rmqbroker-1
ports:
# vip
- 10909:10909
# normal
- 10911:10911
# HA
- 10912:10912
volumes:
# 权限配置文件
- ./broker-conf/plain_acl.yml:/home/rocketmq/rocketmq-5.3.1/conf/plain_acl.yml
# 路由配置文件(实际上不知道为什么没生效,需要通过 command 的 -c 指定,见下)
- ./broker-conf/broker1.conf:/home/rocketmq/rocketmq-5.3.1/conf/broker.conf
- ./broker-conf/store1:/mnt/data/rocketmq/store
environment:
- NAMESRV_ADDR=namesrv:9876
# 初始化配置,指定命名空间,指定配置文件,自动创建topic(可选)
command: sh mqbroker -n namesrv:9876 -c /home/rocketmq/rocketmq-5.3.1/conf/broker.conf
networks:
- rocketmq-net
# 实例2(需要指定一个新的broker.conf文件否则会重名)
broker2:
image: apache/rocketmq:5.3.1
container_name: rmqbroker-2
ports:
- 10929:10909
- 10921:10911
- 10922:10912
volumes:
- ./broker-conf/plain_acl.yml:/home/rocketmq/rocketmq-5.3.1/conf/plain_acl.yml
# 注意配置文件是2
- ./broker-conf/broker2.conf:/home/rocketmq/rocketmq-5.3.1/conf/broker.conf
- ./broker-conf/store2:/mnt/data/rocketmq/store
environment:
- NAMESRV_ADDR=namesrv:9876
command: sh mqbroker -n namesrv:9876 -c /home/rocketmq/rocketmq-5.3.1/conf/broker.conf
networks:
- rocketmq-net
networks:
rocketmq-net:
# 加入已有网络直接加入无需再次创建
external: true
broker1.conf
brokerClusterName = DefaultCluster
brokerName = broker-1
brokerId = 0
deleteWhen = 04
fileReservedTime = 48
brokerRole = ASYNC_MASTER
flushDiskType = ASYNC_FLUSH
namesrvAddr=192.168.17.69:9876
brokerIP1=192.168.17.69
brokerIP2=192.168.17.69
#权限
aclEnable=true
#自动创建topic
autoCreateTopicEnable=true
#自动创建订阅组
autoCreateSubscriptionGroup=true
#acl配置文件路径
aclConfigFile=/home/rocketmq/rocketmq-5.3.1/conf/acl.conf
# 指定存储 CommitLog 的路径,默认为 ${user.home}/store
storePathRootDir=/mnt/data/rocketmq/store
# 指定消费进度和索引文件的存储路径,默认为 ${user.home}/store
storePathCommitLog=/mnt/data/rocketmq/store/commitlog
# 指定消费队列的存储路径,默认为 ${user.home}/store/consumequeue
storePathConsumeQueue=/mnt/data/rocketmq/store/consumequeue
# 指定索引文件的存储路径,默认为 ${user.home}/store/index
storePathIndex=/mnt/data/rocketmq/store/index
# 指定检查点文件的存储路径,默认为 ${user.home}/store/checkpoint
storeCheckpoint=/mnt/data/rocketmq/store/checkpoint
# 指定 abort 文件的存储路径,默认为 ${user.home}/store/abort
abortFile=/mnt/data/rocketmq/store/abort
broker2.conf跟1大同小异
broker1跟2共用一个plain_acl.yml,跟namesrv大同小异
rocket-mq-proxy.yml
version: '3.8'
services:
proxy:
image: apache/rocketmq:5.3.1
container_name: rmqproxy
ports:
# http端口
- 8080:8080
# grpc端口
- 8081:8081
environment:
- ROCKETMQ_PROXY_HTTP_PORT=8080
# - ROCKETMQ_PROXY_ENABLE_GRPC=false # 禁用 gRPC
# 设置namesrv地址,因为是在容器内同一网络所以直接使用容器名
- NAMESRV_ADDR=namesrv:9876
- ROCKETMQ_NAMESRV_ADDR=namesrv:9876
- ROCKETMQ_PROXY_CLUSTER_NAME=DefaultCluster
- JAVA_OPTS=-Drocketmq.acl.enable=true
# -Drocketmq.namesrv.acl.file=/home/rocketmq/rocketmq-5.3.1/conf/acl.conf
# -Drocketmq.client.accessChannel=ROCKETMQ \
# -Drocketmq.client.username=proxy_account \
# -Drocketmq.client.password=proxy_password
volumes:
- ./proxy-conf/rmq-proxy.json:/home/rocketmq/rocketmq-5.3.1/conf/rmq-proxy.json
- ./proxy-conf/log4j2.properties:/home/rocketmq/rocketmq-5.3.1/conf/log4j2.properties
- ./proxy-conf/plain_acl.yml:/home/rocketmq/rocketmq-5.3.1/conf/plain_acl.yml
# 在proxy架构下(Local模式请忽略)
# 指定 proxy的配置文件,注意,当broker启用了acl鉴权后,配置文件需要开启内部鉴权模式,否则proxy无法连接broker
# see [https://github.com/apache/rocketmq/issues/8406#issuecomment-2290768529](url)
# see [https://blog.csdn.net/weixin_47585404/article/details/140445335](url)
# 初始化命令,指定namesrv地址,指定配置文件
command: sh mqproxy -n namesrv:9876 -pc /home/rocketmq/rocketmq-5.3.1/conf/rmq-proxy.json
networks:
- rocketmq-net
networks:
rocketmq-net:
external: true
proxy的配置文件 proxy-conf/rmq-proxy.json
{
"rocketMQClusterName": "DefaultCluster",
"namesrvAddr": "192.168.17.69:9876",
"remotingListenPort": "8080",
"grpcServerPort": "8081",
"enableAclRpcHookForClusterMode": "true", // 非常重要
"proxyMode": "CLUSTER"
}
proxy的acl,账号很重要,java客户端将通过这些账号区分权限
globalWhiteRemoteAddresses:
- 10.10.103.*
- 192.168.17.*
accounts:
- accessKey: RocketMQ
secretKey: 12345678
whiteRemoteAddress:
admin: false
defaultTopicPerm: DENY
defaultGroupPerm: SUB
topicPerms:
- topicA=DENY
- topicB=PUB|SUB
- topicC=SUB
groupPerms:
# the group should convert to retry topic
- groupA=DENY
- groupB=PUB|SUB
- groupC=SUB
- accessKey: rocketmq2
secretKey: 12345678
whiteRemoteAddress: 192.168.17.*
# if it is admin, it could access all resources
admin: true
- accessKey: eic_producer
secretKey: 66666666
whiteRemoteAddress: 192.168.17.*
# if it is admin, it could access all resources
admin: false
defaultTopicPerm: PUB
defaultGroupPerm: SUB
- accessKey: eic_consumer
secretKey: 66666666
whiteRemoteAddress: 192.168.17.*
# if it is admin, it could access all resources
admin: false
defaultTopicPerm: SUB
defaultGroupPerm: SUB
控制台 rocket-mq-dashboard.yml
version: '3.8'
services:
rmqdashboard:
image: apacherocketmq/rocketmq-dashboard:latest
container_name: rmqdashboard
ports:
- 8083:8080
# command: sh mqnamesrv
environment:
- NAMESRV_ADDR=namesrv:9876
# 开启控制面板登录,设置mq登录凭证,这个凭证对应plain_acl的账号
- JAVA_OPTS=-Drocketmq.config.loginRequired=true -Drocketmq.dashboard.proxyAddr=192.168.17.69:8080 -Drocketmq.config.accessKey=rocketmq2 -Drocketmq.config.secretKey=12345678
volumes:
# 控制面板登录用户账号信息
- ./dashboard-conf/users.properties:/tmp/rocketmq-console/data/users.properties
networks:
- rocketmq-net
networks:
rocketmq-net:
external: true
users.properties 控制台的账号
# 1是管理员权限,0是普通用户
admin=123456,1
dev=123456,0
启动
依次启动 namesrv、broker、proxy、dashboard
docker-compose -f .\rocket-mq-namesrv.yml up -d
docker-compose -f .\rocket-mq-broker.yml up -d
docker-compose -f .\rocket-mq-proxy.yml up -d
docker-compose -f .\rocket-mq-dashboard.yml up -d
namesrv:输出
2025-01-17 18:11:55 load config properties file OK, /home/rocketmq/rocketmq-5.3.1/conf/broker.conf
2025-01-17 18:11:56 The Name Server boot success. serializeType=JSON, address 0.0.0.0:9876
broker:
2025-01-17 18:12:02 The broker[broker-1, 192.168.17.69:10911] boot success. serializeType=JSON and name server is namesrv:9876
2025-01-17 18:12:01 The broker[broker-2, 192.168.17.69:10911] boot success. serializeType=JSON and name server is namesrv:9876
proxy:
2025-01-17 18:13:37 Fri Jan 17 10:13:37 UTC 2025 rocketmq-proxy startup successfully
浏览器打开 http://192.168.17.69:8083
登录admin
登录dev
JAVA 工程
pom.xml 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-spring-boot-starter -->
<!-- <dependency>-->
<!-- <groupId>org.apache.rocketmq</groupId>-->
<!-- <artifactId>rocketmq-spring-boot-starter</artifactId>-->
<!-- <version>2.3.1</version>-->
<!-- </dependency>-->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-v5-client-spring-boot-starter</artifactId>
<version>2.3.1</version>
</dependency>
application.yml
server:
port: 8088
rocketmq:
# rocketmq-spring-boot-starter 这个版本使用这个配置,连接的是namesrv地址,通过namesrv地址做服务发现分发消息 !!
# name-server: 192.168.17.69:8080
# producer:
# group: TEST_GROUP
# send-message-timeout: 60000
# retry-times-when-send-async-failed: 1
# retry-times-when-send-failed: 1
# # 有发布跟订阅权限
# access-key: eic_producer
# secret-key: 66666666
# consumer:
# # 只有订阅权限
# access-key: eic_consumer
# secret-key: 66666666
# rocketmq-v5-client-spring-boot-starter 这个版本使用这个配置,连接的是proxy地址,通过proxy地址直接访问消息队列!!
# [https://help.aliyun.com/zh/apsaramq-for-rocketmq/cloud-message-queue-rocketmq-5-x-series/developer-reference/consumer-types?spm=a2c4g.11186623.0.0.63be2821oS5qtw#section-r97-urp-who](url)
producer:
# 有发布跟订阅权限
access-key: eic_producer
secret-key: 66666666
endpoints: 192.168.17.69:8080
max-attempts: 3
# 发送消息超时时间,单位毫秒
request-timeout: 3
# push 模式是mq主动给客户端推送消息
push-consumer:
endpoints: 192.168.17.69:8080
# 此账号只有订阅权限
access-key: eic_consumer
secret-key: 66666666
# 订阅主题 tag,不知道有什么用,但是不配置会报错,可以在每个consumer选择特定的tag处理
tag: "*"
# simple模式需要客户端自行维护offset,自行拉取消息
# simple-consumer:
# endpoints: 192.168.17.69:8080
# access-key: eic_consumer
# secret-key: 66666666
# # 订阅主题 tag,不知道有什么用,但是不配置会报错,可以在每个consumer选择特定的tag处理
# tag: "*"
生产者(便于测试使用web接口)
package com.example.rocketmq;
import org.apache.rocketmq.client.apis.producer.SendReceipt;
import org.apache.rocketmq.client.core.RocketMQClientTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Rcong
* @version 1.0
* @description:
* @date 2025/1/17 16:40
*/
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private RocketMQClientTemplate rocketMQClientTemplate;
@GetMapping()
public void sendMsg(@RequestParam(value = "msg") String msg, @RequestParam(value = "tag") String tag) {
Message<String> message = MessageBuilder.withPayload(msg).build();
SendReceipt eicTestTopic = rocketMQClientTemplate.syncSendNormalMessage("EIC_TEST_TOPIC:" + tag, message);
System.out.println("发送 " + tag + " 消息成功,消息ID:----------- " + eicTestTopic.getMessageId());
}
}
订阅所有消息tag的消费者
package com.example.rocketmq;
import org.apache.rocketmq.client.annotation.RocketMQMessageListener;
import org.apache.rocketmq.client.apis.consumer.ConsumeResult;
import org.apache.rocketmq.client.apis.message.MessageView;
import org.apache.rocketmq.client.core.RocketMQListener;
import org.springframework.stereotype.Component;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
/**
* @author Rcong
* @version 1.0
* @description: 注意,一条消息只会被 一个 consumerGroup 的实例消费
* @date 2025/1/7 16:18
*/
@Component
@RocketMQMessageListener(topic = "EIC_TEST_TOPIC", consumerGroup = "EIC_TEST_GROUP", consumptionThreadCount = 1, tag = "*")
public class ConsumerAllDemo implements RocketMQListener {
@Override
public ConsumeResult consume(MessageView messageView) {
ByteBuffer body = messageView.getBody();
byte[] bodyByte = new byte[body.remaining()];
body.get(bodyByte);
System.out.println("All tag 收到 订阅 * 的MQ消息:------------: : " + new String(bodyByte, StandardCharsets.UTF_8));
return ConsumeResult.SUCCESS;
}
}
只订阅消息tag=test_tag的消费者
package com.example.rocketmq;
import org.apache.rocketmq.client.annotation.RocketMQMessageListener;
import org.apache.rocketmq.client.apis.consumer.ConsumeResult;
import org.apache.rocketmq.client.apis.message.MessageView;
import org.apache.rocketmq.client.core.RocketMQListener;
import org.springframework.stereotype.Component;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
/**
* @author Rcong
* @version 1.0
* @description: 注意,一条消息只会被 一个 consumerGroup 的实例消费
* @date 2025/1/7 16:18
*/
@Component
@RocketMQMessageListener(topic = "EIC_TEST_TOPIC", consumerGroup = "EIC_TEST_GROUP_FILTER", consumptionThreadCount = 1, tag = "test_tag")
public class ConsumerDemo implements RocketMQListener {
@Override
public ConsumeResult consume(MessageView messageView) {
ByteBuffer body = messageView.getBody();
byte[] bodyByte = new byte[body.remaining()];
body.get(bodyByte);
System.out.println("自定义收到 订阅 test_tag 的MQ消息:------------: : " + new String(bodyByte, StandardCharsets.UTF_8));
return ConsumeResult.SUCCESS;
}
}
测试环节
http://localhost:8088/test?msg=随意tag的消息&tag=random_tag
http://localhost:8088/test?msg=特定tag的消息&tag=test_tag
结语
可以看到,当消费者设置*的时候可以消费任何tag的消息,设置了 指定tag的消费者只能消费对应消息
消费组
自行查阅官方文档
注意事项
所有服务可以在同一个docker-compose.yml里面编排,注意先后顺序,使用depends_on编排顺序
踩坑点
-
当使用了proxy服务作为消息连接入口,java客户端api需要使用 rocketmq-v5-client-spring-boot-starter并配置endpoint地址为proxy的服务地址;当使用namesrv服务作为消息队列入口,使用rocketmq-spring-boot-starter ,并配置name-server地址为namesrv的服务地址
-
开启broker跟namesrv开启ACL后,需要配置proxy的账号,不然proxy无法登录