Rocket MQ系列三 - 生产者启动流程

项目启动时,rocketMQ发送失败 rocketMQ报错分析及解决:The producer service state not OK, CREATE_JUST 阅读详情

        DefaultMQProducer是RocketMQ中默认的生产者实现,DefaultMQProducer的类之间的继承关系如下所示: 

 可以看到这个生产者在实现时包含生产者的操作和配置属性,这是典型的类对象设计。

以下是一些核心属性:

namesrvAddr:继承自ClientConfig,表示RocketMQ集群的Namesrv地址,如果是多个则用分号分开。

clientIP:使用的客户端程序所在机器的IP地址,支持IPv4和IPv6,IPv4排除了本地的环回地址和私有内网地址。这了需要注意的是,如果Client运行在Docker容器中,获取的IP地址是容器所在得IP地址,而非宿主机的IP地址。

instanceName:实例名,每个实例都需要取唯一的名字,因为有时我们会在同一个机器上部署多个程序进程,如果名字有重复就会导致启动失败。

vipChannelEnabled:这是一个boolean值,表示是否开启VIP通道。VIP通道和非VIP通道区别是:在通信过程中使用的端口号不同。

clientCallBackExecutorThreads:客户端回调线程数。该参数表示Netty通信层回调线程的个数,默认值Runtime.getRuntime().availableProcessors() 表示当前CPU的有效个数。

pollNameServerInterval:获取Topic路由信息的间隔时长,单位为ms,默认为30000ms。

heartbeatBrokerInterval:与Broker心跳间隔的时长,单位为ms,默认为30000ms。

defaultMQProducerImpl:默认生产者的实现类,其中封装了Broker的各种API。如果你想自己实现一个生产者,可以添加一个新的实现,保持DefaultMQProducer对外接口不变,用户完全没有感知。

producerGroup:生产者组名,这是一个必须传递的参数。RocketMQ-way表示同一个生产者组中的生产者实例行为需要一致。

sendMsgTimeout:发送超时时间,单位为ms。

compressMsgBodyOverHowmuch:消息体的容量上限,超过该上限时消息体会通过ZIP进行压缩,该值默认为4MB。该参数在Client中是如何生效的呢?具体实现代码如下:

   private boolean tryToCompressMessage(final Message msg) {
        if (msg instanceof MessageBatch) {
            //batch dose not support compressing right now
            return false;
        }
        byte[] body = msg.getBody();
        if (body != null) {
            if (body.length >= this.defaultMQProducer.getCompressMsgBodyOverHowmuch()) {
                try {
                    byte[] data = UtilAll.compress(body, zipCompressLevel);
                    if (data != null) {
                        msg.setBody(data);
                        return true;
                    }
                } catch (IOException e) {
                    log.error("tryToCompressMessage exception", e);
                    log.warn(msg.toString());
                }
            }
        }

        return false;
    }

