详细各个模块启动流程可以查看该篇文章:https://blog.csdn.net/u010597819/article/details/86646245
对于同一个主题的消息,对于多master的broker是轮询方式发送消息至master,master-slave的broker可以支持同步或异步方式进行同步数据。
如此设计的好处是服务的水平扩展与负载均衡扩展的能力都是强大的,但是高可用方案则需要从同步与异步的实现主从刷新中进行一个权衡抉择
我们按照无序、并发、group、集群模式(点对点模式,优化了其中的点为group抽象)、多消费者推送的方式讨论。
详细过程此文不再赘述
// 消费者与group分组是一对一关系
ConcurrentMap<String/* group */, MQConsumerInner> consumerTable = new ConcurrentHashMap<String, MQConsumerInner>();
图片来源:https://www.jianshu.com/p/824066d70da8
命名服务器,上图已经很明确了其作用,broker的发现服务。官方的解释为:命名服务器是一个路由信息提供者。生产者或者消费者客户端通过主题查找对应的broker列表
Name server serves as the routing information provider.
Producer/Consumer clients look up topics to find the corresponding broker list.
可以看出命名服务器的重要性,如果重要的角色,当然必须要求是高可用的,那么命名服务器可以部署为集群,那么集群中各个命名服务器节点的数据是否需要同步,如果需要如果同步?分布式一致性如何保证?
看过源码的同学应该知道,没看过的同学可以查看文章开头列出的源码学习博文。在启动每个broker时会向所有的命名服务器发送上报信息的,并且会启动线程30秒上报一次
所以命名服务器并不是越多越好,由于是30秒上报一次信息,命名服务器是默认10秒扫描一次broker的活跃状态,所以是有可能broker宕机后broker非活跃状态有10秒的无感知
从官方的事务实现案例中可以看到LocalTransactionState事务的状态由3种(commit、rollback、未知),了解过分布式一致性算法的同学应该对2-PC,3-PC有所了解,看到此处状态便能猜到实现采用2-PC模式,在官网中也已经给出了答案,采用2阶段提交模式实现消息的分布式一致性。当然该算法的弊端有兴趣的同学可以参考《从Paxos到Zookeeper》一书,里面给出了非常详细介绍,此处不再赘述
public enum LocalTransactionState {
COMMIT_MESSAGE,
ROLLBACK_MESSAGE,
UNKNOW,
}
使用方法可以参考官方案例,实现TransactionListener接口
executeLocalTransaction方法:发送prepare(准备)消息并根据事务id缓存事务的状态,发送消息的方式为同步模式
checkLocalTransaction方法:上个方法发送prepare(准备)消息后没有得到响应,broker将会发送校验消息检查事务状态,该方法将会被调用,读取本地事务状态
创建DefaultMQProducer时指定enableMsgTrace即可启用消息跟踪,跟踪的实现使用的钩子方法,在发送消息的模板中,发送消息的前后会调用钩子方法的before与after,输出一些跟踪信息
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
if (enableMsgTrace) {
try {
AsyncTraceDispatcher dispatcher = new AsyncTraceDispatcher(customizedTraceTopic, rpcHook);
dispatcher.setHostProducer(this.getDefaultMQProducerImpl());
traceDispatcher = dispatcher;
this.getDefaultMQProducerImpl().registerSendMessageHook(
new SendMessageTraceHookImpl(traceDispatcher));
} catch (Throwable e) {
log.error("system mqtrace hook init failed ,maybe can't send msg trace data");
}
}
}
// 同步调用
private SendResult sendKernelImpl(final Message msg,
final MessageQueue mq,
final CommunicationMode communicationMode,
final SendCallback sendCallback,
final TopicPublishInfo topicPublishInfo,
final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
...
if (this.hasSendMessageHook()) {
context = new SendMessageContext();
context.setProducer(this);
context.setProducerGroup(this.defaultMQProducer.getProducerGroup());
context.setCommunicationMode(communicationMode);
context.setBornHost(this.defaultMQProducer.getClientIP());
context.setBrokerAddr(brokerAddr);
context.setMessage(msg);
context.setMq(mq);
String isTrans = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);
if (isTrans != null && isTrans.equals("true")) {
context.setMsgType(MessageType.Trans_Msg_Half);
}
if (msg.getProperty("__STARTDELIVERTIME") != null || msg.getProperty(MessageConst.PROPERTY_DELAY_TIME_LEVEL) != null) {
context.setMsgType(MessageType.Delay_Msg);
}
this.executeSendMessageHookBefore(context);
}...
SendResult sendResult = null;
switch (communicationMode) {
// 异步模式会在异步发送之后回调after钩子方法
case ASYNC:
...
if (this.hasSendMessageHook()) {
context.setSendResult(sendResult);
this.executeSendMessageHookAfter(context);
}
return sendResult;
} catch (RemotingException e) {
if (this.hasSendMessageHook()) {
context.setException(e);
this.executeSendMessageHookAfter(context);
}
throw e;
} catch (MQBrokerException e) {
if (this.hasSendMessageHook()) {
context.setException(e);
this.executeSendMessageHookAfter(context);
}
throw e;
} catch (InterruptedException e) {
if (this.hasSendMessageHook()) {
context.setException(e);
this.executeSendMessageHookAfter(context);
}
throw e;
} ...
}
// 异步调用
private void sendMessageAsync(
final String addr,
final String brokerName,
final Message msg,
final long timeoutMillis,
final RemotingCommand request,
final SendCallback sendCallback,
final TopicPublishInfo topicPublishInfo,
final MQClientInstance instance,
final int retryTimesWhenSendFailed,
final AtomicInteger times,
final SendMessageContext context,
final DefaultMQProducerImpl producer
) throws InterruptedException, RemotingException {
this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() {
@Override
public void operationComplete(ResponseFuture responseFuture) {
RemotingCommand response = responseFuture.getResponseCommand();
if (null == sendCallback && response != null) {
try {
SendResult sendResult = MQClientAPIImpl.this.processSendResponse(brokerName, msg, response);
if (context != null && sendResult != null) {
context.setSendResult(sendResult);
context.getProducer().executeSendMessageHookAfter(context);
}
} catch (Throwable e) {
}
producer.updateFaultItem(brokerName, System.currentTimeMillis() - responseFuture.getBeginTimestamp(), false);
return;
}
if (response != null) {
try {
SendResult sendResult = MQClientAPIImpl.this.processSendResponse(brokerName, msg, response);
assert sendResult != null;
if (context != null) {
context.setSendResult(sendResult);
context.getProducer().executeSendMessageHookAfter(context);
}
...
}