文章目录

  • 生产者详解
  • 生产者概述
  • 生产者高可用
  • 客户端保证
  • 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),如下图:

python rocketmq 生产消息 rocketmq生产者_中间件

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

生产者启动流程

生产者启动流程时序图如下:

python rocketmq 生产消息 rocketmq生产者_实例名_02

用户创建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启动流程结束