① Broker分为Master与Slave,一个Master可以对应多个Slave,但是一个Slave只能对应一个Master,Master与Slave的对应关系通过指定相同的BrokerName,不同的BrokerId来定义,BrokerId为0表示Master,非0表示Slave。Master也可以部署多个。每个Broker与Name Server集群中的所有节点建立长连接,定时注册Topic信息到所有Name Server。
② Producer与Name Server集群中的其中一个节点(随机选择)建立长连接,定期从Name Server取Topic路由信息,并向提供Topic服务的Broker(Master)建立长连接,且定时向Broker(Master)发送心跳。
③ Consumer与Name Server集群中的其中一个节点(随机选择)建立长连接,定期从Name Server取Topic路由信息,并向提供Topic服务的Master、Slave建立长连接,且定时向Master、Slave发送心跳。Consumer既可以从Master订阅消息,也可以从Slave订阅消息,订阅规则由Broker配置决定。
1.DefaultMQProducerImpl类的start()方法 - (启动的全部流程)
public void start(final boolean startFactory) throws MQClientException {
switch (this.serviceState) {
// 如果当前服务状态为CREATE_JUST【刚创建】
case CREATE_JUST:
// 更改状态防止多次启动
this.serviceState = ServiceState.START_FAILED;
// groupName合法性校验
this.checkConfig();
// 判断当前生产者组是否符合要求
// 改变生产者的实例id为进程id
if (!this.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) {
this.defaultMQProducer.changeInstanceNameToPID();
}
// MQClientInstance 负责与NameServer 通信进行心跳维持、根据Topic获取Broker地址;
// MQClientInstance 负责与Broker通信进行收发消息、ReBalance;
// 根据clientId获取对应的MQClientInstance,同一个clientId会复用同一个MQClientInstance
this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQProducer, rpcHook);
// 将producer注册到MQClientInstance.producerTbale
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与发布关系放入topicPublishInfoTable注册表
// 缓存topic的路由信息
this.topicPublishInfoTable.put(this.defaultMQProducer.getCreateTopicKey(), new TopicPublishInfo());
if (startFactory) {
// 启动MQClientInstance
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;
}
// 生产者启动时与某一个nameServer建立长连接,并定时从该nameServer拉取topic对应路由信息
// 生产者启动时与所有Broker建立长连接,并定时发送心跳请求,定时清除本地无效broker信息
// 向所有 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);
}
2.MQClientInstance类是负责生产者与NameServer和Broker通信的核心类。看看它的实现
this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQProducer, rpcHook);
public MQClientInstance getOrCreateMQClientInstance(final ClientConfig clientConfig, RPCHook rpcHook) {
// 这种情况会出现在你在一个JVM里启动了多个Producer时,
// 且没有设置instanceName和unitName,那么这两个Producer会公用一个MQClientInstance,发送的消息会路由到同一个集群。
// 例如,你起了两个Producer,并且配置的NameServer地址不一样,本意是让这两个Producer往不同集群上分配消息,
// 但是由于共用了一个MQClientInstance,这个MQClientInstance是基于先来的Producer配置构建的,
// 第二个Producer和他公用后被认为是同一instance,配置是相同的,消息的路由就是相同的,就没有达到你想要的效果。
// 构建MQClientId
String clientId = clientConfig.buildMQClientId();
// 从clientId与MQClientInstance映射表factoryTable中获取当前clientId对应的MQClientInstance
MQClientInstance instance = this.factoryTable.get(clientId);
// 如果MQClientInstance不存在则创建一个新的并放入映射表factoryTable中
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;
}
3. // 将producer注册到MQClientInstance.producerTbale (这里用的是线程安全的ConcurrentMap,group为key,实例为value)
boolean registerOK = mQClientFactory.registerProducer(this.defaultMQProducer.getProducerGroup(), this);
private final ConcurrentMap producerTable = new ConcurrentHashMap();
public boolean registerProducer(final String group, final DefaultMQProducerImpl producer) {
if (null == group || null == producer) {
return false;
}
MQProducerInner prev = this.producerTable.putIfAbsent(group, producer);
if (prev != null) {
log.warn("the producer group[{}] exist already.", group);
return false;
}
return true;
}
4.// 注册成功则将当前生产者组对应的topic与发布关系放入topicPublishInfoTable注册表
// 缓存topic的路由信息
this.topicPublishInfoTable.put(this.defaultMQProducer.getCreateTopicKey(), new TopicPublishInfo());
5.关键类启动 mQClientFactory.start();
public void start() throws MQClientException {
// 同步当前实例
synchronized (this) {
switch (this.serviceState) {
case CREATE_JUST:
this.serviceState = ServiceState.START_FAILED;
// 如果没有指定,就会通过默认的接口去获取name server的地址
if (null == this.clientConfig.getNamesrvAddr()) {
this.mQClientAPIImpl.fetchNameServerAddr();
}
// 启动用于通讯的客户端,内部是用Netty实现的,启动client与name节点的通讯服务
this.mQClientAPIImpl.start();
// 启动所有调度服务
this.startScheduledTask();
// 拉消息服务
this.pullMessageService.start();
// 开启负载均衡消费队列服务
this.rebalanceService.start();
// 启动消费者
this.defaultMQProducer.getDefaultMQProducerImpl().start(false);
log.info("the client factory [{}] start OK", this.clientId);
this.serviceState = ServiceState.RUNNING;
break;
case START_FAILED:
throw new MQClientException("The Factory object[" + this.getClientId() + "] has been created before, and failed.", null);
default:
break;
}
}
}
这个方法是同步的
第一步会通过接口去获取NameServer地址
第二步启动用于通讯的客户端,内部是用Netty实现的,启动client与name节点的通讯服务。
第三步启动所有调度服务。底层开启很多定时任务来实现的。(以下五个:
2分钟发送请求来获取NameServer地址来动态更新NameServer地址/
30s 定时的从 NameServer 中获取 Topic、broker、queue 相关信息并更新
30s 定时清理无效的Broker,并向所有的Broker 发送心跳数据
5s 持久化消费偏移量(持久化到远程或者本地)
1s 调整push方式下的拉取线程数
)
第四步启动拉消息服务(开启一个线程)
public void run() {
log.info(this.getServiceName() + " service started");
while (!this.isStopped()) {
try {
//从LinkedBlockingQueue中拉取pullRequest
PullRequest pullRequest = this.pullRequestQueue.take();
this.pullMessage(pullRequest);
} catch (InterruptedException ignored) {
} catch (Exception e) {
log.error("Pull Message Service Run Method exception", e);
}
}
log.info(this.getServiceName() + " service end");
}
第五步开启负载均衡消费队列服务(开启一个线程)
@Override
public void run() {
log.info(this.getServiceName() + " service started");
while (!this.isStopped()) {
// 每隔20秒做一次负载均衡
this.waitForRunning(waitInterval);
this.mqClientFactory.doRebalance();
}
log.info(this.getServiceName() + " service end");
}
第六步启动消费者(这里再启动一次的疑问,等看了消费者启动过程就明白了,暂时先放着)
6.向所有 broker发送心跳
// 生产者启动时与某一个nameServer建立长连接,并定时从该nameServer拉取topic对应路由信息
// 生产者启动时与所有Broker建立长连接,并定时发送心跳请求,定时清除本地无效broker信息
// 向所有 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);
1、生产者启动的时候会检查相关配置:生产组是否配置, 必须配置且不能为null也不能为DEFAULT_PRODUCER
2、生产者实例名的设置,若未主动设置,则采用默认的配置, 生成的实例名(格式):pid@hostname
3、获得生产者的真正意义上的实例,并且以组名为key值, 将其缓存在缓存中(此处,生产者与组应是一一对应)
4、topicPublishInfoTable会初始化一个默认的topic信息,TBW102
5、MQ客户端实例开启
6、向所有的broker发送心跳