Linux 服务器 RocketMQ 5.1.4 安装 + ACL 密码鉴权 + 启停脚本 + SpringBoot Java 生产消费 Demo

前言 用来学习MQ使用的 demo场景

本文环境:CentOS7/8、JDK8、RocketMQ 5.1.4(ACL1.0稳定版,支持任意时间戳定时消息)

包含:服务器离线安装、JVM内存调优、开启ACL账号密码、启动/停止/重启命令、防火墙放行端口、SpringBoot整合带密码连接、生产者发送定时消息、消费者消费并调用业务接口,适配课程定时推送场景。

一、服务器前置准备

1. 环境要求

  1. JDK 1.8+ 已配置环境变量
  2. 服务器开放端口:9876(NameServer)10911(Broker)
bash 复制代码
# 防火墙放行端口
firewall-cmd --add-port=9876/tcp --permanent
firewall-cmd --add-port=10911/tcp --permanent
firewall-cmd --reload
# 查看端口是否放行
firewall-cmd --list-ports

2. 创建安装目录

bash 复制代码
mkdir -p /usr/local/rocketmq
cd /usr/local/rocketmq

二、下载&解压RocketMQ

1. 下载二进制包

官网下载地址:https://rocketmq.apache.org/dowloading/releases

选择 rocketmq-all-5.1.4-bin-release.zip

上传到服务器 /usr/local/rocketmq 目录

2. 解压

bash 复制代码
unzip rocketmq-all-5.1.4-bin-release.zip
mv rocketmq-all-5.1.4-bin-release rocketmq5.1.4
cd rocketmq5.1.4
# 定义环境变量(临时生效,永久写入/etc/profile)
export ROCKETMQ_HOME=/usr/local/rocketmq/rocketmq5.1.4

三、修改JVM内存(低配服务器必改,否则启动OOM)

1. 修改NameServer内存

bash 复制代码
vim bin/runserver.sh
# 找到JAVA_OPT,修改Xms Xmx
JAVA_OPT="${JAVA_OPT} -Xms256m -Xmx256m -Xmn128m"

2. 修改Broker内存

bash 复制代码
vim bin/runbroker.sh
JAVA_OPT="${JAVA_OPT} -Xms256m -Xmx256m -Xmn128m"

四、开启ACL密码鉴权(设置账号密码)

步骤1:开启Broker ACL开关

bash 复制代码
vim conf/broker.conf
# 文件末尾追加
aclEnable=true
# 允许自动创建Topic(测试环境)
autoCreateTopicEnable=true

步骤2:配置账号、密码、权限 plain_acl.yml

bash 复制代码
vim conf/plain_acl.yml

完整配置(业务账号仅允许发送/消费课程推送Topic):

yaml 复制代码
accounts:
  # 管理员账号,运维使用
  - accessKey: mq_admin
    secretKey: Admin@2026#Rmq
    whiteRemoteAddress: 127.0.0.1,服务器内网IP
    admin: true
  # 业务应用账号(Java项目连接使用,用户名密码)
  - accessKey: course_push_app
    secretKey: Push@666888
    whiteRemoteAddress: 0.0.0.0 # 所有IP可访问,生产填业务服务IP
    admin: false
    defaultTopicPerm: DENY
    defaultGroupPerm: DENY
    # 仅允许操作课程推送Topic:发布PUB 订阅SUB
    topicPerms:
      - "course_push_topic=PUB|SUB"
    groupPerms:
      - "course-push-consumer-group=SUB"
globalWhiteRemoteAddresses:
  - 127.0.0.1
  • accessKey = 用户名
  • secretKey = 密码
    修改完成保存,重启Broker生效

五、RocketMQ 启动/停止/重启完整命令

进入mq根目录执行:cd /usr/local/rocketmq/rocketmq5.1.4

1. 启动顺序:先NameServer,后Broker

启动NameServer
bash 复制代码
# 后台启动,输出日志到nohup.out
nohup sh bin/mqnamesrv &
# 查看启动日志,出现 boot success 代表成功
tail -f ~/logs/rocketmqlogs/namesrv.log
启动Broker(绑定本机IP)
bash 复制代码
# 替换为你的服务器公网/内网IP
nohup sh bin/mqbroker -n 127.0.0.1:9876 &
# 查看Broker日志
tail -f ~/logs/rocketmqlogs/broker.log

2. 停止服务(顺序先Broker,后NameServer)