retryTimesWhenSendAsyncFailed:异步发送失败后重试的次数。默认是2次。异步重试是有条件的重试,并不是每次发送失败后都重试。

        以下是一些核心方法:

        start():这是启动整个生产者实例的入口,主要负责校验生产者的配置参数是否正确,并启动通信通道、各种定时计划任务、Pull服务、Rebalance服务、注册生产者到Broker等操作。

        shutdown():关闭本地已注册的生产者,关闭已注册到Broker的客户端。

        fetchPublishMessageQueue(Topic):获取一个Topic有哪些Queue。在发送消息、Pull消息时需要调用。

        send(Messgae msg):同步发送普通消息。

        send(Message msg, long timeout):同步发送普通消息(超时设置)

        send(Message msg,SendCallBack sendCallBack):异步发送普通消息

        send(Message msg,SendCallBack sendCallBack, long timeout):异步发送普通消息(超时设置)

        sendOneway(Message msg):发送单向消息。只负责发送消息,不管发送结果

        send(Message msg,MessageQueue mq):同步向指定队列发送消息

        send(Message msg,MessageQueue mq, long timeout):同步向指定队列发送消息(超时设置)

        同步向指定队列发送消息时,如果只有一个发送线程,在发送到某个指定队列中时,这个指定队列中的消息是有顺序的,那么就按照发送时间排序;如果某个Topic的队列都是这种情况,那么我们称该Topic的全部消息是分区有序的。

         send(Message msg,MessageQueue mq, SendCallBack sendCallBack):异步发送消到指定队列

        send(Message msg,MessageQueue mq, SendCallBack sendCallBack, long timeout):异步发送消到指定队列(超时设置)

        send(Message msg,MessageQueueSelector selector,Object arg,SendCallBack sendCallBack):自定义消息发送到指定队列。通过实现MessageQueueSelector接口来选择消息发送到哪个队列。

        send(Collection<Message> msgs):批量发送消息

        下面介绍两个核心管理接口

        createTopic(String key,String newTopic,int queueName):创建Topic

        viewMessage(String offsetMsgId):根据消息id查询消息内容

        生产者启动的流程比消费者启动的流程更加简单,一般用户使用DefaultMQProducer的构造函数构造一个生产者实例,并设置各种参数。比如Namesrv地址、生产者组等,调用start()方法启动生产者实例,start()方法调用了生产者默认实现类的start()方法启动,这里我们主要讲实现类的start()方法的实现:

    // 第一步   调用 DefaultMQProducer 默认构造方法
    public DefaultMQProducer(final String namespace, final String producerGroup, RPCHook rpcHook) {
        // 设置 namespace 和生产者组
        this.namespace = namespace;
        this.producerGroup = producerGroup;
        // 创建生产者默认实现
        defaultMQProducerImpl = new DefaultMQProducerImpl(this, rpcHook);
    }



    // 第二步调用start方法启动生产者实例
    public void start() throws MQClientException {
        this.setProducerGroup(withNamespace(this.producerGroup));
        // 调用默认实现的 start() 方法
        this.defaultMQProducerImpl.start();
        if (null != traceDispatcher) {
            try {
                traceDispatcher.start(this.getNamesrvAddr(), this.getAccessChannel());
            } catch (MQClientException e) {
                log.warn("trace dispatcher start failed ", e);
            }
        }
    }



    public void start(final boolean startFactory) throws MQClientException {
        // 服务状态 默认是创建状态,如果不是创建状态直接抛出异常
        switch (this.serviceState) {
            case CREATE_JUST:
                // 将生产者状态设置为启动失败
                this.serviceState = ServiceState.START_FAILED;

                // 参数校验
                this.checkConfig();

                // 默认生产者组名转换为 进程id
                if (!this.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) {
                    this.defaultMQProducer.changeInstanceNameToPID();
                }
                // 创建mq工厂
                this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQProducer, rpcHook);
                    
                // 注册生产者
                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);
                }

                this.topicPublishInfoTable.put(this.defaultMQProducer.getCreateTopicKey(), new TopicPublishInfo());

                // 注册本地路由信息
                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();
        
        // 启用定时任务,每秒扫描过期任务并移除
        this.startScheduledTask();

    }



   //  mQClientFactory.start();
  public void start() throws MQClientException {

        synchronized (this) {
            switch (this.serviceState) {
                case CREATE_JUST:
                    // 设置服务状态为启动失败
                    this.serviceState = ServiceState.START_FAILED;
                    // If not specified,looking address from name server
                    // 如果没有配置 namesrv 则远程获取
                    if (null == this.clientConfig.getNamesrvAddr()) {
                        this.mQClientAPIImpl.fetchNameServerAddr();
                    }
                    // Start request-response channel
                    // 启动通信模块服务
                    this.mQClientAPIImpl.start();
                    // Start various schedule tasks
                    // 启动各种定时任务
                    this.startScheduledTask();
                    // Start pull service
                    // 启动消息拉取服务
                    this.pullMessageService.start();
                    // Start rebalance service
                    // 启动 rebalanceService 服务
                    this.rebalanceService.start();
                    // Start push service
                    // 启动消息推送服务
                    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;
            }
        }
    }

        第一步:通过switch-case判断当前生产者的服务状态,创建时默认状态是CREATE_JUST,设置默认启动状态为启动失败。

        第二步:执行checkConfig()方法。校验生产者实例设置的各种参数。比如生产者组名是否为空,是否满足命名规则、长度是否满足等。

        第三步:执行changeInstanceNameToPID()方法。校验 instance name,如果是默认名字则将其修改为进程id。

        第四步:执行getAndCreateMQClientInstance()方法,根据生产者组名获取获者初始化一个MQClientInstance。初始化代码如下:

    public MQClientInstance getOrCreateMQClientInstance(final ClientConfig clientConfig, RPCHook rpcHook) {
        String clientId = clientConfig.buildMQClientId();
        MQClientInstance instance = this.factoryTable.get(clientId);
        if (null == instance) {
            // 创建客户端实例
            instance =
                new MQClientInstance(clientConfig.cloneClientConfig(),
                    this.factoryIndexGenerator.getAndIncrement(), clientId, rpcHook);
            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);
            }
        }

        return instance;
    }


    
    public String buildMQClientId() {
        StringBuilder sb = new StringBuilder();
        sb.append(this.getClientIP());

        sb.append("@");
        sb.append(this.getInstanceName());
        if (!UtilAll.isBlank(this.unitName)) {
            sb.append("@");
            sb.append(this.unitName);
        }

        return sb.toString();
    }

        MQClientInstance实例的功能是管理本实例中全部生产者与消费者的生产和消费行为。

        下面对该实例中变量进行说明

public class MQClientInstance {

    ......

    // 当前client实例的全部生产者的内部实例
    private final ConcurrentMap<String/* group */, MQProducerInner> producerTable = new ConcurrentHashMap<String, MQProducerInner>();

    // 当前client实例的全部消费者的内部实例
    private final ConcurrentMap<String/* group */, MQConsumerInner> consumerTable = new ConcurrentHashMap<String, MQConsumerInner>();

    // 当前client实例的全部管理实例
    private final ConcurrentMap<String/* group */, MQAdminExtInner> adminExtTable = new ConcurrentHashMap<String, MQAdminExtInner>();

    private final NettyClientConfig nettyClientConfig;
    
    // 其实每个client也是一个netty server,也会支持Broker访问,这里实现了全部client支持的接口
    private final MQClientAPIImpl mQClientAPIImpl;
    
    // 管理接口的本地实现类
    private final MQAdminImpl mQAdminImpl;

