本文参考地址:RocketMQ官方中文文档
丁威《RocketMQ技术内幕》
杨开元《RocketMQ实战与原理解析》
1.生产者:
负责生产消息,一般由业务系统负责生产消息。一个消息生产者会把业务应用系统里产生的消息发送到broker服务器
RocketMQ提供多种发送方式,同步发送、异步发送、顺序发送、单向发送。同步和异步方式均需要Broker返回确认信息,单向发送不需要
2.不同类型的生产者
由于生产者在向broker消息队列中写入消息时,根据不同的业务场景采用了不同的写入策略,从而产生出不同类型的生产者类型,其中包含同步、异步、单向、延迟、事务等类型,但最核心也是默认的生产者类是DefaultMQProducer:
使用(橙色步骤必不可少):
①通过消息组名创建生产者实例
DefaultMQProducer producer = new DefaultMQProducer("group_name");
②为了帮助JVM区分不同的Producer,需要设置InstanceName(不设置为"DEFAULT")
produce.setInstanceName("instanceName");
③设置发送失败重试次数,防止网络异常消息丢失
producer.setRetryTimesWhenSendFailed(5);
④设置NameServer管理服务地址(可以是集群)
producer.setNameserverAddr("ip1:9876;ip2:9876......");
⑤启动Producer实例
producer.start();
⑥组装消息
public Message(
String topic, //消息主题
String tags, //过滤消息TAG
String keys, //消息索引键,MQ可以通过这个key快速定位本条消息
int flag, //消息标记,不做处理
byte[] body, //自定义消息体,即消息内容
boolean waitStoreMsgOK //消息发送时是否需要等待消息存储完成时再返回
)
⑦发送消息
SendResult sendResult = producer.send(message); //同步发送
producer.send(msg, new SendCallback(){ //成功或失败的处理方式
@Override
public void onSuccess(SendResult sendResult) {
System.out.printf("%-10d OK %s %n", index,
sendResult.getMsgId());
}
@Override
public void onException(Throwable e) {
System.out.printf("%-10d Exception %s %n", index, e);
e.printStackTrace();
} }); //异步,设置回调函数
producer.send(message); //单向发送,不关心发送结果
⑧关闭Producer实例
producer.shutdown();
3.生产者启动流程(原创):
4.消息发送方式:
(1)同步 生产者给MQ发送消息后,需等待Broker的响应结果,多用于重要的消息通知,短信通知
public class SyncProducer {
public static void main(String[] args) throws Exception {
// 实例化消息生产者Producer
DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
// 设置NameServer的地址
producer.setNamesrvAddr("localhost:9876");
// 启动Producer实例
producer.start();
for (int i = 0; i < 100; i++) {
// 创建消息,并指定Topic,Tag和消息体
Message msg = new Message("TopicTest" /* Topic */,
"TagA" /* Tag */,
("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
);
// 发送消息到一个Broker
SendResult sendResult = producer.send(msg);
// 通过sendResult返回消息是否成功送达
System.out.printf("%s%n", sendResult);
}
// 如果不再发送消息,关闭Producer实例。
producer.shutdown();
}
}
(2)异步 生产者在给MQ发送消息时,会指定一个回调函数(在新的线程中执行),然后自身线程立即返回,不会同步阻塞等待,然后结束,多用在对响应时间敏感的业务场景,即发送端不能容忍长时间地等待Broker的响应
public class AsyncProducer {
public static void main(String[] args) throws Exception {
// 实例化消息生产者Producer
DefaultMQProducer producer = new
DefaultMQProducer("please_rename_unique_group_name");
// 设置NameServer的地址
producer.setNamesrvAddr("localhost:9876");
// 启动Producer实例
producer.start();
producer.setRetryTimesWhenSendAsyncFailed(0);
int messageCount = 100;
// 根据消息数量实例化倒计时计算器
final CountDownLatch2 countDownLatch = new CountDownLatch2(messageCount);
for (int i = 0; i < messageCount; i++) {
final int index = i;
// 创建消息,并指定Topic,Tag和消息体
Message msg = new Message("TopicTest",
"TagA",
"OrderID188",
"Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET));
// SendCallback接收异步返回结果的回调
producer.send(msg, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.printf("%-10d OK %s %n", index,
sendResult.getMsgId());
}
@Override
public void onException(Throwable e) {
System.out.printf("%-10d Exception %s %n", index, e);
e.printStackTrace();
}
});
}
// 等待5s
countDownLatch.await(5, TimeUnit.SECONDS);
// 如果不再发送消息,关闭Producer实例。
producer.shutdown();
}
}
(3)单向 生产者给MQ发送消息后不会阻塞等待、也不会指定回调函数,只管发;多用于不特别关心发送结果的场景,例如日志发送
public class OnewayProducer {
public static void main(String[] args) throws Exception{
// 实例化消息生产者Producer
DefaultMQProducer producer = new
DefaultMQProducer("please_rename_unique_group_name");
// 设置NameServer的地址
producer.setNamesrvAddr("localhost:9876");
// 启动Producer实例
producer.start();
for (int i = 0; i < 100; i++) {
// 创建消息,并指定Topic,Tag和消息体
Message msg = new Message("TopicTest" /* Topic */,
"TagA" /* Tag */,
("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
);
// 发送单向消息,没有任何返回结果
producer.sendOneway(msg);
}
// 如果不再发送消息,关闭Producer实例。
producer.shutdown();
}
}
5.消息类型:
(1)顺序消息
一类消息消费时,能按照发送的顺序来消费,RocketMQ可以严格的保证消息有序。
全局顺序消息与分区顺序消息
某个Topic下的所有消息都要保证顺序
全局顺序对于指定的一个 Topic,所有消息按照严格的先入先出(FIFO)的顺序进行发布和消费。
性能要求不高,所有的消息严格按照 FIFO 原则进行消息发布和消费的场景
只要保证每一组消息被顺序消费即可
分区顺序对于指定的一个 Topic,所有消息根据 sharding key 进行区块分区。 同一个分区内的消息按照严格的 FIFO 顺序进行发布和消费。 Sharding key 是顺序消息中用来区分不同分区的关键字段,和普通消息的 Key 是完全不同的概念。性能要求高,以 sharding key 作为分区字段,在同一个区块中严格的按照 FIFO 原则进行消息发布和消费的场景
实现:
同组任务可以通过唯一的参数id(比如订单id),和Message集合长度取余,拿到index队列,只要保证传入参数id不变,就能保证同一组消息发送到同一个消息队列中,加之消息队列可以保证FIFO的特性,因此可以做到顺序生产、顺序消费
(2)延时消息
支持定时消息,但是不支持任意时间精度,仅支持特定的level,从1s到2h分别对应着等级1到18;
消息消费失败后也会进入延时消息队列,消息发送时间与设置的延时等级和重试次数有关
1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
使用:
public class ScheduledMessageProducer {
public static void main(String[] args) throws Exception {
// 实例化一个生产者来产生延时消息
DefaultMQProducer producer = new DefaultMQProducer("ExampleProducerGroup");
// 启动生产者
producer.start();
int totalMessagesToSend = 100;
for (int i = 0; i < totalMessagesToSend; i++) {
Message message = new Message("TestTopic", ("Hello scheduled message " + i).getBytes()); //设置延时等级3,消息将在10s之后发送(现在只支持固定的几个时间,详看delayTimeLevel)
message.setDelayTimeLevel(3);
// 发送消息
producer.send(message);
}
// 关闭生产者
producer.shutdown();
}
}
6.消息发送基本流程:
(1)消息验证
先确保生产者处于运行状态,再确定消息是否符合相应的规范
规范要求:
主题名称、消息体不能为空
消息长度不等于0,且不允许超过最大允许长度(maxMessageSize = 1024*1024*4)
(2)寻找路由
消息发送前,先获取主题的路由信息,获取后才知道消息要发送到哪个具体Broker节点
private TopicPublishInfo tryToFindTopicPublishInfo(final String topic) {
// 获取缓存中topic的路由信息
TopicPublishInfo topicPublishInfo = this.topicPublishInfoTable.get(topic);
if (null == topicPublishInfo || !topicPublishInfo.ok()) {
// 缓存中不存在路由信息,那么就去NameServer中查询
this.topicPublishInfoTable.putIfAbsent(topic, new TopicPublishInfo());
this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic);
topicPublishInfo = this.topicPublishInfoTable.get(topic);
}
if (topicPublishInfo.isHaveTopicRouterInfo() || topicPublishInfo.ok()) {
// 存在topic路由信息,则直接返回路由信息
return topicPublishInfo;
} else {
this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic, true, this.defaultMQProducer);
topicPublishInfo = this.topicPublishInfoTable.get(topic);
return topicPublishInfo;
}
}
(3)选择队列
public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName) {
if (this.sendLatencyFaultEnable) {
try {
int index = tpInfo.getSendWhichQueue().incrementAndGet();
for (int i = 0; i < tpInfo.getMessageQueueList().size(); i++) {
int pos = Math.abs(index++) % tpInfo.getMessageQueueList().size();
if (pos < 0)
pos = 0;
MessageQueue mq = tpInfo.getMessageQueueList().get(pos);
if (latencyFaultTolerance.isAvailable(mq.getBrokerName()))
return mq;
}
final String notBestBroker = latencyFaultTolerance.pickOneAtLeast();
int writeQueueNums = tpInfo.getQueueIdByBroker(notBestBroker);
if (writeQueueNums > 0) {
final MessageQueue mq = tpInfo.selectOneMessageQueue();
if (notBestBroker != null) {
mq.setBrokerName(notBestBroker);
mq.setQueueId(tpInfo.getSendWhichQueue().incrementAndGet() % writeQueueNums);
}
return mq;
} else {
latencyFaultTolerance.remove(notBestBroker);
}
} catch (Exception e) {
log.error("Error occurred when selecting message queue", e);
}
return tpInfo.selectOneMessageQueue();
}
return tpInfo.selectOneMessageQueue(lastBrokerName);
}
(4)消息发送
此处见——>4.消息发送方式