bash 复制代码
# 停止Broker
sh bin/mqshutdown broker
# 停止NameServer
sh bin/mqshutdown namesrv

3. 重启流程

bash 复制代码
# 1.停止
sh bin/mqshutdown broker
sh bin/mqshutdown namesrv
# 2.等待5秒
sleep 5
# 3.重新启动
nohup sh bin/mqnamesrv &
sleep 3
nohup sh bin/mqbroker -n 127.0.0.1:9876 &

4. 验证服务连通(内置工具测试)

bash 复制代码
export NAMESRV_ADDR=127.0.0.1:9876
# 发送测试消息(未开ACL可用,开启ACL会报错,需代码带AK/SK)
sh bin/tools.sh org.apache.rocketmq.example.quickstart.Producer
sh bin/tools.sh org.apache.rocketmq.example.quickstart.Consumer

六、SpringBoot Java Demo(带ACL密码,定时消息生产+消费)

1. Maven依赖 pom.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.15</version>
        <relativePath/>
    </parent>

    <groupId>com.demo</groupId>
    <artifactId>rocketmq-acl-demo</artifactId>
    <version>1.0.0</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- SpringBoot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- RocketMQ Starter 带ACL支持 -->
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-spring-boot-starter</artifactId>
            <version>2.2.3</version>
        </dependency>
        <!-- Redis 幂等防重复推送 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- JSON序列化 -->
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.32</version>
        </dependency>
    </dependencies>
</project>

2. application.yml 配置(核心:配置mq地址+ACL账号密码)

yaml 复制代码
spring:
  application:
    name: rocketmq-course-push
  redis:
    host: 127.0.0.1
    port: 6379

# mq 配置
rocketmq:
  # 服务器 NameServer 地址,替换为你的服务器IP
  name-server: 127.0.0.1:9876
  producer:
    group: course-push-producer-group
    # ACL账号密码(plain_acl.yml配置的业务账号)
    access-key: course_push_app
    secret-key: Push@666888
    send-message-timeout: 5000
  consumer:
    access-key: course_push_app
    secret-key: Push@666888

3. 消息实体类 CoursePushMsg.java

java 复制代码
package com.demo.entity;

import lombok.Data;

@Data
public class CoursePushMsg {
    private Long courseId;
    private String title;
    private String content;
    private Long endTimeStamp; // 课程结束时间戳(毫秒)
}

4. 生产者服务:发送定时消息 CoursePushProducer.java

java 复制代码
package com.demo.service;

import com.alibaba.fastjson2.JSON;
import com.demo.entity.CoursePushMsg;
import org.apache.rocketmq.client.producer.SendMessageWithTimeStampRequest;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;

@Service
public class CoursePushProducer {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    private static final String TOPIC = "course_push_topic";

    /**
     * 发送课程结束定时推送消息(指定精确到期时间戳)
     * @param msg 课程消息实体
     */
    public void sendCourseTimedMsg(CoursePushMsg msg) {
        byte[] body = JSON.toJSONString(msg).getBytes(StandardCharsets.UTF_8);
        SendMessageWithTimeStampRequest request = SendMessageWithTimeStampRequest.builder()
                .topic(TOPIC)
                .body(body)
                .deliveryTimeStamp(msg.getEndTimeStamp())
                .build();
        try {
            rocketMQTemplate.syncSendWithTimestamp(request);
            System.out.println("定时消息发送成功,课程ID:" + msg.getCourseId());
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("发送MQ定时消息失败");
        }
    }
}

5. 模拟推送接口客户端 PushApiClient.java

java 复制代码
package com.demo.client;

import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

@Component
public class PushApiClient {
    @Resource
    private RestTemplate restTemplate;

    /**
     * 业务推送接口:根据课程ID批量推送学员消息
     */
    public void pushNotice(Long courseId) {
        // 替换为你自己的推送服务接口地址
        String url = "http://127.0.0.1:8080/api/push/send?courseId=" + courseId;
        String resp = restTemplate.postForObject(url, null, String.class);
        System.out.println("调用推送接口完成,返回:" + resp);
    }
}

6. 消费者:监听消息、校验课程、调用推送接口 CoursePushConsumer.java

java 复制代码
package com.demo.consumer;

import com.alibaba.fastjson2.JSON;
import com.demo.client.PushApiClient;
import com.demo.entity.CoursePushMsg;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.TimeUnit;