    // 当前生产者、消费者中全部Topic的本地缓存路由信息
    private final ConcurrentMap<String/* Topic */, TopicRouteData> topicRouteTable = new ConcurrentHashMap<String, TopicRouteData>();

    private final Lock lockNamesrv = new ReentrantLock();

    private final Lock lockHeartbeat = new ReentrantLock();

    private final ConcurrentMap<String/* Broker Name */, HashMap<Long/* brokerId */, 

String/* address */>> brokerAddrTable =
        new ConcurrentHashMap<String, HashMap<Long, String>>();

    private final ConcurrentMap<String/* Broker Name */, HashMap<String/* address */, Integer>> brokerVersionTable =
        new ConcurrentHashMap<String, HashMap<String, Integer>>();

    // 本地定时任务,比如定期获取当前 Namesrv 地址,定期同步Namesrv信息,定期更新Topic路由信息,定期发送心跳信息给Broker、定期清理已下线的Broker、定期持久化消费位点、定期调整消费线程数
    private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, "MQClientFactoryScheduledThread");
        }
    });

    // 请求的处理器,从处理方法 processRequest() 中我们可以知道目前支持哪些功能接口
    private final ClientRemotingProcessor clientRemotingProcessor;

    // Pull服务
    private final PullMessageService pullMessageService;

    // 重新平衡服务。定期指定重新平衡方法,
    private final RebalanceService rebalanceService;
    private final DefaultMQProducer defaultMQProducer;

    // 消费监控
    private final ConsumerStatsManager consumerStatsManager;
  
    public MQClientInstance(ClientConfig clientConfig, int instanceIndex, String clientId) {
        this(clientConfig, instanceIndex, clientId, null);
    }

    public MQClientInstance(ClientConfig clientConfig, int instanceIndex, String clientId, RPCHook rpcHook) {
        this.clientConfig = clientConfig;
        this.instanceIndex = instanceIndex;
        this.nettyClientConfig = new NettyClientConfig();
        this.nettyClientConfig.setClientCallbackExecutorThreads(clientConfig.getClientCallbackExecutorThreads());
        this.nettyClientConfig.setUseTLS(clientConfig.isUseTLS());
        this.clientRemotingProcessor = new ClientRemotingProcessor(this);
        this.mQClientAPIImpl = new MQClientAPIImpl(this.nettyClientConfig, this.clientRemotingProcessor, rpcHook, clientConfig);

        if (this.clientConfig.getNamesrvAddr() != null) {
            this.mQClientAPIImpl.updateNameServerAddressList(this.clientConfig.getNamesrvAddr());
            log.info("user specified name server address: {}", this.clientConfig.getNamesrvAddr());
        }

        this.clientId = clientId;

        this.mQAdminImpl = new MQAdminImpl(this);

        this.pullMessageService = new PullMessageService(this);

        this.rebalanceService = new RebalanceService(this);

        this.defaultMQProducer = new DefaultMQProducer(MixAll.CLIENT_INNER_PRODUCER_GROUP);
        this.defaultMQProducer.resetClientConfig(clientConfig);

        this.consumerStatsManager = new ConsumerStatsManager(this.scheduledExecutorService);

        log.info("Created a new client Instance, InstanceIndex:{}, ClientID:{}, ClientConfig:{}, ClientVersion:{}, SerializerType:{}",
            this.instanceIndex,
            this.clientId,
            this.clientConfig,
            MQVersion.getVersionDesc(MQVersion.CURRENT_VERSION), RemotingCommand.getSerializeTypeConfigInThisServer());
    }

  
   
    // 从多个Namesrv中获取最新Topic路由信息,更新本地缓存
    public void updateTopicRouteInfoFromNameServer() {
        Set<String> topicList = new HashSet<String>();

        // Consumer
        {
            Iterator<Entry<String, MQConsumerInner>> it = this.consumerTable.entrySet().iterator();
            while (it.hasNext()) {
                Entry<String, MQConsumerInner> entry = it.next();
                MQConsumerInner impl = entry.getValue();
                if (impl != null) {
                    Set<SubscriptionData> subList = impl.subscriptions();
                    if (subList != null) {
                        for (SubscriptionData subData : subList) {
                            topicList.add(subData.getTopic());
                        }
                    }
                }
            }
        }

        // Producer
        {
            Iterator<Entry<String, MQProducerInner>> it = this.producerTable.entrySet().iterator();
            while (it.hasNext()) {
                Entry<String, MQProducerInner> entry = it.next();
                MQProducerInner impl = entry.getValue();
                if (impl != null) {
                    Set<String> lst = impl.getPublishTopicList();
                    topicList.addAll(lst);
                }
            }
        }

        for (String topic : topicList) {
            this.updateTopicRouteInfoFromNameServer(topic);
        }
    }

    /**
     * Remove offline broker 检查Client是在Broker中有效
     */
    private void cleanOfflineBroker() {
        try {
            if (this.lockNamesrv.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS))
                try {
                    ConcurrentHashMap<String, HashMap<Long, String>> updatedTable = new ConcurrentHashMap<String, HashMap<Long, String>>();

                    Iterator<Entry<String, HashMap<Long, String>>> itBrokerTable = this.brokerAddrTable.entrySet().iterator();
                    while (itBrokerTable.hasNext()) {
                        Entry<String, HashMap<Long, String>> entry = itBrokerTable.next();
                        String brokerName = entry.getKey();
                        HashMap<Long, String> oneTable = entry.getValue();

                        HashMap<Long, String> cloneAddrTable = new HashMap<Long, String>();
                        cloneAddrTable.putAll(oneTable);

                        Iterator<Entry<Long, String>> it = cloneAddrTable.entrySet().iterator();
                        while (it.hasNext()) {
                            Entry<Long, String> ee = it.next();
                            String addr = ee.getValue();
                            if (!this.isBrokerAddrExistInTopicRouteTable(addr)) {
                                it.remove();
                                log.info("the broker addr[{} {}] is offline, remove it", brokerName, addr);
                            }
                        }

                        if (cloneAddrTable.isEmpty()) {
                            itBrokerTable.remove();
                            log.info("the broker[{}] name's host is offline, remove it", brokerName);
                        } else {
                            updatedTable.put(brokerName, cloneAddrTable);
                        }
                    }

                    if (!updatedTable.isEmpty()) {
                        this.brokerAddrTable.putAll(updatedTable);
                    }
                } finally {
                    this.lockNamesrv.unlock();
                }
        } catch (InterruptedException e) {
            log.warn("cleanOfflineBroker Exception", e);
        }
    }


    // 发送客户端的心跳信息给所有的Broker
    public void sendHeartbeatToAllBrokerWithLock() {
        if (this.lockHeartbeat.tryLock()) {
            try {
                this.sendHeartbeatToAllBroker();
                this.uploadFilterClassSource();
            } catch (final Exception e) {
                log.error("sendHeartbeatToAllBroker exception", e);
            } finally {
                this.lockHeartbeat.unlock();
            }
        } else {
            log.warn("lock heartBeat, but failed. [{}]", this.clientId);
        }
    }


    // 在本地注册一个消费者
    public synchronized boolean registerConsumer(final String group, final MQConsumerInner consumer) {
        if (null == group || null == consumer) {
            return false;
        }

        MQConsumerInner prev = this.consumerTable.putIfAbsent(group, consumer);
        if (prev != null) {
            log.warn("the consumer group[" + group + "] exist already.");
            return false;
        }

        return true;
    }

    // 取消本地注册的消费者
    public synchronized void unregisterConsumer(final String group) {
        this.consumerTable.remove(group);
        this.unregisterClient(null, group);
    }

    // 在本地注册一个生产者
    public synchronized boolean registerProducer(final String group, final DefaultMQProducerImpl producer) {
        if (null == group || null == producer) {
            return false;
        }

        MQProducerInner prev = this.producerTable.putIfAbsent(group, producer);
        if (prev != null) {
            log.warn("the producer group[{}] exist already.", group);
            return false;
        }

        return true;
    }

    // 取消本地注册的生产者
    public synchronized void unregisterProducer(final String group) {
        this.producerTable.remove(group);
        this.unregisterClient(group, null);
    }

    // 注册一个管理实例
    public boolean registerAdminExt(final String group, final MQAdminExtInner admin) {
        if (null == group || null == admin) {
            return false;
        }

        MQAdminExtInner prev = this.adminExtTable.putIfAbsent(group, admin);
        if (prev != null) {
            log.warn("the admin group[{}] exist already.", group);
            return false;
        }

        return true;
    }


    // 立即执行一次 rebalance
    public void rebalanceImmediately() {
        this.rebalanceService.wakeup();
    }

    // 对于所有已经注册的消费者实例,执行一次 rebalance
    public void doRebalance() {
        for (Map.Entry<String, MQConsumerInner> entry : this.consumerTable.entrySet()) {
            MQConsumerInner impl = entry.getValue();
            if (impl != null) {
                try {
                    impl.doRebalance();
                } catch (Throwable e) {
                    log.error("doRebalance exception", e);
                }
            }
        }
    }

 
    // 在本地缓存中查询master或salve信息
    public FindBrokerResult findBrokerAddressInAdmin(final String brokerName) {
        String brokerAddr = null;
        boolean slave = false;
        boolean found = false;

        HashMap<Long/* brokerId */, String/* address */> map = this.brokerAddrTable.get(brokerName);
        if (map != null && !map.isEmpty()) {
            for (Map.Entry<Long, String> entry : map.entrySet()) {
                Long id = entry.getKey();
                brokerAddr = entry.getValue();
                if (brokerAddr != null) {
                    found = true;
                    if (MixAll.MASTER_ID == id) {
                        slave = false;
                    } else {
                        slave = true;
                    }
                    break;

                }
            } // end of for
        }

        if (found) {
            return new FindBrokerResult(brokerAddr, slave, findBrokerVersion(brokerName, brokerAddr));
        }

        return null;
    }

    // 在本地缓存中查询master信息
    public String findBrokerAddressInPublish(final String brokerName) {
        HashMap<Long/* brokerId */, String/* address */> map = this.brokerAddrTable.get(brokerName);
        if (map != null && !map.isEmpty()) {
            return map.get(MixAll.MASTER_ID);
        }

        return null;
    }

    // 在本地缓存中查询salve信息
    public FindBrokerResult findBrokerAddressInSubscribe(
        final String brokerName,
        final long brokerId,
        final boolean onlyThisBroker
    ) {
        String brokerAddr = null;
        boolean slave = false;
        boolean found = false;

        HashMap<Long/* brokerId */, String/* address */> map = this.brokerAddrTable.get(brokerName);
        if (map != null && !map.isEmpty()) {
            brokerAddr = map.get(brokerId);
            slave = brokerId != MixAll.MASTER_ID;
            found = brokerAddr != null;

            if (!found && slave) {
                brokerAddr = map.get(brokerId + 1);
                found = brokerAddr != null;
            }

            if (!found && !onlyThisBroker) {
                Entry<Long, String> entry = map.entrySet().iterator().next();
                brokerAddr = entry.getValue();
                slave = entry.getKey() != MixAll.MASTER_ID;
                found = true;
            }
        }

        if (found) {
            return new FindBrokerResult(brokerAddr, slave, findBrokerVersion(brokerName, brokerAddr));
        }

        return null;
    }


    // 查找消费者id列表
    public List<String> findConsumerIdList(final String topic, final String group) {
        String brokerAddr = this.findBrokerAddrByTopic(topic);
        if (null == brokerAddr) {
            this.updateTopicRouteInfoFromNameServer(topic);
            brokerAddr = this.findBrokerAddrByTopic(topic);
        }

        if (null != brokerAddr) {
            try {
                return this.mQClientAPIImpl.getConsumerIdListByGroup(brokerAddr, group, clientConfig.getMqClientApiTimeout());
            } catch (Exception e) {
                log.warn("getConsumerIdListByGroup exception, " + brokerAddr + " " + group, e);
            }
        }

        return null;
    }

    // 通过 Topic 名字查找 Broker 地址
    public String findBrokerAddrByTopic(final String topic) {
        TopicRouteData topicRouteData = this.topicRouteTable.get(topic);
        if (topicRouteData != null) {
            List<BrokerData> brokers = topicRouteData.getBrokerDatas();
            if (!brokers.isEmpty()) {
                int index = random.nextInt(brokers.size());
                BrokerData bd = brokers.get(index % brokers.size());
                return bd.selectBrokerAddr();
            }
        }

        return null;
    }


    // 重置消费位点
    public synchronized void resetOffset(String topic, String group, Map<MessageQueue, Long> offsetTable) {
        DefaultMQPushConsumerImpl consumer = null;
        try {
            MQConsumerInner impl = this.consumerTable.get(group);
            if (impl != null && impl instanceof DefaultMQPushConsumerImpl) {
                consumer = (DefaultMQPushConsumerImpl) impl;
            } else {
                log.info("[reset-offset] consumer dose not exist. group={}", group);
                return;
            }
            consumer.suspend();

            ConcurrentMap<MessageQueue, ProcessQueue> processQueueTable = consumer.getRebalanceImpl().getProcessQueueTable();
            for (Map.Entry<MessageQueue, ProcessQueue> entry : processQueueTable.entrySet()) {
                MessageQueue mq = entry.getKey();
                if (topic.equals(mq.getTopic()) && offsetTable.containsKey(mq)) {
                    ProcessQueue pq = entry.getValue();
                    pq.setDropped(true);
                    pq.clear();
                }
            }

            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
            }

            Iterator<MessageQueue> iterator = processQueueTable.keySet().iterator();
            while (iterator.hasNext()) {
                MessageQueue mq = iterator.next();
                Long offset = offsetTable.get(mq);
                if (topic.equals(mq.getTopic()) && offset != null) {
                    try {
                        consumer.updateConsumeOffset(mq, offset);
                        consumer.getRebalanceImpl().removeUnnecessaryMessageQueue(mq, processQueueTable.get(mq));
                        iterator.remove();
                    } catch (Exception e) {
                        log.warn("reset offset failed. group={}, {}", group, mq, e);
                    }
                }
            }
        } finally {
            if (consumer != null) {
                consumer.resume();
            }
        }
    }

    // 获取一个订阅关系中每个队列的消费进度
    public Map<MessageQueue, Long> getConsumerStatus(String topic, String group) {
        MQConsumerInner impl = this.consumerTable.get(group);
        if (impl != null && impl instanceof DefaultMQPushConsumerImpl) {
            DefaultMQPushConsumerImpl consumer = (DefaultMQPushConsumerImpl) impl;
            return consumer.getOffsetStore().cloneOffsetTable(topic);
        } else if (impl != null && impl instanceof DefaultMQPullConsumerImpl) {
            DefaultMQPullConsumerImpl consumer = (DefaultMQPullConsumerImpl) impl;
            return consumer.getOffsetStore().cloneOffsetTable(topic);
        } else {
            return Collections.EMPTY_MAP;
        }
    }


    // 获取本地缓存Topic路由
    public ConcurrentMap<String, TopicRouteData> getTopicRouteTable() {
        return topicRouteTable;
    }

    // 直接将消息发送给指定的消费者消费,和正常投递不同的是,指定了已经订阅的消费者组中的一个,而不是全部。一般适用于在消费消息后,某一个消费者组想再消费一次的场景
    public ConsumeMessageDirectlyResult consumeMessageDirectly(final MessageExt msg,
        final String consumerGroup,
        final String brokerName) {
        MQConsumerInner mqConsumerInner = this.consumerTable.get(consumerGroup);
        if (null != mqConsumerInner) {
            DefaultMQPushConsumerImpl consumer = (DefaultMQPushConsumerImpl) mqConsumerInner;

            ConsumeMessageDirectlyResult result = consumer.getConsumeMessageService().consumeMessageDirectly(msg, brokerName);
            return result;
        }

        return null;
    }

    // 获取消费者的消费统计信息,包含消费RT,消费tps等
    public ConsumerRunningInfo consumerRunningInfo(final String consumerGroup) {
        MQConsumerInner mqConsumerInner = this.consumerTable.get(consumerGroup);
        if (mqConsumerInner == null) {
            return null;
        }

        ConsumerRunningInfo consumerRunningInfo = mqConsumerInner.consumerRunningInfo();

        List<String> nsList = this.mQClientAPIImpl.getRemotingClient().getNameServerAddressList();

        StringBuilder strBuilder = new StringBuilder();
        if (nsList != null) {
            for (String addr : nsList) {
                strBuilder.append(addr).append(";");
            }
        }

        String nsAddr = strBuilder.toString();
        consumerRunningInfo.getProperties().put(ConsumerRunningInfo.PROP_NAMESERVER_ADDR, nsAddr);
        consumerRunningInfo.getProperties().put(ConsumerRunningInfo.PROP_CONSUME_TYPE, mqConsumerInner.consumeType().name());
        consumerRunningInfo.getProperties().put(ConsumerRunningInfo.PROP_CLIENT_VERSION,
            MQVersion.getVersionDesc(MQVersion.CURRENT_VERSION));

        return consumerRunningInfo;
    }

   
}

