RocketMQ源码分析——消费端

长轮询 (RocketMQ使用)

Consumer -> Broker RocketMQ采用的长轮询建立连接

  • consumer的处理能力Broker不知道
  • 直接推送消息 broker端压力较大
  • 采用长连接有可能consumer不能及时处理推送过来的数据
  • pull主动权在consumer手里

短轮询

client不断发送请求到server,每次都需要重新连接

长轮询

client发送请求到server,server有数据返回,没有数据请求挂起不断开连接

长连接

连接一旦建立,永远不断开,push方式推送

消费端

主要从以下 5步操作 进行源码跟踪

  1. new出 DefaultMQPushConsumer
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer");
  1. 设置Namesrv地址
consumer.setNamesrvAddr("192.168.88.134:9876");
  1. 订阅topic,并进行过滤。( pullMessageService 启动后 ,会看到内部如何操作)
  • DefaultMQPushConsumer的方法由 defaultMQPushConsumerImpl类进行真正实现
  • 返回subscriptionData ,subExpression要么指定,要么为*
  • mQClientFactory会在 消费客户端启动后,向broker发送心跳包
consumer.subscribe("tagTopic", "TAG-A");

-----------------------------------------------------------------------------------------

public void subscribe(String topic, String subExpression) throws MQClientException {
	this.defaultMQPushConsumerImpl.subscribe(this.withNamespace(topic), subExpression);
    }
    
-----------------------------------------------------------------------------------------
    
this.rebalanceImpl.getSubscriptionInner().put(topic, subscriptionData);
    
-----------------------------------------------------------------------------------------

if (this.mQClientFactory != null) {
                this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();
            }
  1. 设置消息监听,并回调
consumer.registerMessageListener

默认情况下 这条消息只会被 一个consumer 消费到 点对点
message 状态修改 ( 由broker进行维护 )

ACK (重新投递) 返回消费状态--->CONSUME_SUCCESS 消费成功 || RECONSUME_LATER 消费失败,重新消费
  • 返回 Broker RECONSUME_LATER状态时
  • RocketMQ会把这批消息重发回Broker。(topic不是原topic而是这个消费租的RETRY topic 重发topic)
  • 在延迟的某个时间点(默认是10秒,业务可设置)后,再次投递到这个ConsumerGroup的另一个消费者。
  • 如果一直这样重复消费都持续失败到一定次数(默认16次),就会投递到DLQ死信队列。应用可以监控死信队列来做人工干预。
  1. 启动 消费客户端 ( 开启 traceDispatcher 追踪调度 )
consumer.start();
this.defaultMQPushConsumerImpl.start();
  • 针对 ServiceState 状态进行操作
    刚刚创建	CREATE_JUST,
    正在运行	RUNNING,
    已经关闭	SHUTDOWN_ALREADY,
    开启失败	START_FAILED;
  • 检查配置,获取订阅列表 SubscriptionData
this.checkConfig();
this.copySubscription();
  • 获取MQClient实例
this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQPushConsumer, this.rpcHook);
  • 注册消费者,并开启客户端
boolean registerOK = this.mQClientFactory.registerConsumer(this.defaultMQPushConsumer.getConsumerGroup(), this);
------------------------------------------------------------------------
this.mQClientFactory.start();

consumeMessageService启动

this.consumeMessageService.start();

MQClientInstance启动流程

this.mQClientAPIImpl.start();
this.startScheduledTask();
this.pullMessageService.start();
this.rebalanceService.start();
this.defaultMQProducer.getDefaultMQProducerImpl().start(false);

NettyRemotingClient启动

  • 启动Netty远程调用Client (4个工作线程),创建事件执行组并放入Netty管道中
  • NettyRemotingClient定时扫描 ResponseTable
  • 遍历responseTable ,ConcurrentHashMap类型 ,初始容量256
  • 对于超时请求 进行删除操作
				this.mQClientAPIImpl.start();
				private int clientWorkerThreads = 4;
				