@Component
@RocketMQMessageListener(
        topic = "course_push_topic",
        consumerGroup = "course-push-consumer-group"
)
public class CoursePushConsumer implements MessageListenerConcurrently {

    @Autowired
    private PushApiClient pushApiClient;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext context) {
        for (MessageExt msg : msgs) {
            try {
                String bodyStr = new String(msg.getBody());
                CoursePushMsg msgData = JSON.parseObject(bodyStr, CoursePushMsg.class);
                Long courseId = msgData.getCourseId();
                long msgScheduleTs = msg.getDeliveryTimestamp();
                long realEndTs = msgData.getEndTimeStamp();

                // 1. 过滤旧消息(修改课程结束时间产生的过期定时消息)
                if (Math.abs(msgScheduleTs - realEndTs) > 60 * 1000) {
                    System.out.println("旧定时消息,丢弃 courseId:" + courseId);
                    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                }

                // 2. Redis幂等,防止重复推送
                String redisKey = "push:course:" + courseId;
                if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))) {
                    System.out.println("该课程已推送,跳过");
                    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                }

                // 3. 核心逻辑:调用业务推送接口
                pushApiClient.pushNotice(courseId);

                // 4. 成功后写入幂等标记,7天过期
                redisTemplate.opsForValue().set(redisKey, "1", 7, TimeUnit.DAYS);
            } catch (Exception e) {
                e.printStackTrace();
                // 消费异常,MQ自动重试
                return ConsumeConcurrentlyStatus.RECONSUME_LATER;
            }
        }
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }
}

7. 测试接口 Controller TestMqController.java

java 复制代码
package com.demo.controller;

import com.demo.entity.CoursePushMsg;
import com.demo.service.CoursePushProducer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;

@RestController
public class TestMqController {

    @Autowired
    private CoursePushProducer producer;

    /**
     * 测试发送定时推送消息
     * 访问示例:http://127.0.0.1:8080/send/push?courseId=1001&endTime=2026-06-25 18:30:00
     */
    @GetMapping("/send/push")
    public String sendPushMsg(@RequestParam Long courseId, @RequestParam String endTime) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime localDateTime = LocalDateTime.parse(endTime, formatter);
        long timeStamp = localDateTime.toInstant(ZoneOffset.of("+8")).toEpochMilli();

        CoursePushMsg msg = new CoursePushMsg();
        msg.setCourseId(courseId);
        msg.setTitle("课程已结束提醒");
        msg.setContent("本节课学习完成,请完成课后作业");
        msg.setEndTimeStamp(timeStamp);

        producer.sendCourseTimedMsg(msg);
        return "定时消息提交成功,到期自动推送学员";
    }
}

8. 启动类 RocketMqApplication.java

java 复制代码
package com.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class RocketMqApplication {
    public static void main(String[] args) {
        SpringApplication.run(RocketMqApplication.class, args);
    }

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

七、业务场景说明(课程修改结束时间解决方案)

  1. 新增课程:保存课程endTime到数据库,调用接口发送定时消息
  2. 修改课程结束时间:更新数据库endTime,重新调用发送接口生成新定时消息;旧消息到期消费时,时间不匹配直接丢弃,不会重复推送
  3. 取消课程推送:数据库增加pushStatus=2标记,消费时查询状态直接跳过推送逻辑

八、常见踩坑问题

  1. 连接报错ACL权限不足
    • 检查yml的access-key、secret-key和plain_acl.yml完全一致
    • 确认topicPerms配置了PUB|SUB权限
  2. 定时消息不生效
    • RocketMQ版本必须5.x以上才支持syncSendWithTimestamp任意时间戳定时
  3. 服务器外网无法连接
    • 防火墙放行9876、10911端口
    • broker启动时绑定公网IP:nohup sh bin/mqbroker -n 服务器公网IP:9876 &
  4. 重复推送
    • Redis幂等标记 + 数据库推送状态双重兜底
  5. 启动内存不足

九、总结

  1. 生产环境必须开启ACL设置账号密码,禁止裸奔无鉴权
  2. RocketMQ定时消息无法删除,采用「数据库存真实时间,消费过滤旧消息」方案适配课程修改时间场景
  3. SpringBoot starter自动携带ACL鉴权信息,无需手动编写RPC钩子,开发极简
  4. 启停顺序:先NameServer后Broker;停止顺序相反,重启前必须完整关闭进程