文章目录
- 生产者详解
- 生产者概述
- 生产者高可用
- 客户端保证
- Broker端保证
- 生产者源码解析
- 生产者类
- 生产者启动流程
生产者详解
生产者概述
发送消息的一方被称作生产者
RocketMQ客户端中的生产者有两个独立的实现类:org.apache.rocketmq.client.producer.DefaultMQProducer
(用于生产普通消息、顺序消息、单向消息、批量消息、延迟消息)和org.apache.rocketmq.client.producer.TransactionMQProducer
(用于生产事务消息)
生产者高可用
RocketMQ通过以下机制保证Producer高可用
客户端保证
- 重试机制:支持RocketMQ同步、异步发送消息失败后选择其他Broker进行重试保证消息正常发送
通过配置 retryTimesWhenSendFailed 表示同步重试次数,默认2次(加上正常发送的1次,总计三次) - 客户端容错:RocketMQ Client会维护一个“Broker-发送延迟”关系,根据这个关系选择一个发送延迟级别较低的Broker来发送消息,尽量保证消息的正常发送。通过配置 sendLatencyFaultEnable 设置是否开启延迟容错,默认为false(即关闭)
Broker端保证
- 数据同步方式保证:通过Broker的主从复制(同步复制、异步复制),保证Broker数据一致性和Broker端高可用,从而使Producer高可用
生产者源码解析
生产者类
DefaultMQProducer
是RocketMQ中默认的生产者实现,继承了 ClientConfig
,实现了 MQProducer
(继承了 MQAdmin
),如下图:
ClientConfig
是记录客户端公共配置类,保存管理些公共属性配置,如上图所示:
- nameservAddr:RocketMQ集群的NameSrv地址,如果是多个用,隔开,通过配置rocketmq.namesrv.addr获取,如果没配置,则获取配置NAMESRV_ADDR的值
- clientIP:使用的客户端程序所在机器的IP地址,支持IPV4、IPV6(如果客户端在容器内,则获取的是容器所在的IP地址),优先IPV4地址
- instanceName:唯一实例名,通过rocketmq.client.name配置,默认值为 DEFAULT
- clientCallbackExecutorThreads:客户端回调线程数,表示Netty通信层回调线程的个数(默认使用当前CPU的有效个数)
- namespace:命名空间
- accessChannel:访问通道,如果需要将rocketmq服务迁移到云端,建议设置为“CLOUD”。 否则设置为“LOCAL”,特别是使用消息跟踪功能
- pollNameServerInterval:从NameServ拉取topic信息的间隔时长,默认30s
- heartbeatBrokerInterval:与Broker的心跳间隔,默认30s
- persistConsumerOffsetInterval:持久化消费者的Offset间隔,默认5s
- pullTimeDelayMillsWhenException:拉取消息发生异常时时间延迟,默认1s
- vipChannelEnabled:是否开启VIP通道,VIP通道和非VIP通道的区别在于通信过程中使用的端口号不同,VIP的端口在非VIP的端口下-2,默认不开启
- useTLS:判断客户端是否使用SSL,默认不开启,用于在两个通信应用程序之间提供保密性和数据完整性
DefaultMQProducer
是默认的生产者类,其包含的属性有:
- defaultMQProducerImpl:生产者真正的实现类,DefaultMQProducer的方法实现都是通过调用这个实现类对应的方法实现的
- producerGroup:生产者组,聚合有相同角色的所有生产者实例,在事务消息时尤为重要
- createTopicKey:默认值为 TBW102
- defaultTopicQueueNums:每个topic队列数,默认值为4个
- sendMsgTimeout:发送消息超时时间,默认3s
- compressMsgBodyOverHowmuch:消息体的容量最大上限,默认4MB,超出之后会通过ZIP进行压缩
- retryTimesWhenSendFailed:同步发送消息失败的重试次数,默认两次,加上正常的1次,总共发送消息3次
- retryTimesWhenSendAsyncFailed:异步发送消息失败的重试次数,默认两次,加上正常的1次,总共发送消息3次
- retryAnotherBrokerWhenNotStoreOK:是否在发送失败时重试时选择另一个broker,默认为false
- maxMessageSize:默认消息的最大大小:4M
- traceDispatcher:异步传输数据接口
MQProducer
定义了producer一系列必须的启动、关闭、发送消息等接口定义
MQAdmin
定义了MQ的基础接口:createTopic、searchOffset、maxOffset、minOffset、earliestMsgStoreTime、viewMessage、queryMessage
生产者启动流程
生产者启动流程时序图如下:
用户创建DefaultProducer实例对象,设置唯一的ProducerGroupName,nameservAddr等参数,然后调用**DefaultProducer#start()**方法,启动生产者。
ProducerGroup这个概念发送普通的消息时,作用不大,但是发送分布式事务消息时,比较关键,因为服务器会回查这个Group下的任意一个Producer
DefaultProducer#start()源码解析:
@Override
public void start() throws MQClientException {
// 设置生产者producerGroup
this.setProducerGroup(withNamespace(this.producerGroup));
// 调用真正的DefaultMQProducer启动接口
this.defaultMQProducerImpl.start();
if (null != traceDispatcher) {
try {
// 启动消息轨迹服务
traceDispatcher.start(this.getNamesrvAddr(), this.getAccessChannel());
} catch (MQClientException e) {
log.warn("trace dispatcher start failed ", e);
}
}
}
消息轨迹服务具体解析请看《消息轨道》----消息轨迹
ClientConfig#withNamespace(String resource)源码解析:
public String withNamespace(String resource) {
return NamespaceUtil.wrapNamespace(this.getNamespace(), resource);
}
public String getNamespace() {
if (StringUtils.isNotEmpty(namespace)) {
return namespace;
}
if (StringUtils.isNotEmpty(this.namesrvAddr)) {
// 判断nameservAddr是否匹配 ^(\w+://|)MQ_INST_\w+_\w+\..*
if (NameServerAddressUtils.validateInstanceEndpoint(namesrvAddr)) {
// 返回nameservAddr的uri
return NameServerAddressUtils.parseInstanceIdFromEndpoint(namesrvAddr);
}
}
return namespace;
}
NamespaceUtil#wrapNamespace(String namespace, String resourceWithOutNamespace)源码解析:
public static String wrapNamespace(String namespace, String resourceWithOutNamespace) {
if (StringUtils.isEmpty(namespace) || StringUtils.isEmpty(resourceWithOutNamespace)) {
return resourceWithOutNamespace;
}
// 如果resourceWithOutNamespace是系统定义的topic或者系统内部的消费者组,或者 resourceWithOutNamespace已经是以namespace开头,直接返回resourceWithOutNamespace
if (isSystemResource(resourceWithOutNamespace) || isAlreadyWithNamespace(resourceWithOutNamespace, namespace)) {
return resourceWithOutNamespace;
}
// 否则返回一个新的 以 {%RETRY%} + {%DLQ%} + namespace + % + 原resourceWithOutNamespace去除掉%RETRY%和%DLQ%的字符串
String resourceWithoutRetryAndDLQ = withOutRetryAndDLQ(resourceWithOutNamespace);
StringBuilder stringBuilder = new StringBuilder();
// {%RETRY%} 和 {%DLQ%} 需要判断原resourceWithOutNamespace是否存在这两个前缀
if (isRetryTopic(resourceWithOutNamespace)) {
stringBuilder.append(MixAll.RETRY_GROUP_TOPIC_PREFIX);
}
if (isDLQTopic(resourceWithOutNamespace)) {
stringBuilder.append(MixAll.DLQ_GROUP_TOPIC_PREFIX);
}
return stringBuilder.append(namespace).append(NAMESPACE_SEPARATOR).append(resourceWithoutRetryAndDLQ).toString();
}
为什么需要特殊处理producerGroup,因为需要处理重试和死信消息是,Broker需要将这些消息通过特定唯一的producerGroup发送到特定的消息队列中。设置完生产者的producerGroup后,便执行DefaultMQProducerImpl#start()方法,源码如下:
// 启动生产者实例
public void start() throws MQClientException {
this.start(true);
}
public void start(final boolean startFactory) throws MQClientException {
// 1、判断生产者的服务状态,新建的生产者的服务状态是 CREATE_JUST
switch (this.serviceState) {
// 如果是刚创建,还没有start
case CREATE_JUST:
// 2、设置serviceState为启动失败(START_FAILED)状态
this.serviceState = ServiceState.START_FAILED;
// 3、校验Producer的producerGroup
this.checkConfig();
// 如果producerGroup值不是客户端内部生产者
if (!this.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) {
// 4、修改实例名为进程id值
this.defaultMQProducer.changeInstanceNameToPID();
}
// 5、返回一个已存在或者新创建一个MQClientInstance,MQClientInstance实例与clientId是一一对应的,而clientId是由clientIp、instanceName及unitName构成
this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQProducer, rpcHook);
// 6、注册生产者,往MQClientInstance的Map producerTable中存放producer信息
boolean registerOK = mQClientFactory.registerProducer(this.defaultMQProducer.getProducerGroup(), this);
// 如果注册失败,即producer对应的producerGroup的key的producer已存在
if (!registerOK) {
// 设置serviceState为刚刚创建状态
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);
}
// 7、往topic路由信息AUTO_CREATE_TOPIC_KEY_TOPIC的路由信息
this.topicPublishInfoTable.put(this.defaultMQProducer.getCreateTopicKey(), new TopicPublishInfo());
if (startFactory) {
// 8、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;
}
// 9、发送心跳
this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();
this.startScheduledTask();
}
1、根据生产者的serviceState判断进行下一步,producerGroup的serviceState新创建时默认值为CREATE_JUST
2、先设置serviceState为START_FAILED,启动失败状态。这里先设置START_FAILED状态是为了避免多线程同一个producer调用多次start导致多次启动初始化,但是又不能选择RUNNING、SHUTDOWN_ALREADY,所以选择设置成START_FAILED
3、校验producerGroup是否为空、长度是否大于255,最长为255,判断producerGroup值是否符合规范:只允许a-zA-Z0-9_-%|
,是否与默认的ProducerGroup相同(默认的ProducerGroup值为DEFAULT_PRODUCER)
4、如果producerGroup值不是客户端内部生产者(值为CLIENT_INNER_PRODUCER),如果instanceName实例名等于DEFAULT,修改producer的实例名为 进程id值#系统时间,ClientConfig#changeInstanceNameToPID()源码如下
public void changeInstanceNameToPID() {
// 如果实例名是DEFAULT值,instanceName值可以通选项"rocketmq.client.name"配置
if (this.instanceName.equals("DEFAULT")) {
// 获取当前进程的id+#+系统时间作为实例名
this.instanceName = UtilAll.getPid() + "#" + System.nanoTime();
}
}
5、返回一个已存在或者新创建一个MQClientInstance,MQClientInstance实例与clientId是一一对应的,而clientId是由clientIp、instanceName及unitName构成。
MQClientManager#getOrCreateMQClientInstance(final ClientConfig clientConfig, RPCHook rpcHook)源码解析:
public MQClientInstance getOrCreateMQClientInstance(final ClientConfig clientConfig, RPCHook rpcHook) {
// 如果所有clientId一致则只会创建一个MQClientInstance实例
// 获取factoryTable的key值:clientId,由clientIp@instanceName{@unitName}组成
String clientId = clientConfig.buildMQClientId();
// 尝试根据clientId获取MQClientInstance实例
MQClientInstance instance = this.factoryTable.get(clientId);
// 如果没有则创建
if (null == instance) {
instance =
new MQClientInstance(clientConfig.cloneClientConfig(),
this.factoryIndexGenerator.getAndIncrement(), clientId, rpcHook);
// 将新创建的实例put到factoryTable中,如果已存在clientId,则返回旧的
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);
}
}
// 返回instance
return instance;
}
6、注册生产者,往MQClientInstance的Map producerTable中存放producer信息,如果producerGroup已经在MQClientInstance的producerTable(记录所有生产者实例的ConcurrentMap,已producerGroup作为key,MQProducerInner作为value)中,则返回false,表示producerGroup不唯一无法注册成功,进而抛出producerGroup不唯一的异常
7、往topic路由信息AUTO_CREATE_TOPIC_KEY_TOPIC的路由信息,topicPublishInfoTable存储了topic对应的路由信息(消息发送的时候需要根据topic找打对应的Broker),这里存储了一个key为TBW102,value为默认的TopicPublishInfo实例对象。
8、如果传入的startFactory为true,启动MQClientInstance。MQClientInstance#start()方法源码解析如下:
public void start() throws MQClientException {
synchronized (this) {
// 这里的serviceState是MQClientInstance的,也是默认初始化值为CREATE_JUST
switch (this.serviceState) {
case CREATE_JUST:
// 设置serviceState为START_FAILED,意图应该与上面的一致
this.serviceState = ServiceState.START_FAILED;
// If not specified,looking address from name server、
// 1、如果clientConfig的nameservAddr为空
if (null == this.clientConfig.getNamesrvAddr()) {
this.mQClientAPIImpl.fetchNameServerAddr();
}
// Start request-response channel
// 2、启动netty通信服务
this.mQClientAPIImpl.start();
// Start various schedule tasks
// 3、启动各种定时任务
this.startScheduledTask();
// Start pull service
// 4、启动消息拉取服务
this.pullMessageService.start();
// Start rebalance service
// 5、启动负载均衡服务
this.rebalanceService.start();
// Start push service
// 6、启动push服务,这里是MQClientInstance的defaultMQProducer
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;
}
}
}
(1)、如果clientConfig的nameservAddr为空,则手动拉取nameservAddr,这里的MQClientInstance的ClientConfig实例是上面 MQCLientManager#getOrCreateMQClientInstance方法(第5步)中初始化MQClientInstance传入clientConfig实例(实际就是用户创建的DefaultMQProducer实例)。MQClientAPIImpl#fetchNameServerAddr()方法则是从特定的url(默认是 http://jmenv.tbsite.net:8080/rocketmq/nsaddr)中拉取nameservAddr信息,如果拉取到的nameservAddr和本地的nameservAddr不一致,则更新NettyRemotingClient的namesrvAddrList列表(作用于通过netty基于nameservAddr构建channel发送mq消息)和MQClientAPIImpl的nameSrvAddr值,为下一步启动netty通信服务做准备。
(2)、启动netty通信服务:MQClientAPIImpl#start()方法实际调用的是NettyRemotingClient#start()方法:
@Override
public void start() {
// 线程池,大小默认为4,通过com.rocketmq.remoting.client.worker.size配置
this.defaultEventExecutorGroup = new DefaultEventExecutorGroup(
nettyClientConfig.getClientWorkerThreads(),
new ThreadFactory() {
private AtomicInteger threadIndex = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "NettyClientWorkerThread_" + this.threadIndex.incrementAndGet());
}
});
// netty的初始化
Bootstrap handler = this.bootstrap.group(this.eventLoopGroupWorker).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.SO_KEEPALIVE, false)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, nettyClientConfig.getConnectTimeoutMillis())
.option(ChannelOption.SO_SNDBUF, nettyClientConfig.getClientSocketSndBufSize())
.option(ChannelOption.SO_RCVBUF, nettyClientConfig.getClientSocketRcvBufSize())
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
if (nettyClientConfig.isUseTLS()) {
if (null != sslContext) {
pipeline.addFirst(defaultEventExecutorGroup, "sslHandler", sslContext.newHandler(ch.alloc()));
log.info("Prepend SSL handler");
} else {
log.warn("Connections are insecure as SSLContext is null!");
}
}
pipeline.addLast(
defaultEventExecutorGroup,
new NettyEncoder(),
new NettyDecoder(),
new IdleStateHandler(0, 0, nettyClientConfig.getClientChannelMaxIdleTimeSeconds()),
new NettyConnectManageHandler(),
new NettyClientHandler());
}
});
// 定时器,用于定时扫描netty的响应对象列表,超时清除,没有超时的执行回到函数
this.timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
try {
NettyRemotingClient.this.scanResponseTable();
} catch (Throwable e) {
log.error("scanResponseTable exception", e);
}
}
}, 1000 * 3, 1000);
if (this.channelEventListener != null) {
this.nettyEventExecutor.start();
}
}
(3)、启动各种定时任务,MQClientInstance#startScheduledTask()源码解析如下:
private void startScheduledTask() {
if (null == this.clientConfig.getNamesrvAddr()) {
// nameServer服务地址拉取定时任务
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
MQClientInstance.this.mQClientAPIImpl.fetchNameServerAddr();
} catch (Exception e) {
log.error("ScheduledTask fetchNameServerAddr exception", e);
}
}
}, 1000 * 10, 1000 * 60 * 2, TimeUnit.MILLISECONDS);
}
// 从nameServer拉取topic路由信息定时任务,间隔默认1000 * 30ms
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
MQClientInstance.this.updateTopicRouteInfoFromNameServer();
} catch (Exception e) {
log.error("ScheduledTask updateTopicRouteInfoFromNameServer exception", e);
}
}
}, 10, this.clientConfig.getPollNameServerInterval(), TimeUnit.MILLISECONDS);
// 定时清除下线broker,发送心跳
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
// 通过判断broker的addr是否存在nameServ的topic路由信息中,来移除brokerAddrTable中下线的broker
MQClientInstance.this.cleanOfflineBroker();
// 发送心跳到所有的Broker
MQClientInstance.this.sendHeartbeatToAllBrokerWithLock();
} catch (Exception e) {
log.error("ScheduledTask sendHeartbeatToAllBroker exception", e);
}
}
}, 1000, this.clientConfig.getHeartbeatBrokerInterval(), TimeUnit.MILLISECONDS);
// 定时持久化所有消费位点,用于消费者
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
MQClientInstance.this.persistAllConsumerOffset();
} catch (Exception e) {
log.error("ScheduledTask persistAllConsumerOffset exception", e);
}
}
}, 1000 * 10, this.clientConfig.getPersistConsumerOffsetInterval(), TimeUnit.MILLISECONDS);
// 定时调整PushConsumer的线程池,看实现应该是没有实现完
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
MQClientInstance.this.adjustThreadPool();
} catch (Exception e) {
log.error("ScheduledTask adjustThreadPool exception", e);
}
}
}, 1, 1, TimeUnit.MINUTES);
}
(4)、启动消息拉取服务,就是new一个 PullMessageService Thread对象并start
(5)、启动负载均衡服务,new一个 RebalanceService Thread对象并start
(6)、启动push服务,这里是MQClientInstance的defaultMQProducer,producerGroup为 CLIENT_INNER_PRODUCER,这里push模式的DefaultMQProducer是一个MQ内部的producer,用于自动将重试消息发送回给Broker
9、启动完MQClientInstance对象完成了producer的启动,接下来producer就会立即发送心跳到所有的broker注册信息,通过调用方法MQClientInstance#sendHeartbeatToAllBrokerWithLock()方法:
public void sendHeartbeatToAllBrokerWithLock() {
if (this.lockHeartbeat.tryLock()) {
try {
// 发送心跳到所有的broker,不向从服务器broker发送心跳
this.sendHeartbeatToAllBroker();
// 上传所有的filter过滤器文件信息
this.uploadFilterClassSource();
} catch (final Exception e) {
log.error("sendHeartbeatToAllBroker exception", e);
} finally {
this.lockHeartbeat.unlock();
}
} else {
log.warn("lock heartBeat, but failed. [{}]", this.clientId);
}
}
至此,producer启动流程结束