Rocket MQ 集群搭建 集群搭建(三台机器56 57 58) 免密登录(56机器) 搭建(2主2从) 配置第一组broker-a 配置第二组broker-b 启动集群 ① 修改三个节点上的bin/runserver.sh,调整里面的jvm内存配置 ② 直接在三个节点上启动nameServer ③ 启动broker ④ 验证 搭建管理控制台 ① 下载地址:https://github.com/apache/rocketmq-externals ② 进入rocket-console目录,使用maven 阅读详情

相关推荐

Rocket MQ 完全入门手册

本手册旨在帮助读者体会Rocket MQ的工作流程,力求能使读者跟着本手册实操一遍之后,获得比单看理论更多的理解。

记录与分享 907

RocketMq报错MQClientExceptionThe producer service state not OK, maybe started once, RUNNING

用注解的形式配置时不用手动启动 producer,所以把producer.start() 的代码删除就OK了。我是用的@Bean配置DefaultMQProducer,把下面producer.start();

ibigboy 3725

RocKetMQ : MQClientException: The producer service state not OK, CREATE_JUST

org.apache.rocketmq.client.exception.MQClientException: The producer service state not OK, CREATE_JUST See http://rocketmq.apache.org/docs/faq/ for further details. at org.apache.rocketmq.client.imp...

