为了账号安全,请及时绑定邮箱和手机立即绑定

手写rocketMQ学习:入门教程与实践指南

标签:
Java 中间件
概述

本文介绍了如何手写RocketMQ学习,涵盖了RocketMQ的基本概念、安装步骤、消息发布与订阅流程以及常见问题与调试技巧,帮助读者全面了解和掌握RocketMQ的使用。

RocketMQ简介与安装
RocketMQ是什么

RocketMQ是由阿里巴巴开源的一款分布式消息中间件,它基于高可用设计,旨在为大规模分布式系统提供可靠的消息发布与订阅服务。RocketMQ具有高可用性、高并发性、高可扩展性、高吞吐量以及消息顺序性等特点,使其在各种应用场景中都能发挥出色表现。

RocketMQ的优势
  1. 高可用性:RocketMQ采用主从复制、多副本机制,确保在部分节点故障的情况下仍能保证服务的连续性。
  2. 高并发处理能力:RocketMQ可支持每秒数百万的消息吞吐量,适用于大数据量下的消息处理场景。
  3. 丰富的消息模型:RocketMQ支持集群模式和广播模式两种消息订阅模式,满足不同的业务需求。
  4. 消息顺序处理:RocketMQ支持消息按照特定的顺序进行处理,保证消息处理的顺序性。
  5. 事务消息:RocketMQ支持事务消息,可以确保消息的可靠传递。
  6. 多语言支持:RocketMQ支持多种语言的客户端,如Java、C++、Python等。
安装RocketMQ环境准备

安装RocketMQ之前,需要准备以下环境:

  • 操作系统:建议使用Linux或MacOS,Windows环境可参考官方文档。
  • Java JDK:RocketMQ运行需要Java环境,建议使用Java8及以上版本。
  • Zookeeper:RocketMQ依赖Zookeeper服务来管理和维护集群状态。
  • Maven:RocketMQ的依赖管理工具是Maven,需要在本地安装。
安装RocketMQ步骤详解

安装JDK

  1. 下载Java JDK安装包,并安装到指定路径,比如/usr/local/java
  2. 设置环境变量。编辑/etc/profile文件,添加以下内容:
    export JAVA_HOME=/usr/local/java
    export PATH=$JAVA_HOME/bin:$PATH
    export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
  3. 使环境变量生效:
    source /etc/profile

安装Zookeeper

  1. 下载Zookeeper安装包并解压到指定目录。
  2. 配置Zookeeper。编辑Zookeeper的配置文件conf/zoo.cfg,设置数据存储目录和客户端端口。
  3. 启动Zookeeper服务:
    cd /path/to/zookeeper
    bin/zkServer.sh start

下载RocketMQ

  1. 下载RocketMQ源码包,解压到指定目录。
  2. 进入RocketMQ的安装目录,运行启动脚本:
    cd /path/to/rocketmq
    ./bin/mqbroker -n localhost:9876 &
    ./bin/mqnamesrv &

验证安装

启动完成后,可以通过发送和接收消息来验证RocketMQ是否安装成功。可以使用命令行工具或编写简单的Java代码进行测试。

RocketMQ的核心概念
消息模型

RocketMQ支持两种消息模型:发布-订阅模型和点对点模型。

  • 发布-订阅模型:发布者向特定主题(Topic)发送消息,多个订阅者可以订阅该主题,接收发布者发送的消息。
  • 点对点模型:消息发送到队列,只被消费一次。
命名空间(Namespace)

命名空间是一个逻辑隔离的区域,用于管理一组相关的Topic。命名空间可以将多个应用实例的Topic隔离,避免冲突。RocketMQ通过命名空间来区分不同的消息实体。

Topic与Tag
  • Topic:消息的主题,是消息的一个逻辑分类。Topic是发布-订阅模型中的核心概念,发布者发布消息到特定的Topic,订阅者订阅该Topic以接收消息。
  • Tag:消息的标签,用于进一步细分消息。Tag对消息进行分类,帮助消费者选择特定的消息进行处理。
消费者(Consumer)与生产者(Producer)
  • 生产者(Producer):负责向RocketMQ发送消息的程序。
  • 消费者(Consumer):负责从RocketMQ接收消息并处理消息的程序。
手写RocketMQ消息发布与订阅
消息发送的基本流程
  • 创建生产者实例。
  • 设置生产者属性,如是否开启消息顺序发送。
  • 启动生产者。
  • 发送消息。
  • 关闭生产者。

消息发送的Java代码示例

import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;