----------------------------------------------------------------------------------------
 pipeline.addFirst(NettyRemotingClient.this.defaultEventExecutorGroup, "sslHandler", 					NettyRemotingClient.this.sslContext.newHandler(ch.alloc()));
                NettyRemotingClient.log.info("Prepend SSL handler");
-----------------------------------------------------------------------------------------

this.timer.scheduleAtFixedRate(new TimerTask() {
            public void run() {
                try {
                    NettyRemotingClient.this.scanResponseTable();
                } catch (Throwable var2) {
                    NettyRemotingClient.log.error("scanResponseTable exception", var2);
                }

            }
        }, 3000L, 1000L);
        
        protected final ConcurrentMap<Integer, ResponseFuture> responseTable = new ConcurrentHashMap(256);
        
        if (rf.getBeginTimestamp() + rf.getTimeoutMillis() + 1000L <= System.currentTimeMillis()) {
                rf.release();
                it.remove();
                rfList.add(rf);
                log.warn("remove timeout request, " + rf);
            }
  • channelEventListener 不为空, 开启nettyEventExecutor 事件执行器( 启动ServiceThread线程 )

  • org.apache.rocketmq.remoting.common 属于Netty的ServiceThread
    
if (this.channelEventListener != null) {
            this.nettyEventExecutor.start();
        }

startScheduledTask启动

  • 每120s 判断 NamesrvAddr地址,若为空,便去获取新的地址
 private void startScheduledTask() {
        if (null == this.clientConfig.getNamesrvAddr()) {
            this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
                public void run() {
                    try {
                        MQClientInstance.this.mQClientAPIImpl.fetchNameServerAddr();
                    } catch (Exception var2) {
                        MQClientInstance.this.log.error("ScheduledTask fetchNameServerAddr exception", var2);
                    }

                }
            }, 10000L, 120000L, TimeUnit.MILLISECONDS);
        }

pullMessageService启动 、 实现消息消费 ( 重点 )

  • org.apache.rocketmq.common 属于Rocketmq的ServiceThread
    
public void start() {
        log.info("Try to start service thread:{} started:{} lastThread:{}", new Object[]{this.getServiceName(), this.started.get(), this.thread});
        if (this.started.compareAndSet(false, true)) {
            this.stopped = false;
            this.thread = new Thread(this, this.getServiceName());
            this.thread.setDaemon(this.isDaemon);
            this.thread.start();
        }
    }
final LinkedBlockingQueue<PullRequest> pullRequestQueue = new LinkedBlockingQueue();
  • 拉取消息服务
public void run() {
        this.log.info(this.getServiceName() + " service started");

        while(!this.isStopped()) {
            try {
                PullRequest pullRequest = (PullRequest)this.pullRequestQueue.take();
                
-----------------------------------------------------------------------------------------
                this.pullMessage(pullRequest);
-----------------------------------------------------------------------------------------

            } catch (InterruptedException var2) {
                ;
            } catch (Exception var3) {
                this.log.error("Pull Message Service Run Method exception", var3);
            }
        }

        this.log.info(this.getServiceName() + " service end");
    }
  • LinkedBlockingQueue pullRequestQueue 拉取队列中取出一个拉取请求
  • 获取AtomicInteger、可中断的ReentrantLock重入锁
  • lockInterruptibly(); 可中断重入锁 (一旦检测到中断请求,方法返回不再参与锁竞争,直接抛出中断异常)
public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();
        try {
            while (count.get() == 0) {
                notEmpty.await();
            }
            x = dequeue();
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }
  • dequeue 出队列,出一个PullRequest拉取请求

  • PullRequest拉取请求包括:消费组,messageQueue(元消息队列包括:topic、brokerName、queueId )

  • processQueue 处理队列主要包括:(TreeMap<Long, MessageExt> 存放消息)

  • private String consumerGroup;
    private MessageQueue messageQueue;
    private ProcessQueue processQueue;
    private long nextOffset;
    
