前面分析Broker的启动过程,接下来分析Producer的启动过程。
从官网说明开始
先来看看官网的发送消息额实例代码
public class Producer {
public static void main(String[] args) throws MQClientException {
// 创建指定分组名的生产者
DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName");
// 启动生产者
producer.start();
for (int i = 0; i < 128; i++)
try {
// 构建消息
Message msg = new Message("TopicTest",
"TagA",
"OrderID188",
"Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET));
// 同步发送
SendResult sendResult = producer.send(msg);
// 打印发送结果
System.out.printf("%s%n", sendResult);
} catch (Exception e) {
e.printStackTrace();
}
producer.shutdown();
}
}
整个发送消息的步骤主要分为以下几步:
- 创建
DefaultMQProducer
- 启动生产者实例
- 构建消息
- 调用
DefaultMQProducer
的send
方法发送消息
DefaultMQProducer
(该类是线程安全的。在配置并启动完成后可在多个线程间安全共享)类是应用用来投递消息的入口,开箱即用,可通过无参构造方法快速创建一个生产者。主要负责消息的发送,支持同步/异步/oneway的发送方式,这些发送方式均支持批量发送。可以通过该类提供的getter/setter方法,调整发送者的参数。DefaultMQProducer
提供了多个send方法,每个send方法略有不同。
字段摘要
类型 | 字段名称 | 描述 |
---|---|---|
DefaultMQProducerImpl | defaultMQProducerImpl | 生产者的内部默认实现 |
String | producerGroup | 生产者分组 |
String | createTopicKey | 在发送消息时,自动创建服务器不存在的topic |
int | defaultTopicQueueNums | 创建topic时默认的队列数量 |
int | sendMsgTimeout | 发送消息的超时时间 |
int | compressMsgBodyOverHowmuch | 压缩消息体的阈值 |
int | retryTimesWhenSendFailed | 同步模式下内部尝试发送消息的最大次数 |
int | retryTimesWhenSendAsyncFailed | 异步模式下内部尝试发送消息的最大次数 |
boolean | retryAnotherBrokerWhenNotStoreOK | 是否在内部发送失败时重试另一个broker |
int | maxMessageSize | 消息的最大长度 |
TraceDispatcher | traceDispatcher | 消息追踪器。使用rcpHook来追踪消息 |
构造方法摘要
方法名称 | 方法描述 |
---|---|
DefaultMQProducer() | 由默认参数值创建一个生产者 |
DefaultMQProducer(final String producerGroup) | 使用指定的分组名创建一个生产者 |
DefaultMQProducer(final String producerGroup, boolean enableMsgTrace) | 使用指定的分组名创建一个生产者,并设置是否开启消息追踪 |
DefaultMQProducer(final String producerGroup, boolean enableMsgTrace, final String customizedTraceTopic) | 使用指定的分组名创建一个生产者,并设置是否开启消息追踪及追踪topic的名称 |
DefaultMQProducer(RPCHook rpcHook) | 使用指定的hook创建一个生产者 |
DefaultMQProducer(final String producerGroup, RPCHook rpcHook) | 使用指定的分组名及自定义hook创建一个生产者 |
DefaultMQProducer(final String producerGroup, RPCHook rpcHook, boolean enableMsgTrace,final String customizedTraceTopic) | 使用指定的分组名及自定义hook创建一个生产者,并设置是否开启消息追踪及追踪topic的名称 |
使用方法摘要
返回值 | 方法名称 | 方法描述 |
---|---|---|
void | createTopic(String key, String newTopic, int queueNum) | 在broker上创建指定的topic |
void | createTopic(String key, String newTopic, int queueNum, int topicSysFlag) | 在broker上创建指定的topic |
long | earliestMsgStoreTime(MessageQueue mq) | 查询最早的消息存储时间 |
List | fetchPublishMessageQueues(String topic) | 获取topic的消息队列 |
long | maxOffset(MessageQueue mq) | 查询给定消息队列的最大offset |
long | minOffset(MessageQueue mq) | 查询给定消息队列的最小offset |
QueryResult | queryMessage(String topic, String key, int maxNum, long begin, long end) | 按关键字查询消息 |
long | searchOffset(MessageQueue mq, long timestamp) | 查找指定时间的消息队列的物理offset |
SendResult | send(Collection msgs) | 同步批量发送消息 |
SendResult | send(Collection msgs, long timeout) | 同步批量发送消息 |
SendResult | send(Collection msgs, MessageQueue messageQueue) | 向指定的消息队列同步批量发送消息 |
SendResult | send(Collection msgs, MessageQueue messageQueue, long timeout) | 向指定的消息队列同步批量发送消息,并指定超时时间 |
SendResult | send(Message msg) | 同步单条发送消息 |
SendResult | send(Message msg, long timeout) | 同步发送单条消息,并指定超时时间 |
SendResult | send(Message msg, MessageQueue mq) | 向指定的消息队列同步发送单条消息 |
SendResult | send(Message msg, MessageQueue mq, long timeout) | 向指定的消息队列同步单条发送消息,并指定超时时间 |
void | send(Message msg, MessageQueue mq, SendCallback sendCallback) | 向指定的消息队列异步单条发送消息,并指定回调方法 |
void | send(Message msg, MessageQueue mq, SendCallback sendCallback, long timeout) | 向指定的消息队列异步单条发送消息,并指定回调方法和超时时间 |
SendResult | send(Message msg, MessageQueueSelector selector, Object arg) | 向消息队列同步单条发送消息,并指定发送队列选择器 |
SendResult | send(Message msg, MessageQueueSelector selector, Object arg, long timeout) | 向消息队列同步单条发送消息,并指定发送队列选择器与超时时间 |
void | send(Message msg, MessageQueueSelector selector, Object arg, SendCallback sendCallback) | 向指定的消息队列异步单条发送消息 |
void | send(Message msg, MessageQueueSelector selector, Object arg, SendCallback sendCallback, long timeout) | 向指定的消息队列异步单条发送消息,并指定超时时间 |
void | send(Message msg, SendCallback sendCallback) | 异步发送消息 |
void | send(Message msg, SendCallback sendCallback, long timeout) | 异步发送消息,并指定回调方法和超时时间 |
TransactionSendResult | sendMessageInTransaction(Message msg, LocalTransactionExecuter tranExecuter, final Object arg) | 发送事务消息,并指定本地执行事务实例 |
TransactionSendResult | sendMessageInTransaction(Message msg, Object arg) | 发送事务消息 |
void | sendOneway(Message msg) | 单向发送消息,不等待broker响应 |
void | sendOneway(Message msg, MessageQueue mq) | 单向发送消息到指定队列,不等待broker响应 |
void | sendOneway(Message msg, MessageQueueSelector selector, Object arg) | 单向发送消息到队列选择器的选中的队列,不等待broker响应 |
void | shutdown() | 关闭当前生产者实例并释放相关资源 |
void | start() | 启动生产者 |
MessageExt | viewMessage(String offsetMsgId) | 根据给定的msgId查询消息 |
MessageExt | public MessageExt viewMessage(String topic, String msgId) | 根据给定的msgId查询消息,并指定topic |
代码分析
DefaultMQProducer
的构造方法
直接分析最复杂的一个构造方法
public DefaultMQProducer(final String producerGroup, RPCHook rpcHook, boolean enableMsgTrace,
final String customizedTraceTopic) {
//消息组
this.producerGroup = producerGroup;
//消息发送实现类
defaultMQProducerImpl = new DefaultMQProducerImpl(this, rpcHook);
//if client open the message trace feature
//如果开启了消息轨迹功能,增加对应的RPC钩子方法来追踪消息
if (enableMsgTrace) {
try {
AsyncTraceDispatcher dispatcher = new AsyncTraceDispatcher(producerGroup, TraceDispatcher.Type.PRODUCE, customizedTraceTopic, rpcHook);
dispatcher.setHostProducer(this.defaultMQProducerImpl);
traceDispatcher = dispatcher;
//发送消息钩子
this.defaultMQProducerImpl.registerSendMessageHook(
new SendMessageTraceHookImpl(traceDispatcher));
//结束事务钩子
this.defaultMQProducerImpl.registerEndTransactionHook(
new EndTransactionTraceHookImpl(traceDispatcher));
} catch (Throwable e) {
log.error("system mqtrace hook init failed ,maybe can't send msg trace data");
}
}
}
这里主要就是创建一个生产者内部的默认实现类DefaultMQProducerImpl
以及消息轨迹相关的钩子方法的注册。进一步的分析对应的默认实现类的构造方法
public DefaultMQProducerImpl(final DefaultMQProducer defaultMQProducer, RPCHook rpcHook) {
this.defaultMQProducer = defaultMQProducer;
//RPC钩子
this.rpcHook = rpcHook;
//异步发送消息的任务队列
this.asyncSenderThreadPoolQueue = new LinkedBlockingQueue<Runnable>(50000);
//异步发送任务的线程池
this.defaultAsyncSenderExecutor = new ThreadPoolExecutor(
Runtime.getRuntime().availableProcessors(),
Runtime.getRuntime().availableProcessors(),
1000 * 60,
TimeUnit.MILLISECONDS,
this.asyncSenderThreadPoolQueue,
new ThreadFactory() {
private AtomicInteger threadIndex = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "AsyncSenderExecutor_" + this.threadIndex.incrementAndGet());
}
});
}
逻辑也是比较简单的。继续下面的分析
生产者实例的启动start
方法
public void start() throws MQClientException {
//设置生产者的producerGroup,也就是分组名,如果没有设置,会按照一定的规律生成一个
this.setProducerGroup(withNamespace(this.producerGroup));
//调用生产者默认实现DefaultMQProducerImpl的启动方法
this.defaultMQProducerImpl.start();
//如果消息轨迹相关的消息分发类不是null,说明启用来消息轨迹分析,这里需要进行启动。不对消息轨迹的逻辑进行分析
if (null != traceDispatcher) {
try {
traceDispatcher.start(this.getNamesrvAddr(), this.getAccessChannel());
} catch (MQClientException e) {
log.warn("trace dispatcher start failed ", e);
}
}
}
生成者的启动实际逻辑也是在DefaultMQProducerImpl
中。进入DefaultMQProducerImpl
看其start
方法实现逻辑
public void start(final boolean startFactory) throws MQClientException {
//根据服务的状态进行不同的逻辑处理
switch (this.serviceState) {
//刚创建,还没启动
case CREATE_JUST:
//预先设置为启动失败
this.serviceState = ServiceState.START_FAILED;
//检查分组配置
this.checkConfig();
//如果生产者分组不是内部消息的分组,则修改对应的实例名(instanceName)
if (!this.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) {
this.defaultMQProducer.changeInstanceNameToPID();
}
//创建客户端
this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQProducer, rpcHook);
//注册生产者,这个会把对应的生产者加入到对应的分组集合中(group, producer),然后在向broker发送心跳包的时候带过去
boolean registerOK = mQClientFactory.registerProducer(this.defaultMQProducer.getProducerGroup(), this);
//注册不成功返回异常
if (!registerOK) {
this.serviceState = ServiceState.CREATE_JUST;
throw new MQClientException("The producer group[" + this.defaultMQProducer.getProducerGroup()
+ "] has been created before, specify another name please." + FAQUrl.suggestTodo(FAQUrl.GROUP_NAME_DUPLICATE_URL),
null);
}
//消息topic发布的集合缓存
this.topicPublishInfoTable.put(this.defaultMQProducer.getCreateTopicKey(), new TopicPublishInfo());
//启动服务
if (startFactory) {
mQClientFactory.start();
}
log.info("the producer [{}] start OK. sendMessageWithVIPChannel={}", this.defaultMQProducer.getProducerGroup(),
this.defaultMQProducer.isSendMessageWithVIPChannel());
this.serviceState = ServiceState.RUNNING;
break;
//处于运行状态和启动弄失败不做任何助理,
case RUNNING:
case START_FAILED:
case SHUTDOWN_ALREADY:
throw new MQClientException("The producer service state not OK, maybe started once, "
+ this.serviceState
+ FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK),
null);
default:
break;
}
//加锁的方式发送心跳给所有的broker
this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();
//定期清除超时请求的任务
this.timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
try {
RequestFutureTable.scanExpiredRequest();
} catch (Throwable e) {
log.error("scan RequestFutureTable exception", e);
}
}
}, 1000 * 3, 1000);
}
start
主要逻辑还是先根据服务启动的状态,进行不同的处理逻辑,其中只有刚创建,还没启动的逻辑是复杂的。服务启动之后会向所有的broker发送心跳,然后启动定时清除超时的发送消息请求的任务。
启动的服务的逻辑如下:
- 检查生产者的分组配置
- 创建客户端
- 吧对应的客户端和对应的分组保存到一个集合中(这个集合会通过心跳发送broker)
- 检查是不是添加是不是成功,然后启动服务,更新服务状态
这几个步骤中比较复杂的逻辑是客户端的创建。因为这个客户端的创建不仅仅是消息的生产者调用,还是消息的消费者也会调用的类。客户端的创建会通过MQClientManager
包装一次,然后创建MQClientInstance
。包装的作用是,对生产者客户端进行缓存,避免重复创建,因为DefaultMQProducer
是可以多线程调用的。
public MQClientInstance getOrCreateMQClientInstance(final ClientConfig clientConfig, RPCHook rpcHook) {
String clientId = clientConfig.buildMQClientId();
MQClientInstance instance = this.factoryTable.get(clientId);
if (null == instance) {
//创建客户端实例
instance =
new MQClientInstance(clientConfig.cloneClientConfig(),
this.factoryIndexGenerator.getAndIncrement(), clientId, rpcHook);
//保存到缓存中
MQClientInstance prev = this.factoryTable.putIfAbsent(clientId, instance);
if (prev != null) {
instance = prev;
log.warn("Returned Previous MQClientInstance for clientId:[{}]", clientId);
} else {
log.info("Created new MQClientInstance for clientId:[{}]", clientId);
}
}
return instance;
}
可以看到DefaultMQProducerImpl
的start
方法的主要做的还是创建MQClientInstance
对象,然后调用他的start
方法。对于MQClientInstance
下一篇文章进行分析。