public class SimpleProducer {
    public static void main(String[] args) throws MQClientException, InterruptedException {
        // 创建生产者实例
        DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName");
        // 设置NameServer地址
        producer.setNamesrvAddr("localhost:9876");
        // 启动生产者
        producer.start();

        // 构造消息
        Message msg = new Message("TopicTest", // topic
                "TagA", // tag
                ("Hello RocketMQ").getBytes(RemotingHelper.DEFAULT_CHARSET)); // body

        // 发送消息
        SendResult sendResult = producer.send(msg);
        System.out.printf("%s%n", sendResult);

        // 关闭生产者
        producer.shutdown();
    }
}
消息订阅的基本流程
  • 创建消费者实例。
  • 设置消费者属性,如是否开启消费消息的顺序性。
  • 启动消费者。
  • 订阅消息。
  • 消费消息。
  • 关闭消费者。

消息订阅的Java代码示例

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.MessageExt;

public class SimpleConsumer {
    public static void main(String[] args) throws InterruptedException, MQClientException {
        // 创建消费者实例
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ConsumerGroupName");
        // 设置NameServer地址
        consumer.setNamesrvAddr("localhost:9876");
        // 设置从何处开始消费
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        // 订阅Topic
        consumer.subscribe("TopicTest", "*");

        // 设置消息监听器
        consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
            for (MessageExt msg : msgs) {
                System.out.printf("Receive New Messages: %s %n", new String(msg.getBody()));
            }
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        });

        // 启动消费者
        consumer.start();

        System.out.println("Consumer Started.");
    }
}
编写简单的发布与订阅代码

结合上述示例,可以编写简单的消息发布与订阅代码。

发布代码

import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;

public class SimpleProducer {
    public static void main(String[] args) throws MQClientException, InterruptedException {
        DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName");
        producer.setNamesrvAddr("localhost:9876");
        producer.start();

        for (int i = 0; i < 100; i++) {
            Message msg = new Message("TopicTest", "TagA", ("Hello RocketMQ " + i).getBytes());
            SendResult sendResult = producer.send(msg);
            System.out.printf("%s%n", sendResult);
        }

        producer.shutdown();
    }
}

订阅代码

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;

public class SimpleConsumer {
    public static void main(String[] args) throws InterruptedException, MQClientException {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ConsumerGroupName");
        consumer.setNamesrvAddr("localhost:9876");
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        consumer.subscribe("TopicTest", "*");

        consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
            for (MessageExt msg : msgs) {
                System.out.printf("Received message: %s%n", new String(msg.getBody()));
            }
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        });

        consumer.start();

        System.out.println("Consumer Started.");
    }
}
消息订阅模式
广播模式

广播模式下,所有订阅者都能接收到发布者发送的所有消息,适用于需要所有订阅者都处理消息的场景。

广播模式代码实现

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;

public class BroadcastConsumer {
    public static void main(String[] args) throws InterruptedException, MQClientException {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ConsumerGroupName");
        consumer.setNamesrvAddr("localhost:9876");
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        consumer.subscribe("TopicTest", "*");

        consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
            for (MessageExt msg : msgs) {
                System.out.printf("Received message: %s%n", new String(msg.getBody()));
            }
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        });

        consumer.start();

        System.out.println("Broadcast Consumer Started.");
    }
}
集群模式

集群模式下,每个订阅者只会接收到消息的一部分,适用于需要负载均衡的场景。

集群模式代码实现

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;

public class ClusterConsumer {
    public static void main(String[] args) throws InterruptedException, MQClientException {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ConsumerGroupName");
        consumer.setNamesrvAddr("localhost:9876");
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        consumer.subscribe("TopicTest", "*");

        consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
            for (MessageExt msg : msgs) {
                System.out.printf("Received message: %s%n", new String(msg.getBody()));
            }
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        });

        consumer.setMessageModel(MessageModel.CLUSTERING);
        consumer.start();

        System.out.println("Cluster Consumer Started.");
    }
}
两种模式的代码实现与差异

在广播模式下,每个订阅者都会接收到所有消息,而在集群模式下,每个订阅者只会接收到一部分消息。集群模式下需要设置消息模型为集群模式。

消息消费与处理
消息消费的基本流程
  • 创建消费者实例。
  • 设置消费者属性,如是否开启消费消息的顺序性。
  • 启动消费者。
  • 订阅消息。
  • 消费消息。
  • 关闭消费者。

消费者消费消息的Java代码示例

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;

public class MessageConsumer {
    public static void main(String[] args) throws InterruptedException, MQClientException {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ConsumerGroupName");
        consumer.setNamesrvAddr("localhost:9876");
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        consumer.subscribe("TopicTest", "*");

        consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
            for (MessageExt msg : msgs) {
                System.out.printf("Received message: %s%n", new String(msg.getBody()));
            }
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        });

        consumer.start();

        System.out.println("Consumer Started.");
    }
}
消费者处理消息的方式

RocketMQ提供了多种处理消息的方式,包括顺序消费、并行消费等。

  • 顺序消费:消息按照发送顺序进行处理。
  • 并行消费:消息可以同时处理多个,提升消费性能。

顺序消费代码示例

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly;
import org.apache.rocketmq.common.message.MessageExt;