private E dequeue() {
        // assert takeLock.isHeldByCurrentThread();
        // assert head.item == null;
        Node<E> h = head;
        Node<E> first = h.next;
        h.next = h; // help GC
        head = first;
        E x = first.item;
        first.item = null;
        return x;
    }
  • 拉取消息服务中 this.pullMessage(pullRequest);
  • 获取消费者,准备进行processQueue 消费
 private void pullMessage(PullRequest pullRequest) {
 
-----------------------------------------------------------------------------------------
        MQConsumerInner consumer = this.mQClientFactory.selectConsumer(pullRequest.getConsumerGroup());
-----------------------------------------------------------------------------------------

        if (consumer != null) {
            DefaultMQPushConsumerImpl impl = (DefaultMQPushConsumerImpl)consumer;
            
-----------------------------------------------------------------------------------------
            impl.pullMessage(pullRequest);
-----------------------------------------------------------------------------------------
        } else {
            this.log.warn("No matched consumer for the PullRequest {}, drop it", pullRequest);
        }
    }
  • 拉取采用异步回调方法,onSuccess( PullResult pullResult )

  • pullResult.getMsgFoundList() 结果为 List<MessageExt> msgFoundList
    
  • submitConsumeRequest 两个实现 ConcurrentlyService 和 OrderlyService 多线程消费和顺序消费

  • executePullRequestImmediately ,将pullRequest put () pullRequestQueue 中

PullCallback pullCallback = new PullCallback()
public void onSuccess(PullResult pullResult) {
switch(pullResult.getPullStatus()) {
	case FOUND:

pullRequest.setNextOffset(pullResult.getNextBeginOffset());

DefaultMQPushConsumerImpl.this.consumeMessageService.submitConsumeRequest(pullResult.getMsgFoundList(), processQueue, pullRequest.getMessageQueue(), dispatchToConsume);

-----------------------------------------------------------------------------------------
if (DefaultMQPushConsumerImpl.this.defaultMQPushConsumer.getPullInterval() > 0L) {        DefaultMQPushConsumerImpl.this.executePullRequestLater(pullRequest, DefaultMQPushConsumerImpl.this.defaultMQPushConsumer.getPullInterval());
                                            } else {
 DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest);
                                            }
-----------------------------------------------------------------------------------------                               
 this.pullAPIWrapper.pullKernelImpl(pullRequest.getMessageQueue(), subExpression, subscriptionData.getExpressionType(), subscriptionData.getSubVersion(), pullRequest.getNextOffset(), this.defaultMQPushConsumer.getPullBatchSize(), sysFlag, commitOffsetValue, 15000L, 30000L, CommunicationMode.ASYNC, pullCallback);
 	}
 }
  • this.pullAPIWrapper.pullKernelImpl + pullCallback 回调方法 处理拉取到消息PullResult
public class PullResult {
    private final PullStatus pullStatus;
    private final long nextBeginOffset;
    private final long minOffset;
    private final long maxOffset;
    private List<MessageExt> msgFoundList;
  • ConcurrentlyService 并发消费服务

  • 并发消费 和 顺序消费 run执行体 大体相同,

  • 主要区别在于:生产者向指定queue队列发送消息,跟普通消息相比,顺序消息的使用需要在producer的send()方法中添加MessageQueueSelector接口的实现类,并重写select选择使用的队列,因为顺序消息局部顺序,需要将所有消息指定发送到同一队列中。

  • 消费者 设置最大最小线程数为1,并实现MessageListenerOrderly 接口进行消息消费


  • msgs 小于等于 consumeMessageBatchMaxSize ,new出 consumeRequest , 在线程池消费

  • if (msgs.size() <= consumeBatchSize) 
    
  • 若大于consumeMessageBatchMaxSize ,每次只能消费consumeMessageBatchMaxSize 数量的消息

