RocketMQ的安装和使用

一、安装

1. RocketMQ安装

a. 确保自己已经安装JDK环境,如果未安装,请参考:Linux环境安装JDK

b. 下载安装包,官网地址,也可以自行修改版本号,本文为4.9.3版本,上传安装包到服务器上,上传位置看个人习惯或公司要求即可

c. 编辑环境变量,vi /etc/profile,添加RocketMq的环境变量

export ROCKETMQ_HOME=/tools/rocketmq/rocketmq-4.9.3
export PATH=$ROCKETMQ_HOME/bin:$PATH

保存后记得重载,source /etc/profile

d. 启动nameserver和broker,nameserver类似注册中心,broker类似一个服务注册在其中,所以应该先启动nameserver,nohup sh mqnamesrv &;后启动broker,nohup sh mqbroker -n localhost:9876 &,注意,需要确定启动脚本中JVM参数中的堆内存部分自己机器是否满足

e. 关闭时请先关闭broker,sh bin/mqshutdown broker;后关闭nameserver,sh bin/mqshutdown namesrv

2. 控制台(页面管理)安装

a. 是一个开源项目,项目地址,拉取到本地以后打包即可,mvn clean package -Dmaven.test.skip=true
b. 上传jar包到服务器某位置,并启动,nohup java -jar rocketmq-dashboard-1.0.1-SNAPSHOT.jar --server.port=8881 --rocketmq.config.namesrvAddr=localhost:9876 &
c. 访问ip:8881打开页面
在这里插入图片描述

二、部署

1. 配置文件修改

在这里插入图片描述
按照上图我们做一个双主双从、异步写盘的部署,配置如下:
a. nameserver配置,namesrv.properties文件,指定启动端口

namesrv1.properties
listenPort=9876

namesrv2.properties
listenPort=9875

b. broker配置,broker1-m.properties、broker1-s.properties、broker2-m.properties、broker2-s.properties文件

#broker1-m.properties
#ip
brokerIP1=192.168.136.128
#集群名字
brokerClusterName=DefaultCluster
#broker名字,
#例如:broker1-m和broker1-s此处写broker1,broker2-m和broker2-s此处写broker2
brokerName=broker1
#0表示Master,>0表示Slave
#例如:broker1-m写0,broker1-s写1
brokerId=0
#删除文件时间点,默认凌晨4点
deleteWhen=04
#文件保留时间,默认48小时
fileReservedTime=48
#broker的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE 从
brokerRole=SYNC_MASTER
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=ASYNC_FLUSH
#broker对外服务的监听端口
listenPort=10911
#存储路径
storePathRootDir=/home/rocketmq/store/broker1-m
#nameServer地址,分号分割
namesrvAddr=192.168.136.128:9876;192.168.136.128:9875

2. 启动

启动两个nameserver,启动四个broker,启动控制台页面

#nameserver
nohup sh mqnamesrv -c /home/rocketmq/rocketmq-4.9.3/conf/namesrv1.properties > /home/rocketmq/rocketmq-4.9.3/log/namesrv1.log 2>&1 &
nohup sh mqnamesrv -c /home/rocketmq/rocketmq-4.9.3/conf/namesrv2.properties > /home/rocketmq/rocketmq-4.9.3/log/namesrv2.log 2>&1 &
#broker1
nohup sh mqbroker -c /home/rocketmq/rocketmq-4.9.3/conf/broker1-m.properties > /home/rocketmq/rocketmq-4.9.3/log/broker1-m.log 2>&1 &
nohup sh mqbroker -c /home/rocketmq/rocketmq-4.9.3/conf/broker1-s.properties > /home/rocketmq/rocketmq-4.9.3/log/broker1-s.log 2>&1 &
#broker2
nohup sh mqbroker -c /home/rocketmq/rocketmq-4.9.3/conf/broker2-m.properties > /home/rocketmq/rocketmq-4.9.3/log/broker2-m.log 2>&1 &
nohup sh mqbroker -c /home/rocketmq/rocketmq-4.9.3/conf/broker2-s.properties > /home/rocketmq/rocketmq-4.9.3/log/broker2-s.log 2>&1 &
#控制台
nohup java -jar rocketmq-dashboard-1.0.1-SNAPSHOT.jar --server.port=8881 --rocketmq.config.namesrvAddr=localhost:9876,localhost:9875 &

在这里插入图片描述

3. 服务器上测试

