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
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打开页面
按照上图我们做一个双主双从、异步写盘的部署,配置如下:
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
启动两个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 &
export NAMESRV_ADDR=localhost:9876
#生产者测试,在bin目录下执行
./tools.sh org.apache.rocketmq.example.quickstart.Producer
#消费者测试
./tools.sh org.apache.rocketmq.example.quickstart.Consumer
<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
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();
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");
}
}
在 RocketMQ 中,消息的发送状态可以分为以下几种:
当消息发送成功时,会返回 SEND_OK 状态,表示消息已经被成功写入到 Broker 中。如果消息写入磁盘或者同步到从节点超时,会返回 FLUSH_DISK_TIMEOUT 或者 FLUSH_SLAVE_TIMEOUT 状态。如果从节点不可用,则会返回SLAVE_NOT_AVAILABLE 状态。如果出现未知错误,则会返回 UNKNOWN_ERROR 状态。
RocketMQ生产者在发送消息之后,会等待Broker返回确认消息(ACK)。确认消息通常包含以下内容:
总之,确认消息告知生产者消息的发送状态、存储位置和时间戳,以及一个唯一的消息ID。这些信息对于生产者来说非常重要,因为它们可以用来跟踪消息的状态,并确保消息被正确处理。
如果没有收到Broker的ack,producer就会自动重试,默认重试两次,重试次数也可以手动设置
// 同步发送设置重试次数为5次
producer.setRetryTimesWhenSendFailed(5);
// 异步发送设置重试次数为5次
producer.setRetryTimesWhenSendAsyncFailed(5);
因此,同步发送时,要注意处理好消息状态,异步发送时,则注意在回调中做好处理。
broker存储阶段的可靠性主要依靠主从同步机制和消息刷盘机制保证。
a. 主从同步机制:当我们有一个主节点master和一个从节点slave后,节点间依靠同步机制进行传输,分为同步复制和异步复制两种
b. 刷盘机制:当然仅仅靠主从同步并不行,因为broker运行在内存中,如果broker宕机,那么存储在内存中的数据将丢失,所以需要持久化到硬盘中,同样分为两种,分别是同步刷盘和异步刷盘
以上两种需要组合一下,最常用的是主从同步复制和异步刷盘。这两种结合可以保证消息的高可靠性和低延迟性
消费者在保证消息成功消费的关键是何时确认消息,一些关键业务场景,应该在执行完所有消费业务逻辑之后,再发送消费确认。这样,就保证了消费者的一定消费成功。
但是,如果一条消息被发送两次呢?我们还要消费两次吗?这就是消费的幂等性问题。对于消息的幂等性,我们可以采用两种方式处理:业务幂等和消息去重。