rocketmq生产者代码分析

  1. 环境安装
    参考http://rocketmq.apache.org/docs/quick-start/ ,配置环境变量
export NAMESRV_ADDR=localhost:9876

1.1. 安装监控控制台

clone代码https://github.com/apache/rocketmq-externals/tree/master/rocketmq-console ,编译即可生成rocketmq-console-ng-1.0.0.jar,运行即可,默认依赖NAMESRV_ADDR环境变量配置。

java -jar rocketmq-console-ng-1.0.0.jar

打开URL:http://localhost:8080/

  1. 非事务性生产者代码分析
    首先fork下rocketmq的代码,地址:https://github.com/apache/rocketmq。找到org.apache.rocketmq.example.quickstart.Producer类。
    2.1. 构造DefaultMQProducer
DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");

构造DefaultMQProducer对象,设置了生产者组名和DefaultMQProducerImpl实例变量。

2.2. 设置NamesrvAddr

producer.setNamesrvAddr("localhost:9876");

namesrvAddr为命名服务器的地址,多个以分号分割。

2.3. 启动生产者实例

producer.start();

内部委派调用defaultMQProducerImpl.start方法,然后内部调用方法。

public void start(final boolean startFactory) throws MQClientException;

第一步:this.checkConfig()

内部逻辑为producerGroup不能为空,不能有特殊字符,最大长度为255,不能为默认生产者组名DEFAULT_PRODUCER。

第二步:设置defaultMQProducer的instanceName为PID

第三步:获取MQClientInstance实例

这里使用单例对象MQClientManager管理MQClientInstance实例。根据传递的defaultMQProducer的配置(IP@PID)作为key获取实例,没有则创建一个,并缓存该对象。MQClientInstance封装了所有的远程通信模块,依赖netty。

public MQClientInstance(ClientConfig clientConfig, int instanceIndex, String clientId, RPCHook rpcHook);

上面构造方法设置了NettyClientConfig,ClientRemotingProcessor,MQClientAPIImpl,PullMessageService(拉取消息线程),RebalanceService(消费者负载分配线程),默认的DefaultMQProducer。

第四步:注册DefaultMQProducerImpl到MQClientInstance的producerTable里

第五步:mQClientFactory.start()

启动MQClientInstance,核心功能都在这里。

this.mQClientAPIImpl.start();

设置Netty客户端的参数,包括一些通道处理器NettyEncoder,NettyDecoder,NettyClientHandler,用于发送和接受请求。

this.startScheduledTask();

内部开启各种定时任务,定时获取NameServer的地址,定时从NameServer更新主题路由信息并计算出逻辑队列设置到每个消费者实例和生产者实例里,定时清理下线的Broker,定时发送心跳到所有的Broker(心跳数据包括客户端ID,消费者订阅数据,生产者组信息),定时持久化所有的消费队列的消费进度到Broker。

this.pullMessageService.start();

启动拉取消息线程,不停的从拉取请求队列获取PullRequest,进行指定队列的拉取,拉取采用异步请求(回掉类为PullCallback),默认一次拉取32条,拉取成功的话会按设置的消费模式(并发,顺序),回掉到我们设置的消费类处理。

this.rebalanceService.start();

启动负载均衡线程,默认20秒运行一次,获取订阅主题的所有逻辑队列,并且随机从一个Broker上(前面有每个客户端向所有Broker发送心跳)获取当前消费者组下所有的消费者,默认用AllocateMessageQueueAveragely平均分配策略,分配完会生成PullRequest设置到PullMessageService的请求队列里,以供消息拉取,其中PullRequest会设置上一次的拉取偏移量nextOffset,nextOffset首次从Broker上获取。

this.defaultMQProducer.getDefaultMQProducerImpl().start(false);

启动内部的DefaultMQProducerImpl。

第六步:this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();

目的维持tcp长连接,并且注册客户端信息到Broker,包括订阅信息,用于拉取消息的时候Broker根据tag设置过滤。

2.4. 发送消息

Message msg = new Message("TopicTest" /* Topic */,
"TagA" /* Tag */,
("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
);
SendResult sendResult = producer.send(msg);

内部委派调用defaultMQProducerImpl.send(msg),最终方法为

private SendResult sendDefaultImpl(
Message msg,
final CommunicationMode communicationMode,
final SendCallback sendCallback,
final long timeout
) throws MQClientException, RemotingException, MQBrokerException, InterruptedException

发送消息默认同步发送,超时时间3秒。

第一步:Validators.checkMessage(msg, this.defaultMQProducer);

发送消息校验,消息topic和内容的大小校验。

第二步:TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());

获取主题的发布队列信息,默认为轮询发送。

第三步:MessageQueue tmpmq = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName);

同步发送方式默认2次重试机会,先根据上一次的Broker选择下一个发送队列

第四步:sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout);

发送消息内核方法,首先获取要发送队列对应Broker的地址,设置消息的UNIQ_KEY属性(规则为PID+IP+时间戳+计数器),如果消息大于4K字节,则开启压缩。设置SendMessageRequestHeader消息头,包括生产者组,主题,队列ID,消息生成时间,消息属性等。生成请求头,请求code,设置到RemotingCommand请求对象里,然后同步发送,超时时间为3秒,并获取相应结果SendResult。发送的时候这里可能会失败,通过DefaultMQProducer.retryAnotherBrokerWhenNotStoreOK参数控制是否发送Broker获取响应结果为失败后重试,默认为false。

2.5. 关闭生产者实例

producer.shutdown();

内部委派调用到DefaultMQProducerImpl的shutdown方法

public void shutdown(final boolean shutdownFactory);

第一步:this.mQClientFactory.unregisterProducer(this.defaultMQProducer.getProducerGroup());

从MQClientInstance卸载当前生产者组,发送请求到所有Broker取消注册信息(之前发送心跳的时候就注册了客户端的信息,包括生产者和消费者)

第二步:this.mQClientFactory.shutdown();

尝试关闭MQClientInstance,当MQClientInstance没有注册的消费者和生产者的时候(MQClientInstance是IP@PID作为key公用的实例),执行关闭步骤,包括pullMessageService(拉取消息),scheduledExecutorService(调度任务使用),mQClientAPIImpl(远程通信模块),rebalanceService(消费者队列负载均衡),最后从MQClientManager单例移除MQClientInstance。

  1. 事务性生产者代码分析
    找到org.apache.rocketmq.example.transaction.TransactionProducer类。
    3.1. 构造TransactionMQProducer
TransactionMQProducer producer = new TransactionMQProducer("please_rename_unique_group_name");
producer.setCheckThreadPoolMinSize(2);
producer.setCheckThreadPoolMaxSize(2);
producer.setCheckRequestHoldMax(2000);
producer.setTransactionCheckListener(transactionCheckListener);

与非事务性不同的是,事务性生产者会有一个TransactionCheckListener(事务消息超时确认回查类),启动方式类似,只是会另外启动一个checkExecutor回查线程池。

3.2. 发送消息

未完待续。。。