九师兄 9040

RocketMQ—Producer(一)启动流程解密

以下为RocketMQ—生产者系列文章索引: 1:启动流程(本文) 2:路由动态更新 3:发送方式分析+消息类型区别 4:生产者消息的发送流程 5:路由队列选择,客户端冗错策略(问题答疑) 一、Producer介绍 Producer 是 RocketMQ 消息的投递者,负责生产消息。 它会与NameServer集群中的其中一个节点(随机)建立长连接(Keep-alive),定期从NameServer读取Topic路由信息,将路由信息保存在本地内存中; 它向提供Topic服务的M.

IT巅峰技术专栏 3037

均匀直线阵的常规波束形成matlab

常规波束形成概述 波束形成是将基阵各阵元输出进行一定的处理,从而形成基阵空间指向性的技术。一个波束形成器可以对空间某方位的信号有响应,而抑制其他方位的信号,所以又被称为空域滤波。一般分为常规波束形成(CBF)和自适应波束形成(ABF)。 常规波束形成较为基础,一般又可以根据实现的途径不同,分为时延波束形成和频移波束形成。 时延波束形成 在各阵元间添加时延使波束控制于不同方位的方法成为时延波束形成。设定一个目标方向,当信号来源方向在该目标方向时,响应最大;信号来源于其他方向时,响应较小。将这种响应大小

