代码位置
下面我们来看一下一个简单的生产者是怎么执行程序的
我们来到方法org.apache.rocketmq.example.simple.Producer#main中
public class Producer {
public static void main(String[] args) throws MQClientException, InterruptedException {
// 将分组名命名为ProducerGroupName,并通过DefaultMQProducer的构造方法创建生产者
DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName");
// 指定注册中心的ip和port
producer.setNamesrvAddr("127.0.0.1:9876");
// 启动生产者,期间会开启和broker、namesrv的TCP连接,并开启心跳的定时任务
producer.start();
// 开始准备发送消息
try {
// 将消息包装为Message的对象
Message msg = new Message("TopicTest",
"TagA",
"OrderID188",
"Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET));
// 通过生产者发送被包装好的Message对象
SendResult sendResult = producer.send(msg);
System.out.printf("%s%n", sendResult);
} catch (Exception e) {
e.printStackTrace();
}
// 关闭生产者
producer.shutdown();
}
new DefaultMQProducer("xx")的过程
构造方法
进入构造函数,发现有很多构造函数的重载方法,该函数调用了另外一个构造函数
namespace很好理解,一般用来做一级业务划分。
producerGroup自然是用来做二级业务划分。
public DefaultMQProducer(final String producerGroup) {
// 参数分别为namespace、producerGroup、rpcHook
this(null, producerGroup, null);
}
调用勾子,给请求和响应追加代码片段,每当请求执行时,都会运行
public interface RPCHook {
void doBeforeRequest(final String remoteAddr, final RemotingCommand request);
void doAfterResponse(final String remoteAddr, final RemotingCommand request,
final RemotingCommand response);
}
重载后的构造方法
public DefaultMQProducer(final String namespace, final String producerGroup, RPCHook rpcHook) {
this.namespace = namespace;
this.producerGroup = producerGroup;
this.defaultMQProducerImpl = new DefaultMQProducerImpl(this, rpcHook);
}
初始化生产者执行器
public DefaultMQProducerImpl(final DefaultMQProducer defaultMQProducer, RPCHook rpcHook) {
this.defaultMQProducer = defaultMQProducer;
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());
}
});
}
producer.start()的过程
判断serviceState的状态
整体流程
我们可以看到运行start之后,首先会根据当前的serviceState类型,走不一样的逻辑分支
serviceState是类DefaultMQProducerImpl的一个属性,它的默认值是ServiceState.CREATE_JUST
public void start(final boolean startFactory) throws MQClientException {
switch (this.serviceState) {
case CREATE_JUST:
case RUNNING:
case START_FAILED:
case SHUTDOWN_ALREADY:
default:
break;
}
}
ServiceState枚举
ServiceState是生产者服务运行状态的枚举
public enum ServiceState {
/**
* Service just created,not start
*/
CREATE_JUST,
/**
* Service Running
*/
RUNNING,
/**
* Service shutdown
*/
SHUTDOWN_ALREADY,
/**
* Service Start failure
*/
START_FAILED;
}
过程解析
RUNNING、START_FAILED、SHUTDOWN_ALREADY
根据由简入深的流程,我们先来看看RUNNING、START_FAILED、SHUTDOWN_ALREADY等状态会做些什么。
如果处于RUNNING和START_FAILED状态,则直接跳过该逻辑
而面对SHUTDOWN_ALREADY的场景则会直接抛出异常,并提示客户当前的生产者服务异常,需要重新start一次
CREATE_JUST
CREATE_JUST做的事情,我这边就直接写注释注明一下了
switch (this.serviceState) {
case CREATE_JUST:
// 先给serviceState来个默认失败哈哈
this.serviceState = ServiceState.START_FAILED;
// 用来校验分组名是否符合规范
this.checkConfig();
// 这段逻辑的目的是让内部生产者的instanceName全局唯一,为了保证全局唯一还拼了一个纳米级别的时间
// 内部生产者的默认分组会是CLIENT_INNER_PRODUCER_GROUP,默认instanceName是DEFAULT,会在这里被改为全局唯一的时间
if (!this.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) {
this.defaultMQProducer.changeInstan,ceNameToPID();
}
// 这段代码是为了MQClientInstance的实例,放入我们的默认生产者和rpc勾子,rpc勾子是用来在处理请求数据和响应数据的勾子对象
// 获取MQClientInstance的实例时,会优先尝试在MQClientManager.factoryTable中去取MQClientInstance,如果取不到则会根据客户端的配置创建一个新的MQClientInstance实例
this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQProducer, rpcHook);
// MQClientInstance有一个ConcurrentMap<String, MQConsumerInner>类型的属性producerTable
// 这段代码的含义是将当前生产者放入线程安全的Map“producerTable”中,如果分组和生产者有一个为null或者该生产者在Map中已存在则返回false,然后就会抛出异常。
// 由此可知MQClientInstance是分组级别的单例
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);
}
// 将主题推送信息在topicPublishInfoTable Map中管理起来
this.topicPublishInfoTable.put(this.defaultMQProducer.getCreateTopicKey(), new TopicPublishInfo());
// 通过MQClientInstance启动生产者
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();
// 开启定时任务
RequestFutureHolder.getInstance().startScheduledTask(this);
定时任务解析
ScheduledExecutorService是一个可以执行定时任务、周期性任务和延迟任务的线程池
这里创建的线程池是
public synchronized void startScheduledTask(DefaultMQProducerImpl producer) {
this.producerSet.add(producer);
if (null == scheduledExecutorService) {
this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("RequestHouseKeepingService"));
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
RequestFutureHolder.getInstance().scanExpiredRequest();
} catch (Throwable e) {
log.error("scan RequestFutureTable exception", e);
}
}
}, 1000 * 3, 1000, TimeUnit.MILLISECONDS);
}
}
scheduleAtFixedRate的解析如下
/**
* @param command the task to execute
* @param initialDelay the time to delay first execution // 首次执行的延迟时间
* @param period the period between successive executions // 连续执行的间隔时间
* @param unit the time unit of the initialDelay and period parameters // 时间的单位
*/
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);