1.介绍
是一款阿里开源的一款消息中间件。
Apache RocketMQ 自诞生以来,因其架构简单、业务功能丰富、具备极强可扩展性等特点被众多企业开发者以及云厂商广泛采用。历经十余年的大规模场景打磨,RocketMQ 已经成为业内共识的金融级可靠业务消息首选方案,被广泛应用于互联网、大数据、移动互联网、物联网等领域的业务场景。
2.框架
消息生产
Apache RocketMQ 中用于产生消息的运行实体,一般集成于业务调用链路的上游。生产者是轻量级匿名无身份的。
消息存储
- 主题(Topic):Apache RocketMQ 消息传输和存储的分组容器,主题内部由多个队列组成,消息的存储和水平扩展实际是通过主题内的队列实现的。
- 队列(MessageQueue):Apache RocketMQ 消息传输和存储的实际单元容器,类比于其他消息队列中的分区。 Apache RocketMQ 通过流式特性的无限队列结构来存储消息,消息在队列内具备顺序性存储特征。
- 消息(Message):Apache RocketMQ 的最小传输单元。消息具备不可变性,在初始化发送和完成存储后即不可变。
消息消费
- 消费者分组(ConsumerGroup):Apache RocketMQ 发布订阅模型中定义的独立的消费身份分组,用于统一管理底层运行的多个消费者(Consumer)。同一个消费组的多个消费者必须保持消费逻辑和配置一致,共同分担该消费组订阅的消息,实现消费能力的水平扩展。
- 消费者(Consumer):Apache RocketMQ 消费消息的运行实体,一般集成在业务调用链路的下游。消费者必须被指定到某一个消费组中。
- 订阅关系(Subscription):Apache RocketMQ 发布订阅模型中消息过滤、重试、消费进度的规则配置。订阅关系以消费组粒度进行管理,消费组通过定义订阅关系控制指定消费组下的消费者如何实现消息过滤、消费重试及消费进度恢复等。Apache RocketMQ 的订阅关系除过滤表达式之外都是持久化的,即服务端重启或请求断开,订阅关系依然保留。
3.安装与简单使用
3.1安装
在docker下实验,注意,若出现拉镜像拉不下来时请用加速,本实验是用的阿里镜像加速,具体操作见链接
3.1.1创建NameServer
#拉镜像
docker pull rocketmqinc/rocketmq
#创建数据存储路径
mkdir -p /docker/rocketmq/data/namesrv/logs /docker/rocketmq/data/namesrv/store
#构建容器
docker run -d \
--restart=always \
--name rmqnamesrv \
-p 9876:9876 \
-v /docker/rocketmq/data/namesrv/logs:/root/logs \
-v /docker/rocketmq/data/namesrv/store:/root/store \
-e "MAX_POSSIBLE_HEAP=100000000" \
rocketmqinc/rocketmq \
sh mqnamesrv
-d 以守护进程的方式启动
-restart=always | docker重启时候容器自动重启
-name rmqnamesrv | 把容器的名字设置为rmqnamesrv
-p 9876:9876 | 把容器内的端口9876挂载到宿主机9876上面
-v /docker/rocketmq/data/namesrv/logs:/root/logs | 把容器内的/root/logs日志目录挂载到宿主机的 /docker/rocketmq/data/namesrv/logs目录
-v /docker/rocketmq/data/namesrv/store:/root/store | 把容器内的/root/store数据存储目录挂载到宿主机的 /docker/rocketmq/data/namesrv目录
-e “MAX_POSSIBLE_HEAP=100000000” | 设置容器的最大堆内存为100000000
rocketmqinc/rocketmq | 使用的镜像名称
sh mqnamesrv | 启动namesrv服务
3.1.2创建broker节点
#创建broker数据存储路径
mkdir -p /docker/rocketmq/data/broker/logs /docker/rocketmq/data/broker/store /docker/rocketmq/conf
vi /docker/rocketmq/conf/broker.conf
配置
# 所属集群名称,如果节点较多可以配置多个
brokerClusterName = DefaultCluster
#broker名称,master和slave使用相同的名称,表明他们的主从关系
brokerName = broker-a
#0表示Master,大于0表示不同的slave
brokerId = 0
#表示几点做消息删除动作,默认是凌晨4点
deleteWhen = 04
#在磁盘上保留消息的时长,单位是小时
fileReservedTime = 48
#有三个值:SYNC_MASTER,ASYNC_MASTER,SLAVE;同步和异步表示Master和Slave之间同步数据的机制;
brokerRole = ASYNC_MASTER
#刷盘策略,取值为:ASYNC_FLUSH,SYNC_FLUSH表示同步刷盘和异步刷盘;SYNC_FLUSH消息写入磁盘后才返回成功状态,ASYNC_FLUSH不需要;
flushDiskType = ASYNC_FLUSH
# 设置broker节点所在服务器的ip地址
brokerIP1 = 192.168.52.136
# 磁盘使用达到95%之后,生产者再写入消息会报错 CODE: 14 DESC: service not available now, maybe disk full
diskMaxUsedSpaceRatio=95
构建容器
docker run -d \
--restart=always \
--name rmqbroker \
--link rmqnamesrv:namesrv \
-p 10911:10911 \
-p 10909:10909 \
-v /docker/rocketmq/data/broker/logs:/root/logs \
-v /docker/rocketmq/data/broker/store:/root/store \
-v /docker/rocketmq/conf/broker.conf:/opt/rocketmq-4.4.0/conf/broker.conf \
-e "NAMESRV_ADDR=namesrv:9876" \
-e "MAX_POSSIBLE_HEAP=200000000" \
rocketmqinc/rocketmq \
sh mqbroker -c /opt/rocketmq-4.4.0/conf/broker.conf
参数 说明
-d 以守护进程的方式启动
–restart=always docker重启时候镜像自动重启
–name rmqbroker 把容器的名字设置为rmqbroker
–link rmqnamesrv:namesrv 和rmqnamesrv容器通信
-p 10911:10911 把容器的非vip通道端口挂载到宿主机
-p 10909:10909 把容器的vip通道端口挂载到宿主机
-e “NAMESRV_ADDR=namesrv:9876” 指定namesrv的地址为本机namesrv的ip地址:9876
-e “MAX_POSSIBLE_HEAP=200000000” rocketmqinc/rocketmq sh mqbroker 指定broker服务的最大堆内存
sh mqbroker -c /opt/rocketmq-4.4.0/conf/broker.conf 指定配置文件启动broker节点
3.1.3创建rocketmq-console容器
docker pull pangliang/rocketmq-console-ng
构建容器
docker run -d \
--restart=always \
--name rmqadmin \
-e "JAVA_OPTS=-Drocketmq.namesrv.addr=部署nameserver的机器ip:9876 \
-Dcom.rocketmq.sendMessageWithVIPChannel=false" \
-p 9999:8080 \
pangliang/rocketmq-console-ng
-d 以守护进程的方式启动
–restart=always docker重启时候镜像自动重启
–name rmqadmin 把容器的名字设置为rmqadmin
-e "JAVA_OPTS=-Drocketmq.namesrv.addr=192.168.52.136:9876 设置namesrv服务的ip地址
-Dcom.rocketmq.sendMessageWithVIPChannel=false" 不使用vip通道发送消息
–p 9999:8080 把容器内的端口8080挂载到宿主机上的9999端口
3.1.4使用页面
使用前请关闭防火墙
网页访问控制台:http://491.235.142.29:9999/#/
3.2使用(java使用)
3.2.1使用前配置
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client-java</artifactId>
<version>5.0.5</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.6.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
创建topic
3.2.2消息类型
1.普通消息
- 同步发送(发消息后要同步收到消息入队成功消息后才算完成)
- 异步消息(发消息后要异步步收到消息入队成功消息后才算完成)
- 单向发送(发送后不用确认)
2.顺序消息
3.定时/延时消息
4.事物消息
3.2.3消息发送接收
消息只消费一次
3.2.3.1普通消息
1.发送
import org.apache.rocketmq.client.exception.MQBrokerException;
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;
import org.apache.rocketmq.remoting.exception.RemotingException;
/**
* @Author: jiana
* @Date: 2024/3/26 14 : 14
* @Description:
**/
public class NormalSendMessage {
public static void main(String[] args) throws MQClientException {
DefaultMQProducer producer = new DefaultMQProducer("java_test");
producer.setNamesrvAddr("491.235.142.229:9876");
// 启动生产者实例
producer.start();
//普通消息发送。
Message message = new Message("Normal_a", "tag_name", "Hello RocketMQ".getBytes());
try {
//发送消息,需要关注发送结果,并捕获失败等异常。
SendResult sendResult = producer.send(message);
System.out.println(sendResult.getMsgId());
} catch (MQBrokerException e) {
e.printStackTrace();
} catch (RemotingException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 关闭生产者实例
producer.shutdown();
}
}
}
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.exception.RemotingException;
/**
* @Author: jiana
* @Date: 2024/3/26 14 : 14
* @Description:
**/
public class NormalSendMessage {
public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException {
//前两步不变
DefaultMQProducer producer = new DefaultMQProducer("java_test");
producer.setNamesrvAddr("491.235.142.29:9876");
//需要将producer 对象启动
producer.start();
//编辑消息
String body="{userName:'异步发送'}";
//创建消息
Message message = new Message("Normal_a","tag_sys",body.getBytes());
//发送异步消息 发送结果通过callback返回给客户端
producer.send(message, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
//成功回调 同时关闭链接
System.out.println("回调成功");
producer.shutdown();
}
@Override
public void onException(Throwable throwable) {
//失败回调 同时关闭链接
System.out.println("回调失败");
producer.shutdown();
}
});
}
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.exception.RemotingException;
/**
* @Author: jiana
* @Date: 2024/3/26 14 : 14
* @Description:
**/
public class NormalSendMessage {
public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException {
//前两步不变
DefaultMQProducer producer = new DefaultMQProducer("java_test");
producer.setNamesrvAddr("491.235.142.29:9876");
//需要将producer对象启动起来
producer.start();
//编辑信息
String body="userName:'单向',hobby:'Jerry'";
//创建消息
Message message=new Message("Normal_a","tag_dan",body.getBytes());
//发送信息
producer.send(message);
//关闭链接
producer.shutdown();
}
}
2.接收
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
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.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.List;
/**
* @Author: jiana
* @Date: 2024/3/26 16 : 37
* @Description:
**/
public class RocketCustomer {
public static void main(String[] args) throws MQClientException {
// 创建消费者对象
DefaultMQPushConsumer c =
new DefaultMQPushConsumer("cons-group1");//Push直接推送给消费者,Pull要等消费者需要是拉取消息 创建拉的消费者对象
// 设置 name server 注册中心
c.setNamesrvAddr("491.235.142.29:9876");
// 设置从哪里订阅消息
//tag标签: * -- 星号表示所有标签 ,TagA --只接收tag_name
c.subscribe("Normal_a","tag_name");
// 设置消息监听器,会一直监听,有消息就会消费
// Concurrently -- 会启动多个线程并行处理消息,不能保证按顺序依次处理
c.setMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
for (MessageExt ext : list) {
String s = new String(ext.getBody());
System.out.println("收到: "+s);
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; //消息发送成功
// return ConsumeConcurrentlyStatus.RECONSUME_LATER;//稍后重新消费
}
});
// 启动
c.start();
}
}
3.2.3.2顺序消息
生产必须串行,放同一消息队列中,消费必须保证顺序消费,不异步进行
和普通消息相同,只是
1.发送
public class Mccc {
static String[] msgs = {
"15103111039,创建",
"15103111065,创建",
"15103111039,付款",
"15103117235,创建",
"15103111065,付款",
"15103117235,付款",
"15103111065,完成",
"15103111039,推送",
"15103117235,完成",
"15103111039,完成"
};
public static void main(String[] args) throws MQClientException, MQBrokerException, RemotingException, InterruptedException {
// 创建生产者对象
DefaultMQProducer p = new DefaultMQProducer("prod-fifo");
// 设置 name server
p.setNamesrvAddr("49.235.142.29:9876");
// 启动生产者,连接消息服务器
p.start();
// 遍历数组发送消息
for (String s : msgs) {
// s -- "15103111039,完成"
Long orderId = Long.valueOf(s.split(",")[0]);//取出订单的id
Message msg = new Message("Topic_fifo", s.getBytes());
//p.send(消息, 队列选择器是匿名内部类, 选择依据是订单id);
SendResult r =
p.send(msg, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message message, Object arg) {
Long orderId = (Long) arg;
//订单id对队列数量取余, 相同订单id得到相同的队列索引
int index = (int) (orderId % mqs.size());
return mqs.get(index);
}
}, orderId);
System.out.println(r);
}
}
}
2.接收
public static void main(String[] args) throws MQClientException {
// 创建消费者对象
DefaultMQPushConsumer c =
new DefaultMQPushConsumer("cons-fifo");//Push直接推送给消费者,Pull要等消费者需要是拉取消息
// 设置 name server 注册中心
c.setNamesrvAddr("491.235.142.29:9876");
// 设置从哪里订阅消息
//tag标签: * -- 星号表示所有标签
c.subscribe("Topic_fifo","*");
// 设置消息监听器
// Concurrently -- 会启动多个线程并行处理消息,不能保证按顺序依次处理
// Orderly -- 单个线程
c.setMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext consumeOrderlyContext) {
for (MessageExt ext : msgs){
String s = new String(ext.getBody());
System.out.println("收到: " +s);
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
// 启动
c.start();
}
3.2.3.3定时/延时消息
1.发送
public static void main(String[] args) throws MQClientException, MQBrokerException, RemotingException, InterruptedException {
// 创建生产者对象
DefaultMQProducer p = new DefaultMQProducer("prod-delay");
// 设置 name server
p.setNamesrvAddr("491.235.142.29:9876");
// 启动生产者,连接消息服务器
p.start();
// 创建消息封装对象 Message(Topic,Tag,消息)
//发送
//Topic--相当于是一级分类,Tag--相当于是二级分类,s--消息以数组的形式发送
Message msg = new Message("topic_delay", "Tag_delay", "aaaaa".getBytes());
//设置延时级别
msg.setDelayTimeLevel(3);//设置延时级别3--延时10秒
//服务器的反馈信息
SendResult r = p.send(msg);
System.out.println(r);
}
2.接收
public static void main(String[] args) throws MQClientException {
// 创建消费者对象
DefaultMQPushConsumer c =
new DefaultMQPushConsumer("cons-topic_delay");//Push直接推送给消费者,Pull要等消费者需要是拉取消息
// 设置 name server 注册中心
c.setNamesrvAddr("49.235.142.29:9876");
// 设置从哪里订阅消息
//tag标签: * -- 星号表示所有标签 ,TagA --只接收TagA
c.subscribe("topic_delay","Tag_delay");//"TagA || TagB || TagC"
// 设置消息监听器
// Concurrently -- 会启动多个线程并行处理消息,不能保证按顺序依次处理
c.setMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
for (MessageExt ext : list) {
String s = new String(ext.getBody());
System.out.println("收到: "+s);
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; //消息发送成功
// return ConsumeConcurrentlyStatus.RECONSUME_LATER;//稍后重新消费
}
});
// 启动
c.start();
}
3.2.3.4事物消息
下面来看 RocketMQ 的事务消息是如何来发送“可靠消息”的,只需要以下三步:
- 发送半消息(半消息不会发送给消费者)
- 执行本地事务
- 提交消息
1.发送
public static void main(String[] args) throws MQClientException {
// 创建事务消息生产者
TransactionMQProducer p =
new TransactionMQProducer("prod-Transaction");
// 设置 name server
p.setNamesrvAddr("49.235.142.29:9876");
// 设置事务消息监听器
//1. 执行本地事务(调用*service.执行业务方法())
//2. 处理rocketmq的事务回查
p.setTransactionListener(new TransactionListener() {
//执行本地事务
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
System.out.println("执行本地业务, 参数: "+arg);
if (Math.random()<0.5){
System.out.println("本地事务执行成功");
return LocalTransactionState.COMMIT_MESSAGE;// 提交消息并通知服务器,消息可以进行投递
}else {
System.out.println("本地事务执行失败");
return LocalTransactionState.ROLLBACK_MESSAGE;// 失败时,回滚消息,撤回消息
}
//return LocalTransactionState.UNKNOW; //未知,当前方法中,一般不会有此情况,
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
return null;
}
});
// 启动
p.start();
// 发送事务消息,触发事务消息监听器
while (true) {
System.out.println("输入消息: ");
String s = new Scanner(System.in).nextLine();
Message msg = new Message("Topic_Transaction", s.getBytes());//Tag标签可加可不加,此处省略
//p.sendMessageInTransaction(msg,arg(业务数据参数));
TransactionSendResult r = p.sendMessageInTransaction(msg, "业务数据参数");
System.out.println("---事务消息结果: "+ r);
}
}
2.接收
同上