Xujing1143的博客 2万+

CocosCreator 面试题(十七)Cocos creator 固定宽度与固定高度的底层原理是什么?Cocos creator是如何做适配的?

通过固定宽度或固定高度的适配方式,可以使游戏在不同屏幕尺寸上保持一定的一致性,确保游戏中的元素的大小和位置相对稳定。具体选择哪种适配方式取决于你的游戏需求和设计考虑。在 Cocos Creator 中,你可以通过设置「设计分辨率」和使用相对坐标和布局方式来实现固定宽度或固定高度的适配。总之,Cocos Creator 提供了多种适配方式和工具,帮助你在不同屏幕尺寸上保持游戏画面的一致性。在 Cocos Creator 中,固定宽度和固定高度是两种常见的适配方式,用于在不同屏幕尺寸上保持游戏画面的一致性。

w风雨无阻w的专栏 1687

org.apache.rocketmq.client.exception.MQClientException: The producer service state not OK, maybe sta

org.apache.rocketmq.client.exception.MQClientException: The producer service state not OK, maybe started once, RUNNING See http://rocketmq.apache.org/docs/faq/ for further details. at org.apache.rock...

梦之马 8839

ICCompiler II实战:从NDM生成到APR流程的完整避坑指南

本文是一份针对ICCompiler II(ICC II)后端物理实现流程的实战避坑指南。文章详细解析了从NDM库生成、设计初始化、布局规划到时钟树综合与布线的完整APR流程,重点分享了各环节的常见陷阱、精细化配置命令与调试经验,旨在帮助工程师高效达成PPA目标,确保设计一次成功。

