如图所示为RocketMQ基本的部署结构,主要分为NameServer集群、Broker集群、Producer集群和Consumer集群四个部分。
大致流程:
Broker在启动的时候会去向NameServer注册并且定时发送心跳,Producer在启动的时候会到NameServer上去拉取Topic所属的Broker具体地址,然后向具体的Broker发送消息
为了消除单点故障,增加可靠性或增大吞吐量,可以在多台机器上部署多个nameserver和broker,并且为每个broker部署1个或多个slave
NameServer的作用是Broker的注册中心。
Topic 与 Tag 都是业务上用来归类的标识,区分在于 Topic 是一级分类,而 Tag 可以理解为是二级分类。
Topic翻译为话题。我们可以理解为第一级消息类型,比如一个电商系统的消息可以分为:交易消息、物流消息等,一条消息必须有一个Topic。
标签,意思就是子主题,可以理解为第二级消息类型,交易创建消息,交易完成消息… 一条消息可以没有Tag
一个topic下,可以设置多个queue(消息队列),默认4个队列。当我们发送消息时,需要要指定该消息的topic。
RocketMQ会轮询该topic下的所有队列,将消息发送出去。
RocketMQ中也有组的概念。代表具有相同角色的生产者组合或消费者组合,称为生产者组或消费者组。
http://rocketmq.apache.org/docs/quick-start/
安装
1.将下载好的文件解压到D:\coding-software目录下,
2.配置环境变量ROCKETMQ_HOME=D:\coding-software\rocketmq-all-4.4.0-bin-release
启动NAMESERVER
cmd命令框执行进入至‘MQ文件夹\bin’下,然后执行start mqnamesrv.cmd
start mqnamesrv.cmd
启动NAMESERVER。成功后会弹出提示框,此框勿关闭。
启动BROKER
1.cmd命令框执行进入至‘MQ文件夹\bin’下,然后执行start mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true
start mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true
启动BROKER。成功后会弹出提示框,此框勿关闭
注意:假如弹出提示框提示‘错误: 找不到或无法加载主类 xxxxxx’。打开runbroker.cmd,然后将‘%CLASSPATH%’加上英文双引号。保存并重新执行start语句。
RocketMQ插件是一个基于SpringBoot编写的可视化插件,主要用于对RocketMQ提供了可视化的管理界面。
下载地址:https://github.com/apache/rocketmq-externals/releases
先修改下配置文件的nameserver地址
修改rocketmq-console\src\main\resources\application.properties,修改如下:
进入‘\rocketmq-externals\rocketmq-console’文件夹
mvn clean package -Dmaven.test.skip=true
编译生成。
编译成功之后,Cmd进入‘target’文件夹,执行
java -jar rocketmq-console-ng-1.0.0.jar
启动‘rocketmq-console-ng-1.0.0.jar’。
输入地址http://127.0.0.1:8080/#/ops
RocketMQ消息消费类型有2种类型,
集群消息模式:集群模式也就是消息只能同时被一个消费者读取
广播模式。广播模式则可以同时被所有消费者读取。
消息发送步骤:
1.创建DefaultMQProducer
2.设置Namesrv地址
3.启动DefaultMQProducer
4.创建消息Message
5.发送消息,发送到broker
6.关闭DefaultMQProducer
消息消费步骤:
1.创建DefaultMQPushConsumer
2.设置namesrv地址
3.设置subscribe,这里是要读取的主题信息
4.创建消息监听MessageListener
5.启动消费
6.获取消息信息
7.返回消息读取状态
依赖:
<dependencies>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
常量类:
public class SysConstants {
//nameserver的地址
public static final String NAME_SERVER = "127.0.0.1:9876";
}
创建类Producer, main方法代码如下:
/**
* 普通消息生产者
*/
public class Producer {
public static void main(String[] args) throws Exception {
// 创建一个消息发送入口对象,主要用于消息发送,指定生产者组
DefaultMQProducer producer = new DefaultMQProducer("producerGroup");
// 设置NameServe地址,如果是集群环境,用分号隔开
producer.setNamesrvAddr(SysConstants.NAME_SERVER);
// 启动并创建消息发送组件
producer.start();
// topic的名字
String topic = "rocketDemobasic";
// 标签名
String tag = "tag1";
// 要发送的数据
String body = "hello,RocketMq,basic1";
Message message = new Message(topic,tag,body.getBytes());
// 发送消息
SendResult result = producer.send(message,20000);
System.out.println(result);
// 关闭消息发送对象
producer.shutdown();
}
}
创建类Consumer,main方法代码如下:
/**
* 普通消息消费者
*/
public class Consumer {
public static void main(String[] args) throws Exception {
// 创建一个消费管理对象,并创建消费者组名字
DefaultMQPushConsumer consumerGroup = new DefaultMQPushConsumer("ConsumerGroup");
// 设置NameServer地址,如果是集群环境,用逗号分隔
consumerGroup.setNamesrvAddr(SysConstants.NAME_SERVER);
// 设置要读取的消息主题和标签
consumerGroup.subscribe("rocketDemobasic","*");
// 设置消费者 可以消费的消息条数
consumerGroup.setConsumeMessageBatchMaxSize(1);
// 读取消息的位置
//ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET:从最后一个读取
//ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET:从第一个读取
consumerGroup.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
// 设置回调函数,处理消息
consumerGroup.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
ConsumeConcurrentlyContext consumeConcurrentlyContext) {
try{
// 读取消息记录
for (MessageExt messageExt : msgs) {
// 获取消息主题
String topic = messageExt.getTopic();
// 获取消息标签
String tags = messageExt.getTags();
// 获取消息体内容
String body = new String(messageExt.getBody(), "UTF-8");
System.out.println("topic:"+topic+",tags:"+tags+",body:"+body);
}
}catch(Exception e){
e.printStackTrace();
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
// 运行消息消费对象
consumerGroup.start();
}
}
消息有序指的是可以按照消息的发送顺序来消费。RocketMQ是通过将“相同ID的消息发送到同一个队列,而一个队列的消息只由一个消费者处理“来实现顺序消息 。
如何保证顺序
在MQ的模型中,顺序需要由3个阶段去保障:
1.消息被发送时保持顺序
2.消息被存储时保持和发送的顺序一致
3.消息被消费时保持和存储的顺序一致
我们创建一个消息生产者OrderProducer
Producer端确保消息顺序唯一要做的事情就是将消息路由到特定的队列,在RocketMQ中,通过MessageQueueSelector来实现分区的选择。
比如如下实现就可以保证相同的订单的消息被路由到相同的分区:
long orderId ; //订单号
return mqs.get(orderId % mqs.size()); //用订单号 和队列数量 取模
准备顺序消息:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
private Long orderId;
private String desc;
public static List<Order> buildOrders(){
List<Order> list = new ArrayList<>();
Order order1001a = new Order(1001L,"创建");
Order order1004a = new Order(1004L,"创建");
Order order1006a = new Order(1006L,"创建");
Order order1009a = new Order(1009L,"创建");
list.add(order1001a);
list.add(order1004a);
list.add(order1006a);
list.add(order1009a);
Order order1001b = new Order(1001L,"付款");
Order order1004b = new Order(1004L,"付款");
Order order1006b = new Order(1006L,"付款");
Order order1009b = new Order(1009L,"付款");
list.add(order1001b);
list.add(order1004b);
list.add(order1006b);
list.add(order1009b);
Order order1001c = new Order(1001L,"完成");
Order order1006c = new Order(1006L,"完成");
list.add(order1001c);
list.add(order1006c);
return list;
}
}
/**
* 顺序消息生产者
*/
public class ProducerOrder {
//nameserver地址
private static String namesrvaddress= SysConstants.NAME_SERVER;
public static void main(String[] args) throws Exception {
//创建DefaultMQProducer
DefaultMQProducer producer = new DefaultMQProducer("order_producer");
//设置namesrv地址
producer.setNamesrvAddr(namesrvaddress);
//启动Producer
producer.start();
List<Order> orderList = Order.buildOrders();
for (Order order : orderList) {
String body = order.toString();
//创建消息
Message message = new Message("orderTopic","order",body.getBytes());
//发送消息
SendResult sendResult = producer.send(
message,
new MessageQueueSelector() {
/**
* @param mqs topic中的队列集合
* @param msg 消息对象
* @param arg 业务参数
* @return
*/
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
//参数是订单id号
Long orderId = (Long) arg;
//确定选择的队列的索引
long index = orderId % mqs.size();
return mqs.get((int) index);
}
},
order.getOrderId(),
20000);
System.out.println("发送结果="+sendResult);
}
//关闭Producer
producer.shutdown();
}
}
创建一个消息消费者OrderConsumer,消息监听用MessageListenerOrderly来实现顺序消息,
代码如下:
/**
* 顺序消息 消费者
*/
public class ConsumerOrder {
public static void main(String[] args) throws Exception {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("order_consumer");
consumer.setNamesrvAddr(SysConstants.NAME_SERVER);
//从第一个开始消费
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
consumer.subscribe("orderTopic","*");
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
for (MessageExt msg : msgs) {
System.out.println("当前队列:"+context.getMessageQueue().getQueueId()+",接收消息:"+new String(msg.getBody()));
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
consumer.start();
System.out.printf("消费者启动成功.%n");
}
}
rocketmq支持固定延迟等级
//“1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h”
/**
* 延迟消息 生产者
*/
public class ProducerDelay {
public static void main(String[] args) throws Exception {
DefaultMQProducer producer = new DefaultMQProducer("delay_producer");
producer.setSendMsgTimeout(10000);
//设置nameserver
producer.setNamesrvAddr(SysConstants.NAME_SERVER);
//生产者开启
producer.start();
//创建消息对象
Message message = new Message("delayTopic","delay","hello world".getBytes());
//设置延迟时间级别
message.setDelayTimeLevel(2);
//发送消息
SendResult sendResult = producer.send(message,20000);
System.out.println(sendResult);
//生产者关闭
producer.shutdown();
}
}
/**
* 延迟消息 消费者
*/
public class ConsumerDelay {
public static void main(String[] args) throws Exception {
//创建消费者对象
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("delay_consumer");
//设置nameserver
consumer.setNamesrvAddr(SysConstants.NAME_SERVER);
//设置主题和tag
consumer.subscribe("delayTopic","*");
//注册消息监听
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
for (MessageExt msg : msgs) {
long now = System.currentTimeMillis();
long storeTime = msg.getStoreTimestamp();
long dealy = now - storeTime;
Date date = new Date(storeTime);
Date nowdate = new Date(now);
System.out.println("now="+now);
System.out.println("storeTime="+storeTime);
System.out.println("delay="+dealy);
System.out.println("date="+date);
System.out.println("nowdate="+nowdate);
System.out.println("消息ID:"+msg.getMsgId()+",延迟时间:"+dealy);
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
//开启消费者
consumer.start();
System.out.println("消费者启动");
}
}
上面发送消息,我们测试的时候,可以发现消息只有一个消费者能收到,如果我们想实现消息广播,让每个消费者都能收到消息也是可以实现的。而且上面发送消息的时候,每次都是发送单条Message对象,能否批量发送呢?答案是可以的。
/**
* 批量 生产者
*/
public class ProducerBatch {
public static void main(String[] args) throws Exception {
DefaultMQProducer producer = new DefaultMQProducer("batch_producer");
//设置nameserver
producer.setNamesrvAddr(SysConstants.NAME_SERVER);
//生产者开启
producer.start();
//创建消息对象 集合
String topic = "batchTopic";
String tag = "batchTag";
List<Message> messageList = new ArrayList<>();
Message message1 = new Message(topic,tag,"hello world1".getBytes());
Message message2 = new Message(topic,tag,"hello world2".getBytes());
Message message3 = new Message(topic,tag,"hello world3".getBytes());
messageList.add(message1);
messageList.add(message2);
messageList.add(message3);
//发送消息
SendResult sendResult = producer.send(messageList,20000);
System.out.println(sendResult);
//生产者关闭
producer.shutdown();
}
}
注意:发送的总消息长度 不能大于4M。
/**
* 批量消费者
*/
public class ConsumerBatch {
public static void main(String[] args) throws Exception {
//创建消费者对象
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("batch_consumer");
//设置nameserver
consumer.setNamesrvAddr(SysConstants.NAME_SERVER);
//设置主题和tag
consumer.subscribe("batchTopic","*");
//注册消息监听
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
for (MessageExt msg : msgs) {
System.out.println("消息ID:"+msg.getMsgId()+","+msg.getTopic()+","+msg.getTags());
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
//开启消费者
consumer.start();
System.out.println("消费者启动");
}
}
集群模式,只有一个消费者可以收到消息
广播模式,所有的消费者都可以收到消息。
需要把【消息模式】设置为 :广播模式,同时开启2个以上的消费者,再次运行生产者,只需要在消费方设置一下消费模式即可。
MessageModel.BROADCASTING:广播模式
MessageModel.CLUSTERING:集群模式
需要在消费者加入的代码如下:
//设置消费模式为广播模式(BROADCASTING),默认为集群模式(CLUSTERING)
consumer.setMessageModel(MessageModel.CLUSTERING);
注意:2个消费者的 名字都是一样的。
/**
* 批量消费者
*/
public class ConsumerBroadcast {
public static void main(String[] args) throws Exception {
//创建消费者对象
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("delay_consumer");
//设置nameserver
consumer.setNamesrvAddr(SysConstants.NAME_SERVER);
//设置主题和tag
consumer.subscribe("broadcastTopic","*");
//设置消息模式 为 广播模式
consumer.setMessageModel(MessageModel.BROADCASTING);
//注册消息监听
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
for (MessageExt msg : msgs) {
System.out.println("消费者1:消息ID:"+msg.getMsgId()+",内容"+new String(msg.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
//开启消费者
consumer.start();
System.out.println("消费者启动");
}
}
/**
* 批量消费者
*/
public class ConsumerBroadcast1 {
public static void main(String[] args) throws Exception {
//创建消费者对象
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("delay_consumer");
//设置nameserver
consumer.setNamesrvAddr(SysConstants.NAME_SERVER);
//设置主题和tag
consumer.subscribe("broadcastTopic","*");
//设置消息模式 为 广播模式
consumer.setMessageModel(MessageModel.BROADCASTING);
//注册消息监听
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
for (MessageExt msg : msgs) {
System.out.println("消费者2:消息ID:"+msg.getMsgId()+",内容"+new String(msg.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
//开启消费者
consumer.start();
System.out.println("消费者启动");
}
}