export NAMESRV_ADDR=localhost:9876
#生产者测试,在bin目录下执行
./tools.sh org.apache.rocketmq.example.quickstart.Producer

在这里插入图片描述

#消费者测试
./tools.sh org.apache.rocketmq.example.quickstart.Consumer

在这里插入图片描述

三、springboot使用

1. 依赖

<dependency>
      <groupId>org.apache.rocketmq</groupId>
      <artifactId>rocketmq-client</artifactId>
      <version>4.2.0</version>
</dependency>
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.1.1</version>
</dependency>

配置文件添加配置:mq.addr=192.168.1.1:9876

2. 生产者

DefaultMQProducer producer = new DefaultMQProducer("producer_group");
producer.setNamesrvAddr("localhost:9876");
producer.setSendMsgTimeout(10000);
producer.start();

实际使用过程可以设计成单例的工具类,提供出该producer

//生成消息
Message msg = new Message("testTopic", "Hello RocketMQ".getBytes());
//消息可以设置延时属性,比如用来做一些订单超时的业务场景,
//目前免费的版本支支持18个固定时间,4代表30s
//msg.setDelayTimeLevel(4);

//发送同步消息
SendResult sendResult = producer.send(msg);
//发送异步消息
producer.send(msg, new SendCallback() {
   @Override
   public void onSuccess(SendResult sendResult) {
       System.out.println("发送成功:" + sendResult.getMsgId());
   }

   @Override
   public void onException(Throwable throwable) {
       System.out.println("发送失败:" + throwable.getMessage());
   }
});
//发送单向消息
producer.sendOneway(msg);

//最后别忘了关闭实例
producer.shutdown();

3. 消费者

a. pull方式

import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer;
import org.apache.rocketmq.client.consumer.PullResult;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.Set;
/**
	在下面的示例中,创建了一个名为 example_group 的消费者实例,
	并设置了nameserver地址。然后订阅了一个名为 example_topic 的Topic,
	使用Pull方式消费消息。获取Topic的MessageQueue,
	然后循环从每个MessageQueue中Pull消息。在处理完消息后,需要保存消费进度。
**/
public class RocketMQPullConsumer {