p4q5r6s7t的博客 531

Rocket MQ 详解

开这个专栏主要之前实习的时候经常用到Rocket MQ,大厂使用的也比较多,主要讲解一些关键原理和概念。 介绍 定义:分布式消息中间件。MQ是消息队列的意思。 特点:低延迟、高并发、高可用、高可靠。 解释一下“高可用”:通常是指,通过设计减少系统不能提供服务的时间。 假设系统一直能够提供服务,我们说系统的可用性是100%。 如果系统每运行100个时间单位,会有1个时间单位无法提供服务,我们说系统的可用性是99%。 核心概念 Topic:消息主题,我理解为与特定主题任务相关的一类消息。 生产者:生产消息.

strive for the best 的博客 1983

Coding Plan:程序员真实工作的可执行编码决策框架

Coding Plan 是一种面向工程实践的编码决策方法论,它超越传统学习路径,聚焦于代码在真实业务系统中的上下文对齐、影响控制与可演进性保障。其核心在于将模糊的开发直觉转化为可度量、可验证、可协作的日常动作,涵盖目标对齐、上下文锁定、验证闭环、协作契约和演进预留五大锚点。结合2026年AI辅助编码新范式,它强调将大模型作为‘上下文翻译器’而非代码生成器,并通过分钟级工作块、技术债主动发行、可观测性前置等机制,显著提升交付确定性与系统长期健康度。本文详解该框架的底层逻辑与落地实践。

weixin_33326218的博客 437

保姆级教程:从WOS下载文献到Citespace出图,手把手搞定科研可视化(附避坑指南)

本文提供从WOS下载文献到Citespace出图的完整科研可视化指南,涵盖数据采集、清洗、环境配置和可视化优化全流程。特别分享WOS检索技巧、数据清洗方法和Citespace参数设置,帮助科研新手高效生成专业图谱,避开常见技术陷阱。

weixin_29311017的博客 172

Spring boot 3.x 整合 Rocket MQ 遇到的那些事

spring boot 3 整合 Rocket MQ 遇到的那些事

HongSH945的博客 1717

CAN总线介绍及硬件设计

1. CAN是什么 CAN 是 Controller Area Network 的缩写(以下称为 CAN),是 ISO*1 国际标准化的串行通信协议。 在当前的汽车产业中,出于对安全性、舒适性、方便性、低公害、低成本的要求,各种各样的电子控制系统 被开发了出来。由于这些系统之间通信所用的数据类型及对可靠性的要求不尽相同,由多条总线构成的情况很 多,线束的数量也随之增加。为适应“减少线束的数量”、“......

sinat_15677011的博客 9991

启动rocketmq_Linux下搭建Rocket MQ服务

本文介绍如何在Linux环境下搭建Rocket MQ服务,内容包含安装需要的环境,安装步骤,启动服务,模拟消息发送和接收,停止服务以及在安装过程中遇到的问题。还是老规矩,先跟着操作,操作完之后就基本都会了,如果还有疑问,请在留言区评论,我会及时回复ps:文中插入了命令,某些地方排版看起来会比较乱,但是操作还是很方便的依赖的环境Rocket MQ安装需要依赖JDK1.8和maven使用java -v...

weixin_31417135的博客 962

038、OIS 光学防抖原理与调试:陀螺仪数据融合、Lens Shift OIS 的闭环控制

本文分享了OIS光学防抖系统的调试经验和关键技术要点。作者通过一个"呼吸感"Bug案例,揭示了陀螺仪数据融合和闭环控制的重要性。系统介绍了OIS的三个关键层次:传感器层(陀螺仪特性)、控制层(复合算法)和执行层(闭环控制)。重点讲解了陀螺仪数据融合的互补滤波实现、Lens Shift闭环控制的PID优化策略(包括积分抗饱和和微分滤波),以及前馈补偿的应用。最后总结了调试中的关键问题:数据同步、霍尔校准、温度补偿和性能评估方法,并给出实用建议:平衡算法复杂度与性能、预留带宽余量、重视产线校准等。文章强调OIS

sundong_81的博客 257

Win11更新导致TIA博途许可证出现问题的解决办法汇总

Win11更新导致TIA博途许可证出现问题的解决办法汇总

Robot_PLC_HMI_Automation 9890

18、Rocket MQ核⼼编程模型

