生产者启动分析
DefaultMQProducer
和 DefaultMQProducerImpl
DefaultMQProducer
门面 ==》 是生产者发送消息的入口。DefaultMQProducerImpl
实际干活的
官方示例
public static void main(String[] args) throws MQClientException, InterruptedException {
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();
}
继承关系
public class DefaultMQProducer extends ClientConfig implements MQProducer {
ClientConfig
客户端相关的配置信息(生产/消费)MQProducer
生产者抽象接口
public class DefaultMQProducerImpl implements MQProducerInner {
重点属性
// DefaultMQProducer
// 获取路由信息的间隔时间周期, 30s
private int pollNameServerInterval = 1000 * 30;
// 客户端与broker之间心跳周期 30s
private int heartbeatBrokerInterval = 1000 * 30;
// 消费者持久化消费进度的周期, 5秒
private int persistConsumerOffsetInterval = 1000 * 5;
private long pullTimeDelayMillsWhenException = 1000;
// 默认broker每个主题 创建的队列数量
private volatile int defaultTopicQueueNums = 4;
// 发送消息超时限制 默认 3s
private int sendMsgTimeout = 3000;
// 压缩阈值, 当msg body 超过4k后,选择使用压缩算法
private int compressMsgBodyOverHowmuch = 1024 * 4;
// 同步发送失败后,重试发送次数 , 2。 再加上第一次发送。 共3次
private int retryTimesWhenSendFailed = 2;
// 异步发送失败后,重试发送次数, 2
private int retryTimesWhenSendAsyncFailed = 2;
// 消息未存储成功, 是否选择其他broker节点进行消息重试,一般需要设置为true
private boolean retryAnotherBrokerWhenNotStoreOK = false;
// 消息体最大限制,默认值 4mb
private int maxMessageSize = 1024 * 1024 * 4; // 4M
// DefaultMQProducerImpl
// 主题发布信息映射表
// 主题:主题的发布信息
private final ConcurrentMap<String/* topic */, TopicPublishInfo> topicPublishInfoTable =
new ConcurrentHashMap<String, TopicPublishInfo>();
init(构造函数)
-
DefaultMQProducer
: 创建defaultMQProducerImpl
对象。 -
DefaultMQProducerImpl
- 创建异步消息线程池队列,长度五万
- 创建默认的 异步消息任务线程池。
public DefaultMQProducerImpl(final DefaultMQProducer defaultMQProducer, RPCHook rpcHook) { this.defaultMQProducer = defaultMQProducer; this.rpcHook = rpcHook; // 创建异步消息线程池任务队列,长度5w this.asyncSenderThreadPoolQueue = new LinkedBlockingQueue<Runnable>(50000); //创建缺省的 异步消息任务线程池 /* * @param corePoolSize 线程池核心数,平台核心数 * @param maximumPoolSize 线程池最大核心数,平台核心数 * @param keepAliveTime 空闲线程的存活时间,60s * @param unit 时间单位 * @param workQueue 异步消息线程池任务队列 * @param threadFactory 线程工厂,创建出来的线程 AsyncSenderExecutor_ 前缀 */ 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()
DefaultMQProducer
: 调用defaultMQProducerImpl
对象start()
方法。DefaultMQProducerImpl
- 自己维护了启动状态
serviceState
,避免重复调用 - 一顿检查
- 拿到或者创建
MQClientInstance
。 这里叫mQClientFactory
- 将自己(this)注册到
mQClientFactory
。 由mQClientFactory
统一管理。 - 默认一个主题信息("TBW102")
- 启动
mQClientFactory
如果 参数startFactory
为true的情况下。(一般为true) - 强制 rocketMq 客户端实例 向已知的broker节点发送一次心跳,(讲 客户端定时任务时,再聊)
- 定时任务,处理回执消息 =》 太慢的话,从列表中删掉。
- 自己维护了启动状态
回执消息
// request 发送的消息 需要 消费者 回执一条消息。
// 生产者 msg 加了一些信息, 关联ID,客户端ID , 发送到broker之后
// 消费者从 broker 拿到这条消息,检查msg类型, 发现是一个 需要回执的消息。
// 处理完消息之后,根据msg 关联ID 和 客户端ID 生产一条消息,(封装响应给 生产者的结果) 发送到broker
// broker 拿到这个消息后,它知道这是一条 回执消息,根据客户端id,找到channel, 将消息推送给 生产者。
// 生产者这边拿到 回执消息之后,读取出来 关联ID,找到对应的RequestFuture, 将阻塞的线程唤醒。
// 类似于 生产者和消费者 之间进行了一个RPC通信,只不过中间由 broker代理完成的。
//
MqClientInstance
客户端实例,一个JVM进程只有一个该实例。(好像4.9以后的版本改了)
重点属性
// 生产者,消费者 映射表。 组名 : 生产者,或者消费者
private final ConcurrentMap<String/* group */, MQProducerInner> producerTable = new ConcurrentHashMap<String, MQProducerInner>();
private final ConcurrentMap<String/* group */, MQConsumerInner> consumerTable = new ConcurrentHashMap<String, MQConsumerInner>();
private final ConcurrentMap<String/* group */, MQAdminExtInner> adminExtTable = new ConcurrentHashMap<String, MQAdminExtInner>();
// 客户端本地路由数据
// 主题名称: 主题路由数据
private final ConcurrentMap<String/* Topic */, TopicRouteData> topicRouteTable = new ConcurrentHashMap<String, TopicRouteData>();
// broker 物理节点映射表
// key : brokerName 逻辑层面的东西。
// val : HashMap<Long/* brokerId */, String/* address */>
// brokerId 0是master节点 其他是slave
// address 地址 ip:端口
private final ConcurrentMap<String/* Broker Name */, HashMap<Long/* brokerId */, String/* address */>> brokerAddrTable =
new ConcurrentHashMap<String, HashMap<Long, String>>();
// broker 物理节点版本映射表
// key : brokerName 逻辑层面的东西。
// val : HashMap<String/* address */, Integer>
// address 物理节点地址
// 版本号
private final ConcurrentMap<String/* Broker Name */, HashMap<String/* address */, Integer>> brokerVersionTable =
new ConcurrentHashMap<String, HashMap<String, Integer>>();
init(构造函数)
- 给属性赋值
/** * @param clientConfig 客户端配置 * @param instanceIndex index 一般是0 * @param clientId 客户端id * @param rpcHook rpc hook */
- 创建客户端协议处理器
- 创建API实现对象
mQClientAPIImpl
。>>> 核心的一个API实现,它几乎包含了所有服务端 的API, 它的作用就是将MQ业务层的数据 转换为 网络层的 RemotingCommand 对象/** * @param nettyClientConfig 客户端网络配置 * @param clientRemotingProcessor 客户端协议处理器,要注册到 客户端网络层、 * @param rpcHook rpc hook 要注册到网络层 * @param clientConfig 客户端配置 */ this.mQClientAPIImpl = new MQClientAPIImpl(this.nettyClientConfig, this.clientRemotingProcessor, rpcHook, clientConfig);
- 创建内部生产者实例,消息回退时使用。
start()
- 启动客户端网络层
this.mQClientAPIImpl.start();
- 启动定时任务
this.startScheduledTask()
- 还有一些消费者相关的内容,先跳过
MQClientAPIImpl
重点属性
/**
* 客户端网络层对象, 管理客户端 与 服务器之间 连接 NioSocketChannel 对象
* 通过它提供的 invoke 系列方法, 客户端可以与服务端进行远程调用。
* 服务器 也可以直接调用客户端
*/
private final RemotingClient remotingClient;
init(构造函数)
- 为属性赋值
- 创建网络层对象
// 创建网络层对象 /** * 网络层对象 * @param nettyClientConfig 客户端网络层配置 * @param channelEventListener null , 客户端并不关心 channel event */ this.remotingClient = new NettyRemotingClient(nettyClientConfig, null);
- 注册rpc hook
- 注册业务逻辑
start()
- 调用客户端网络层启动
this.remotingClient.start();
- 启动定时任务(细说)
- 消费者服务启动
- 负载均衡服务启动
- 内部生产者启动
定时任务
- 定时任务1: 从nameserver 更新客户端本地的路由数据。周期30秒
- 定时任务2: 清理下线的broker节点。向在线的broker节点发送心跳数据。周期30秒、
- 定时任务3: 消费者持久化 消费进度。周期5秒。(后面细说)
- 定时任务4: 动态调整消费者线程池。周期60秒。(后面细说)
NettyRemotingClient
客户端网络层封装, 在nameserver时,有一个NettyRetimgServer的类,是类似的
重点属性
// Channel 映射表 , key : 服务器地址 value : 客户端-服务端 channel 封装对象
private final ConcurrentMap<String /* addr */, ChannelWrapper> channelTables = new ConcurrentHashMap<String, ChannelWrapper>();
init(构造函数)
- 创建一些线程池,用来处理网络请求
start()
- 配置 netty 客户端启动类对象
- 定时扫描 ResposeFutureTable 中超时的ResponseFuture 避免 客户端线程 长时间 阻塞。
生产者发送消息分析
同步发送 DefaultMQProducer.send(Message msg)
异步发送 DefaultMQProducer.send(Message msg, SendCallback sendCallback)
同步和异步都是调用DefaultMQProducerImpl#sendDefaultImpl()
方法进行发送的。只是异步操作采用了线程池,我们直接看sendDefaultImpl
方法就好。
/**
* @param msg 消息
* @param timeout 发送超时时间 默认3秒
* @return
* @throws MQClientException
* @throws RemotingException
* @throws MQBrokerException
* @throws InterruptedException
*/
public SendResult send(Message msg, long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
// * @param msg 消息
// * @param communicationMode 模式 (同步)
// * @param sendCallback 回调,同步发送时,不需要提供这个参数,只有异步才需要
// * @param timeout 超时
return this.sendDefaultImpl(msg, CommunicationMode.SYNC, null, timeout);
}
/**
* It will be removed at 4.4.0 cause for exception handling and the wrong Semantics of timeout. A new one will be
* provided in next version
*
* @param msg
* @param sendCallback
* @param timeout the <code>sendCallback</code> will be invoked at most time
* @throws RejectedExecutionException
*/
@Deprecated
public void send(final Message msg, final SendCallback sendCallback, final long timeout)
throws MQClientException, RemotingException, InterruptedException {
final long beginStartTime = System.currentTimeMillis();
ExecutorService executor = this.getAsyncSenderExecutor();
try {
executor.submit(new Runnable() {
@Override
public void run() {
long costTime = System.currentTimeMillis() - beginStartTime;
if (timeout > costTime) {
try {
sendDefaultImpl(msg, CommunicationMode.ASYNC, sendCallback, timeout - costTime);
} catch (Exception e) {
sendCallback.onException(e);
}
} else {
sendCallback.onException(
new RemotingTooMuchRequestException("DEFAULT ASYNC send call timeout"));
}
}
});
} catch (RejectedExecutionException e) {
throw new MQClientException("executor rejected ", e);
}
}
sendDefaultImpl
- 检查生产者的状态
- 判断消息规格(Message对象的字段)
- 生成一个调用id,打日志用
- 获取当前消息 主题的发布信息, 需要依赖它里面的 MessageQueues 消息,选择一个队列, 后面去发送消息使用。
- 发送。有重试机制 // 发送总尝试次数, 同步模式 1+2 =3 。 异步模式 1。
- 选择一个MessageQueue。
- 调用核心方法
sendKernelImpl
// 核心方法 /* * @param msg 消息 * @param mq mq, 选择的队列 * @param communicationMode 发送模式 (同步,异步,单向) * @param sendCallback 异步发送时,需要传递一个回调处理对象。同步,单向时 为null * @param topicPublishInfo 主题发布信息 * @param timeout 超时限制 */ sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout - costTime);
- 根据发送模式不同,进行处理
- 异步或者单向发送
// 异步或者单向, 直接 返回null // 异步 返回值由sendCallback 和回调线程处理。 // 单向 服务器不返回任何数据
- 同步发送: 检查是否发送成功,失败的话尝试换一个broker节点发送。(是否切换,是个配置项。)
- 异步或者单向发送
sendKernelImpl
核心发送,组装数据,调用网络端