Docker部署RocketMQ 5.x

241 阅读8分钟

RocketMQ

概念

移步 rocketmq.apache.org/zh/docs/
抽象图

image-1.png

主要角色

  • 生产者
  • 消费者
  • 消息
  • 消息队列
  • 主题
  • 消费组
  • 订阅关系

主要服务

  • namesrv:namesrv相当于微服务中的注册发现中心

  • broker:broker是消息管理的主要对象

  • proxy:代理控制,支持一些特定协议,权限控制,统一入口等

  • dashborad:控制面板

  • client:客户端(消息生产者、消费者)

版本区别

- 4.x版本,主要是namesrv + broker,
- 5.x版本,多了个proxy

image-8.png

实战

演示为在同一个机器的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

image-3.png

登录admin

image-4.png

登录dev

image-5.png

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

image-6.png

http://localhost:8088/test?msg=特定tag的消息&tag=test_tag

image-7.png

结语

可以看到,当消费者设置*的时候可以消费任何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无法登录