这—章节,我们就来看下,在这两张图的基础上,如何编写合适的客户端代码,让我们在项⽬中⽤好。⽣产者要发送的消息⽐较多时,可以将多条消息合并成—个批量消息,—次性发送出去。因此,如果要重新发送消息,最好要带上唯—的系统标识,这样在消费者端,才能⾃⾏做幂等判断。⽽消息安全有两⽅⾯的要求,和⽤户⾃定义的属性,都是随着消息—起传递的,所以,消费者端是可以拿到消息的。的作⽤是可以在服务重启时,按照上—次消费的进度,处理后⾯没有消费过的消息。的机制,我们就可以在消息⽣产者发送完消息后,对发送失败的消息进⾏补救。

srrsheng的博客 1034

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

RocketMQ源码解析之消息生产者(启动流程)

文章目录1.消息生产者一些原理1.1 介绍本篇内容2.启动流程3.获取topic4.发送消息流程5.发送模式5.1 同步发送5.2异步发送5.3单向发送4.延时容错 1.消息生产者一些原理 在上篇《RocketMQ源码解析(搭建环境)》我们主要介绍了读源码的一些知识储备与源码环境搭建,在读源码的一些知识储备中介绍了一下RocketMQ的架构,我们知道RocketMQ 分为nameserv 与broker ,再就是客户端 也就是我们说的消息生产者与消息消费者。接下来我们分别来介绍下: nameserv :玩

猿上生活 2141

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

17、Rocket MQ快速实战以及核⼼概念详解

Rocket MQ是阿里巴巴开源的一个消息中间件,在阿里内部历经了双十一等很多高并发场景的考验,能够处理亿万级别的消息。2016年开源后捐赠给Apache,现在是Apache的—个顶级项目。早期阿里使用ActiveMQ ,但是,当消息开始逐渐增多后,ActiveMQ的IO性能很快达到了瓶颈。于是, 阿⾥开始关注Kafka。但是Kafka是针对日志收集场景设计的,他的高级功能并不是很贴合阿里的业务场景。

srrsheng的博客 1012

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

rocket mq的工作原理

##一、整体介绍 在RocketMQ里,有以下几个核心的模块:Producer,Consumer,Broker,NameSrv。他们之间的关系如下:  先简单了解一下各个模块的功能,下面会有章节详细介绍各个模块的功能。 Producer和Consumer很好理解,顾名思义就是生产者和消费者,生产者负责生产消息,消费者负责消费消息,这2块的逻辑都是由业务使用者定义的。 Br

七月流星 1万+

Rocket MQ 学习

RocketMQ学习笔记 rocketmq 是一款分布式,队列模型的开源消息件 。 一、RocketMQ 的特性 原生分布式 俩种消息拉取 严格消息顺序 特有的分布式协调器 亿级消息堆积 组(group) RocketMQ的基本概念 Producer : 消息生产者,负责产生消息,一般由业务系统负责产生消息。 ​ Consumer:消息消费者,负责消费消息,一般是后台异步系统负责异步消费。 ​ Push Consumer:封装消息拉取,消费进程和内部 *

勿忘初心的博客 1189

Rocket MQ简介

一、概念 1. 中间件:位于系统之间的服务 2. 消息中间件:消息队列MQ,用于接收消息、存储消息、转发消息的中间件 3. Rocket MQ: 分布式的消息中间件,生产者、消费者、队列都可以分布式 二、RocketMQ使用 1. 在服务器上安装Rocket MQ 2. 启动rocket mq,即name server,启动之后监听端口,等待broker\produce...

weixin_30730151的博客 217

docker-compose启动Rocket MQ及其GUI

"请输入您的Linux服务器IP地址 [默认: 192.168.213.8]: "1、创建脚本文件setup-rocketmq.sh。2、设置脚本文件为可执行权限。4、切换目录,执行yml文件。5、查看是否启动成功。

CodeTom的博客 464

【Rocket MQ】消费者启动流程

下图为基于 4.4.0-SNAPSHOT 版的源码画的消费者启动流程,具体见 DefaultMQPushConsumerImpl.start() 方法。

willdomequer的专栏 817

Rocket mq

其实每个NameServer节点之间是互相独立的,没有任何信息的交互,都是全量数据,每个Broker都会往3个NameServer同时注册,他们3个NameServer组成集群的唯一作用是客户端可以指定3个NameServer,如果有其中一个挂了会自动换成另一个NameServer。如果此时发1000条消息,那只有master才会接收到消息,slave只会负责从master上备份消息 ,那此时broker-b的从节点就不会接收消息,所以保证了安全性,但是服务的可用性就降低了【必然有一个取舍】

u014474768的博客 1401

Rocket MQ 集群

Rocket MQ 集群 集群特征 NameServer是一个几乎无状态节点,可集群部署,节点之间无任何信息同步。 Broker部署相对复杂,Broker分为Master与Slave,一个Master可以对应多个Slave,但是一个Slave只能对应一个Master,Master与Slave的对应关系通过指定相同的BrokerName,不同的BrokerId来定义,BrokerId为0表示Master,非0表示Slave。Master也可以部署多个。每个Broker与NameServer集群中的所有节点建立

酆都城博客 413
上一篇: Rocket MQ系列二 - 生产者原理
下一篇: Rocket MQ系列四 - 消息发送流程
geek-wmy
博客等级 码龄9年 322粉丝 126原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值