    public static void main(String[] args) throws MQClientException {
        DefaultMQPullConsumer consumer = new DefaultMQPullConsumer("example_group");
        consumer.setNamesrvAddr("localhost:9876");
        consumer.start();
        Set<MessageQueue> messageQueues = consumer.fetchSubscribeMessageQueues("example_topic");
        for (MessageQueue messageQueue : messageQueues) {
            while (true) {
                try {
                    PullResult pullResult = consumer.pull(messageQueue, "*", getMessageQueueOffset(messageQueue), 32);
                    putMessageQueueOffset(messageQueue, pullResult.getNextBeginOffset());
                    switch (pullResult.getPullStatus()) {
                        case FOUND:
                            for (MessageExt messageExt : pullResult.getMsgFoundList()) {
                                System.out.println("Received message: " + new String(messageExt.getBody()));
                            }
                            break;
                        case NO_MATCHED_MSG:
                            break;
                        case NO_NEW_MSG:
                            break;
                        case OFFSET_ILLEGAL:
                            break;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private static long getMessageQueueOffset(MessageQueue messageQueue) {
        // TODO 获取指定队列的消费进度
        return 0;
    }

    private static void putMessageQueueOffset(MessageQueue messageQueue, long offset) {
        // TODO 保存指定队列的消费进度
    }
}

b. push方式(简单常用)

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

import java.util.List;
/**
	在下面的示例中,创建了一个名为 example_group 的消费者实例,
	并设置了nameserver地址。然后订阅了一个名为 example_topic 的Topic,
	Tag为 example_tag。接下来注册了一个消息监听器,
	在 consumeMessage 方法中处理接收到的消息,并返回消费状态。
	最后启动了消费者实例。
**/
public class RocketMQPushConsumer {

    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("example_group");
        consumer.setNamesrvAddr("localhost:9876");
        consumer.subscribe("example_topic", MessageSelector.byTag("example_tag"));
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                for (MessageExt msg : msgs) {
                    System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), new String(msg.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        consumer.start();
        System.out.printf("Consumer Started.%n");
    }
}

四、消息可靠性

1. 消息状态

在 RocketMQ 中,消息的发送状态可以分为以下几种:

  • SEND_OK:表示消息发送成功。
  • FLUSH_DISK_TIMEOUT:表示消息写入磁盘超时。
  • FLUSH_SLAVE_TIMEOUT:表示消息同步到从节点超时。
  • SLAVE_NOT_AVAILABLE:表示从节点不可用。
  • UNKNOWN_ERROR:表示未知错误。

当消息发送成功时,会返回 SEND_OK 状态,表示消息已经被成功写入到 Broker 中。如果消息写入磁盘或者同步到从节点超时,会返回 FLUSH_DISK_TIMEOUT 或者 FLUSH_SLAVE_TIMEOUT 状态。如果从节点不可用,则会返回SLAVE_NOT_AVAILABLE 状态。如果出现未知错误,则会返回 UNKNOWN_ERROR 状态。

2. 生产者

RocketMQ生产者在发送消息之后,会等待Broker返回确认消息(ACK)。确认消息通常包含以下内容:

  • 消息发送状态:确认消息会告知生产者消息是否已经成功发送到了Broker。如果发送成功,确认消息会包含一个唯一的消息ID,生产者可以使用这个ID来查询消息状态。
  • 消息存储位置:确认消息还会告诉生产者消息存储在Broker的哪个主题和队列中,这对于生产者来说非常重要,因为它需要确保消息被发送到了正确的地方。
  • 消息发送时间戳:确认消息还会包含消息发送的时间戳,这对于生产者来说也非常重要,因为它需要知道消息发送的时间,以便进行一些统计和监控。

总之,确认消息告知生产者消息的发送状态、存储位置和时间戳,以及一个唯一的消息ID。这些信息对于生产者来说非常重要,因为它们可以用来跟踪消息的状态,并确保消息被正确处理。
如果没有收到Broker的ack,producer就会自动重试,默认重试两次,重试次数也可以手动设置

// 同步发送设置重试次数为5次
producer.setRetryTimesWhenSendFailed(5);
// 异步发送设置重试次数为5次
producer.setRetryTimesWhenSendAsyncFailed(5);

因此,同步发送时,要注意处理好消息状态,异步发送时,则注意在回调中做好处理。

3. broker

broker存储阶段的可靠性主要依靠主从同步机制和消息刷盘机制保证。

a. 主从同步机制:当我们有一个主节点master和一个从节点slave后,节点间依靠同步机制进行传输,分为同步复制和异步复制两种

  • 同步复制:master和slave均写成功,才返回客户端成功。maste挂了以后可以保证数据不丢失,但是同步复制会增加数据写入延迟,降低吞吐量
  • 异步复制:master写成功,返回客户端成功。拥有较低的延迟和较高的吞吐量,但是当master出现故障后,有可能造成数据丢失

b. 刷盘机制:当然仅仅靠主从同步并不行,因为broker运行在内存中,如果broker宕机,那么存储在内存中的数据将丢失,所以需要持久化到硬盘中,同样分为两种,分别是同步刷盘和异步刷盘

  • 同步刷盘:消息写入内存的PAGECACHE后,立刻通知刷盘线程刷盘,然后等待刷盘完成,刷盘线程执行完成后唤醒等待的线程,给应用返回消息写成功的状态。吞吐量低,但不会造成消息丢失
  • 异步刷盘:消息被写入内存的PAGECACHE,返回写成功状态,当内存里的消息量积累到一定程度时,统一触发写磁盘操作,快速写入 。吞吐量高,当磁盘损坏时,会丢失消息

以上两种需要组合一下,最常用的是主从同步复制和异步刷盘。这两种结合可以保证消息的高可靠性和低延迟性

4. 消费者

消费者在保证消息成功消费的关键是何时确认消息,一些关键业务场景,应该在执行完所有消费业务逻辑之后,再发送消费确认。这样,就保证了消费者的一定消费成功。

但是,如果一条消息被发送两次呢?我们还要消费两次吗?这就是消费的幂等性问题。对于消息的幂等性,我们可以采用两种方式处理:业务幂等和消息去重。

  • 业务幂等:保证消费逻辑的幂等性,也就是多次调用和一次调用的效果是一样的。这样一来,不管消息消费多少次,对业务都没有影响。
  • 消息去重:对重复的消息就不再消费了。这种方法,需要保证每条消息都有一个惟一的编号,通常是业务相关的,比如订单号,消费的记录需要落库,而且需要保证和消息确认这一步的原子性。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值