  • private String consumerGroup;
    private List&lt;MessageExt&gt; msgList;
    private MessageQueue mq;
    private boolean success;
    private String status;
    private Object mqTraceContext;
    private Map&lt;String, String&gt; props;
    private String namespace;
    
  • 并发消费线程池

this.consumeExecutor = new ThreadPoolExecutor(this.defaultMQPushConsumer.getConsumeThreadMin(), this.defaultMQPushConsumer.getConsumeThreadMax(), 60000L, TimeUnit.MILLISECONDS, this.consumeRequestQueue, new ThreadFactoryImpl("ConsumeMessageThread_"));
  • MessageListenerConcurrently 并发消费监听接口
  • public interface MessageListenerConcurrently extends MessageListener {
        ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> var1, ConsumeConcurrentlyContext var2);
    }
    
MessageListenerConcurrently listener = ConsumeMessageConcurrentlyService.this.messageListener;
                ConsumeConcurrentlyContext context = new  
                ConsumeConcurrentlyContext(this.messageQueue);
                ConsumeConcurrentlyStatus status = null;
                ConsumeMessageConcurrentlyService.this.defaultMQPushConsumerImpl.resetRetryAndNamespace(this.msgs,ConsumeMessageConcurrentlyService.this.defaultMQPushConsumer.getConsumerGroup());
                ConsumeMessageContext consumeMessageContext = null;
     if (ConsumeMessageConcurrentlyService.this.defaultMQPushConsumerImpl.hasHook()) {
                    consumeMessageContext = new ConsumeMessageContext();
                    consumeMessageContext.setNamespace(ConsumeMessageConcurrentlyService.this.defaultMQPushConsumer.getNamespace());
                    consumeMessageContext.setConsumerGroup(ConsumeMessageConcurrentlyService.this.defaultMQPushConsumer.getConsumerGroup());
                    consumeMessageContext.setProps(new HashMap());
                    consumeMessageContext.setMq(this.messageQueue);
                    consumeMessageContext.setMsgList(this.msgs);
                    consumeMessageContext.setSuccess(false);
                    ConsumeMessageConcurrentlyService.this.defaultMQPushConsumerImpl.executeHookBefore(consumeMessageContext);
                }
                long beginTimestamp = System.currentTimeMillis();
                boolean hasException = false;
                ConsumeReturnType returnType = ConsumeReturnType.SUCCESS;
  • OrderlyService 顺序消费服务

  • new出 consumeRequest , 在线程池消费

  • ConsumeMessageOrderlyService.ConsumeRequest consumeRequest = new ConsumeMessageOrderlyService.ConsumeRequest(processQueue, messageQueue);
    
  • consumeExecutor 顺序消费线程池 执行 consumeRequest

  • this.consumeExecutor.submit(consumeRequest);
    
  • this.consumeExecutor = new ThreadPoolExecutor(this.defaultMQPushConsumer.getConsumeThreadMin(), this.defaultMQPushConsumer.getConsumeThreadMax(), 60000L, TimeUnit.MILLISECONDS, this.consumeRequestQueue, new ThreadFactoryImpl("ConsumeMessageThread_"));
    
  • consumeRequest实现Runnable接口,下面是它的run()方法

  • class ConsumeRequest implements Runnable {
        private final ProcessQueue processQueue;
        private final MessageQueue messageQueue;
    
  • 取出List 消息集合

List<MessageExt> msgs = this.processQueue.takeMessags(consumeBatchSize);
  • resetRetryAndNamespace 过滤重投消息
ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.resetRetryAndNamespace(msgs, ConsumeMessageOrderlyService.this.defaultMQPushConsumer.getConsumerGroup());
  • 遍历 消息集合, 找出属性为RETRY_TOPIC 重投的消息 , 设置该消息的topic

  • String retryTopic = msg.getProperty("RETRY_TOPIC");
    if (retryTopic != null && groupTopic.equals(msg.getTopic())) {
        msg.setTopic(retryTopic);
    }
    
    if (StringUtils.isNotEmpty(this.defaultMQPushConsumer.getNamespace())) {
        msg.setTopic(NamespaceUtil.withoutNamespace(msg.getTopic(), this.defaultMQPushConsumer.getNamespace()));
    }
    
if (!msgs.isEmpty()) {
          ConsumeOrderlyContext context = new ConsumeOrderlyContext(this.messageQueue);
          ConsumeOrderlyStatus status = null;
          ConsumeMessageContext consumeMessageContext = null;
       if (ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.hasHook()) {
                consumeMessageContext = new ConsumeMessageContext();
                                        consumeMessageContext.setConsumerGroup(ConsumeMessageOrderlyService.this.defaultMQPushConsumer.getConsumerGroup());
                                        consumeMessageContext.setNamespace(ConsumeMessageOrderlyService.this.defaultMQPushConsumer.getNamespace());
                consumeMessageContext.setMq(this.messageQueue);
                consumeMessageContext.setMsgList(msgs);
                consumeMessageContext.setSuccess(false);
                consumeMessageContext.setProps(new HashMap());
                                        ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.executeHookBefore(consumeMessageContext);
                         }

              long beginTimestamp = System.currentTimeMillis();
              ConsumeReturnType returnType = ConsumeReturnType.SUCCESS;
  • MessageListenerOrderly 顺序消费监听接口 继承了 messageListener 接口

  • MessageListenerOrderly 就是 我们在设置 监听订阅时 回调用的接口,重写此方法进行消息消费

  • public interface MessageListenerOrderly extends MessageListener {
        ConsumeOrderlyStatus consumeMessage(List<MessageExt> var1, ConsumeOrderlyContext var2);
    }
    
  • 就是这一行,如果重写乐监听接口,就能消费消息

-----------------------------------------------------------------------------------------
status = ConsumeMessageOrderlyService.this.messageListener.consumeMessage(
    Collections.unmodifiableList(msgs), context  );
-----------------------------------------------------------------------------------------
  • 消息在消费前后 executeHookBefore,executeHookAfter ( Hook进行调用 )
    if (ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.hasHook()) {
                                        consumeMessageContext.setStatus(status.toString());
                                        consumeMessageContext.setSuccess(ConsumeOrderlyStatus.SUCCESS == status || ConsumeOrderlyStatus.COMMIT == status);
                                        ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.executeHookAfter(consumeMessageContext);
                                    }

rebalanceService启动

  • 和pullMessageService一样启动 Rocketmq的ServiceThread

  • 同一个抽象类 rebalanceService 和 pullMessageService 为具体实现

  • public abstract class ServiceThread implements Runnable

  • 等待间隔

private static long waitInterval = Long.parseLong(System.getProperty("rocketmq.client.rebalance.waitInterval", "20000"));
  public void run() {
        this.log.info(this.getServiceName() + " service started");

        while(!this.isStopped()) {
            this.waitForRunning(waitInterval);
            this.mqClientFactory.doRebalance();
        }
    
        this.log.info(this.getServiceName() + " service end");
    }
  • 进行负载
public void doRebalance() {
        Iterator var1 = this.consumerTable.entrySet().iterator();

        while(var1.hasNext()) {
            Entry<String, MQConsumerInner> entry = (Entry)var1.next();
            MQConsumerInner impl = (MQConsumerInner)entry.getValue();
            if (impl != null) {
                try {
                    impl.doRebalance();
                } catch (Throwable var5) {
                    this.log.error("doRebalance exception", var5);
                }
            }
        }
 public void doRebalance() {
        if (!this.pause) {
            this.rebalanceImpl.doRebalance(this.isConsumeOrderly());
        }

    }
  • 获取订阅列表
  • 广播和集群两种模式 BROADCASTING: CLUSTERING:
private void rebalanceByTopic(String topic, boolean isOrder) {
        Set mqSet;

-----------------------------------------------------------------------------------------
广播模式::::
        switch(this.messageModel) {
        case BROADCASTING:
            mqSet = (Set)this.topicSubscribeInfoTable.get(topic);
            if (mqSet != null) {
-----------------------------------------------------------------------------------------

清理不重要的消息 ( 同一个topic下,清理没有在topicSubscribeInfoTable订阅列表中的MessageQueue )  
boolean changed = this.updateProcessQueueTableInRebalance(topic, mqSet, isOrder);

-----------------------------------------------------------------------------------------
   if (changed) {
     this.messageQueueChanged(topic, mqSet, mqSet);
     log.info("messageQueueChanged {} {} {} {}", new Object[]{this.consumerGroup, topic, mqSet, mqSet});
            }
            } else {
     log.warn("doRebalance, {}, but the topic[{}] not exist.", this.consumerGroup, topic);
            }
            break;
            
  • 集群模式下:::
  • 获取同一个 消费组中 ,订阅同一个topic的 消费者列表
集群模式::::
      case CLUSTERING:
      mqSet = (Set)this.topicSubscribeInfoTable.get(topic);
  List<String> cidAll = this.mQClientFactory.findConsumerIdList(topic, this.consumerGroup);
            if (null == mqSet && !topic.startsWith("%RETRY%")) {
      log.warn("doRebalance, {}, but the topic[{}] not exist.", this.consumerGroup, topic);
            }

            if (null == cidAll) {
     log.warn("doRebalance, {} {}, get consumer id list failed", this.consumerGroup, topic);
            }

            if (mqSet != null && cidAll != null) {
                List<MessageQueue> mqAll = new ArrayList();
                mqAll.addAll(mqSet);
                Collections.sort(mqAll);
                Collections.sort(cidAll);
  AllocateMessageQueueStrategy strategy = this.allocateMessageQueueStrategy;
                List allocateResult = null;
  • allocateMessageQueueStrategy 分配消息队列策略
  • 为当前消费端 分配消息队列MessageQueue
                try {
         allocateResult = strategy.allocate(this.consumerGroup, this.mQClientFactory.getClientId(), mqAll, cidAll);
                } catch (Throwable var10) {
                    log.error("AllocateMessageQueueStrategy.allocate Exception. allocateMessageQueueStrategyName={}", strategy.getName(), var10);
                    return;
                }

                Set<MessageQueue> allocateResultSet = new HashSet();
                if (allocateResult != null) {
                    allocateResultSet.addAll(allocateResult);
                }

           boolean changed = this.updateProcessQueueTableInRebalance(topic, allocateResultSet, isOrder);
                if (changed) {
                    log.info("rebalanced result changed. allocateMessageQueueStrategyName={}, group={}, topic={}, clientId={}, mqAllSize={}, cidAllSize={}, rebalanceResultSize={}, rebalanceResultSet={}", new Object[]{strategy.getName(), this.consumerGroup, topic, this.mQClientFactory.getClientId(), mqSet.size(), cidAll.size(), allocateResultSet.size(), allocateResultSet});
                    this.messageQueueChanged(topic, mqSet, allocateResultSet);
                }
            }
        }

    }

DefaultMQProducerImpl (消费端默认false,不启动)

  • true的话,传入生产者相关配置 class DefaultMQProducer extends ClientConfig 创建生产者实例
  • 在producerTable生产者列表中 , 进行生产者客户端注册( 本质ConcurrentMap )
  this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQProducer, this.rpcHook);
  boolean registerOK = this.mQClientFactory.registerProducer(this.defaultMQProducer.getProducerGroup(), this);


ConcurrentMap<String, MQProducerInner> producerTable;
posted @ 2020-04-28 11:34  爪哇搬砖  阅读(833)  评论(0编辑  收藏  举报