public class OrderlyConsumer {
    public static void main(String[] args) throws InterruptedException, MQClientException {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("OrderlyConsumerGroupName");
        consumer.setNamesrvAddr("localhost:9876");
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        consumer.subscribe("TopicTest", "*");

        consumer.registerMessageListener((MessageListenerOrderly) (msgs, context) -> {
            for (MessageExt msg : msgs) {
                System.out.printf("Received message: %s%n", new String(msg.getBody()));
            }
            return ConsumeOrderlyContext.CONSUME_ORDERLY_OK;
        });

        consumer.setMessageModel(MessageModel.BROADCASTING);
        consumer.setMessageListenerOrderly(true);
        consumer.start();

        System.out.println("Orderly Consumer Started.");
    }
}

并行消费代码示例

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;

public class ConcurrentConsumer {
    public static void main(String[] args) throws InterruptedException, MQClientException {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ConcurrentConsumerGroupName");
        consumer.setNamesrvAddr("localhost:9876");
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        consumer.subscribe("TopicTest", "*");

        consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
            for (MessageExt msg : msgs) {
                System.out.printf("Received message: %s%n", new String(msg.getBody()));
            }
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        });

        consumer.start();

        System.out.println("Concurrent Consumer Started.");
    }
}
如何保证消息不丢失

消息不丢失是RocketMQ中的一个重要特性。RocketMQ通过以下方式保证消息不丢失:

  • 异步复制:消息发送到主节点后,主节点会将消息异步复制到多个从节点,保证消息的冗余备份。
  • 持久化:消息发送后会被持久化到磁盘,即使在内存中丢失,也可以从磁盘中恢复。
  • 主从切换:主节点发生故障时,从节点可以自动切换为主节点,保证服务的连续性。

代码示例

可以通过设置消息的持久化属性来确保消息的持久化存储。

import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.common.message.Message;

public class PersistentProducer {
    public static void main(String[] args) throws MQClientException, InterruptedException {
        DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName");
        producer.setNamesrvAddr("localhost:9876");
        producer.start();

        for (int i = 0; i < 100; i++) {
            Message msg = new Message("TopicTest", "TagA", ("Hello RocketMQ " + i).getBytes());
            // 设置消息持久化属性
            msg.setPersistent(true);
            producer.send(msg);
        }

        producer.shutdown();
    }
}
手写RocketMQ的常见问题与调试
常见错误与解决方法
  • ConsumerNotRegisteredException:消费者未注册到NameServer。检查NameServer地址是否正确。
  • MessageSessionFactoryException:消息会话工厂异常。可能是网络连接问题,检查网络连接。
  • IllegalStateException:状态非法。可能是消费者未启动,检查消费者是否正确启动。
  • UnsupportedEncodingException:编码不支持。确保使用的字符编码是正确的。
  • TopicNotFound:主题不存在。检查Topic名称是否正确。
日志查看与调试技巧

RocketMQ的日志默认输出到logs目录下,可以通过日志来定位问题。

日志查看示例

cd /path/to/rocketmq/logs
tail -f broker.log

日志分析

  • 查看错误日志,找到异常信息。
  • 分析日志中的堆栈信息,定位问题。
  • 使用日志工具进行日志分析,如使用Logstash和Elasticsearch进行日志聚合和分析。
性能优化与调优建议
  • 减少消息大小:减少消息大小可以提升消息发送和接收的性能。
  • 减少网络延迟:优化网络环境,减少网络延迟,提升消息传输的效率。
  • 调整线程池大小:根据实际业务场景调整线程池大小,提升并发处理能力。
  • 开启消息压缩:开启消息压缩功能,减少传输的数据量,提升传输效率。
  • 使用更高效的编码方式:使用更高效的编码方式,减少消息的传输时间和存储空间。

代码示例

可以通过设置线程池大小来优化性能。

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;

public class PerformanceConsumer {
    public static void main(String[] args) throws InterruptedException, MQClientException {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ConsumerGroupName");
        consumer.setNamesrvAddr("localhost:9876");
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        consumer.subscribe("TopicTest", "*");

        // 设置线程池大小
        consumer.setConsumeMessageBatchMaxSize(100);
        consumer.setConsumeThreadMin(8);
        consumer.setConsumeThreadMax(10);

        consumer.start();

        System.out.println("Performance Consumer Started.");
    }
}

消息压缩代码示例


import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.MessageQueueSelector;
import org.apache.rocketmq.common.message.Message;

public class CompressProducer {
    public static void main(String[] args) throws MQClientException, InterruptedException {
        DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName");
        producer.setNamesrvAddr("localhost:9876");
        producer.start();

        for (int i = 0; i < 100; i++) {
            Message msg = new Message("TopicTest", "TagA", ("Hello RocketMQ " + i).getBytes());
            // 设置消息压缩
            msg.setCompress(true);
            producer.send(msg);
        }

        producer.shutdown();
    }
}
``

通过以上示例,可以更好地理解和使用RocketMQ,提升消息系统的可靠性和性能。
点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消