ActiveMQ中消費者是如何接收消息的(二)

       上篇文章大致講述了同步消費者和異步消費者接收消息的異同(詳見《ActiveMQ中消費者是如何接收消息的(一)》http://manzhizhen.iteye.com/blog/2094130 ),但我們還未講到消息是在什麼時候放入消費者ActiveMQMessageConsumer類的“消息容器”unconsumedMessages中的,這很關鍵,因爲爲了解耦,消費者類不需要知道你ActiveMQ是怎麼獲得和分發消息的,我只知道一旦我發現unconsumedMessages中還有未消費的消息,我就會去儘早消費它。

        直接使用過ActiveMQ API編碼的人一定知道,一個MOM地址(即連接器地址)由ActiveMQConnectionFactory綁定,ActiveMQConnectionFactory下可以創建多個ActiveMQConnection(所謂的工廠模式),而一個ActiveMQConnection(連接)下可以創建多個ActiveMQSession(會話),而ActiveMQSession下又可以創建多個ActiveMQMessageConsumer(消費者)和多個ActiveMQMessageProducer(生產者)。所以,你大致也猜想得到,消息從MOM服務器發出後,最先到達的是消費者所屬的ActiveMQConnection,ActiveMQConnection根據消息的來源(Destination)來分給其下的ActiveMQSession,ActiveMQSession接收到消息後,又會分給其下的消費者們(當然,ActiveMQSession會把消息分發的任務交給它的“手下”ActiveMQSessionExecutor類),這樣,消費者的unconsumedMessages中就有了消息(神說,要有光,就有了光 ;我說,要有房,於是,我就買不起房)。

       下面,我們來從源碼的角度,來細細剖析這一過程,碎碎念,碎碎念。。。讓我們來發動起自己的大腦,由於JMS系統要求支持多種通信協議(什麼TCP、UDP之類的)和異構系統(比如Java系統和C++系統通信),如果沒有這些特點,則這個JMS實現是沒有競爭力的。所以,如果讓你來設計一個JMS實現,你首先要做的就是把通信層給解耦,所以,就有了ActiveMQ中Transport接口和TransportSupport抽象類,讓我們看看他們的簽名:

public interface Transport extends Service  【此接口是爲了讓客戶端有消息被異步發送、同步發送和被消費的能力】

public abstract class TransportSupport extends ServiceSupport implements Transport  【此抽象類是Transport 的一個有用的基礎實現類】

       讓咱們看看Transport接口中的幾個主要方法簽名:

void oneway(Object command) throws IOException; 【異步發送消息】

FutureResponse asyncRequest(Object command, ResponseCallback responseCallback) throws IOException;【帶回調功能的異步請求的應答,如果responseCallback不爲空,則在應答完成後將被調用】

Object request(Object command) throws IOException; 【異步請求應答】

Object request(Object command, int timeout) throws IOException; 【帶超時時間的異步請求應答】

TransportListener getTransportListener(); 【獲得當前的傳送監聽器】

void setTransportListener(TransportListener commandListener); 【設置傳送監聽器】 

void reconnect(URI uri) throws IOException; 【重定向到另一個地址】

void updateURIs(boolean rebalance,URI[] uris) throws IOException; 【提供可替代的一系列地址】

 

       而奇怪的是,作爲Transport接口首要的實現類TransportSupport對其上面中的五個方法的直接實現卻是——不支持。。源碼如下:

    public FutureResponse asyncRequest(Object command, ResponseCallback responseCallback) throws IOException {
        throw new AssertionError("Unsupported Method");
    }

    public Object request(Object command) throws IOException {
        throw new AssertionError("Unsupported Method");
    }

    public Object request(Object command, int timeout) throws IOException {
        throw new AssertionError("Unsupported Method");
    }

    public void reconnect(URI uri) throws IOException {
        throw new IOException("Not supported");
    }

    public void updateURIs(boolean reblance,URI[] uris) throws IOException {
        throw new IOException("Not supported");
    }

       也許是最常用的傳送器不需要去支持這些功能,如果需要,實現類也可以去直接覆蓋該方法。TransportSupport類中唯一的成員變量是TransportListener transportListener;,這也意味着一個傳送器支持類綁定一個傳送監聽器類,傳送監聽器接口TransportListener 最重要的方法就是 void onCommand(Object command);,它用來處理命令 。TransportSupport類中最重要的方法是doConsume,它的作用就是用來“消費消息”,源碼如下:

    public void doConsume(Object command) {
        if (command != null) {
            if (transportListener != null) {
                transportListener.onCommand(command);
            } else {
                LOG.error("No transportListener available to process inbound command: " + command);
            }
        }
    }

       其實說白了,TransportSupport只負責維護一些狀態(很多狀態都沒有給實現,直接是不支持或返回false)和拋異常,最重要的事情都交給了它所綁定的傳送監聽器類TransportListener 了。如代碼所示,它直接把Command(消息的內容、執行包裝類)對象作爲參數去調用傳送監聽器類的onCommand方法。 你如果看過TransportListener接口的實現類,你就會恍然大悟,大名鼎鼎的ActiveMQConnection就是其實現類之一,於是,消息就這樣從傳送層到達了我們的連接器上,我們先不急着繼續往下追溯,因爲傳送層這部分還沒說完。TransportSupport有很多實現類,具體的說,ActiveMQ支持的每種通信協議,都有對應的TransportSupport實現類。爲了方便,因爲我們最常用的是TCP協議,所以我們以其實現類TcpTransport來做說明,開門見山,直接給出其構造方法:

    public TcpTransport(WireFormat wireFormat, SocketFactory socketFactory, URI remoteLocation,
                        URI localLocation) throws UnknownHostException, IOException {
        this.wireFormat = wireFormat;
        this.socketFactory = socketFactory;
        try {
            this.socket = socketFactory.createSocket();
        } catch (SocketException e) {
            this.socket = null;
        }
        this.remoteLocation = remoteLocation;
        this.localLocation = localLocation;
        setDaemon(false);
    }

       由這引出了TcpTransport幾個重要的成員變量:

protected final URI remoteLocation; // JMS消息服務器地址,也就是創建連接器工程是配置的地址

protected final WireFormat wireFormat; // 用來處理數據包命令、流、通道和數據報的進和出

protected Socket socket; // 由構造函數中傳入的SocketFactory來創建,採用TCP協議當然離不開套接字

protected DataOutputStream dataOut;  // 從socket.getOutputStream()包裝過的輸出流
protected DataInputStream dataIn; // 從socket.getInputStream()包裝過的輸入流

       現在產生了一個問題,誰來調用這個構造函數,也就是誰來創建TcpTransport,答案就是ActiveMQConnectionFactory,在該工廠創建ActiveMQConnection之前,會先創建好TcpTransport,當然是調用 TransportFactory來創建,我們注意到,Transport是ActiveMQConnection的構造函數的形參之一,該構造函數會去讓該Transport綁定自己(this.transport.setTransportListener(this);)。創建好TcpTransport後,就開始需要啓動它了,run方法源碼如下:

    public void run() {
        LOG.trace("TCP consumer thread for " + this + " starting");
        this.runnerThread=Thread.currentThread();
        try {
            while (!isStopped()) {
                doRun();
            }
        } catch (IOException e) {
            stoppedLatch.get().countDown();
            onException(e);
        } catch (Throwable e){
            stoppedLatch.get().countDown();
            IOException ioe=new IOException("Unexpected error occured: " + e);
            ioe.initCause(e);
            onException(ioe);
        }finally {
            stoppedLatch.get().countDown();
        }
    }

 run()用來從socket中來讀取數據包,只要TcpTransport沒有停止,它就會不停的調用doRun(),在看看doRun()是怎麼實現的:

    protected void doRun() throws IOException {
        try {
            Object command = readCommand();
            doConsume(command);
        } catch (SocketTimeoutException e) {
        } catch (InterruptedIOException e) {
        }
    }

 我們在這裏發現了TcpTransport讀取數據的方法readCommand,doRun方法就作用就是“讀一條,消費一條,讀一條,消費一條。。。”,doConsume方法前面已經講過,這裏我們來看消息是怎麼接收的,readCommand源碼如下:

    protected Object readCommand() throws IOException {
        return wireFormat.unmarshal(dataIn);
    }

 就在此時,傳說中的wireFormat出現了,dataIn前面已經說過,現在我們來看看WireFormat接口,WireFormat接口主要是爲了將流數據解析和組裝。解析的話,說白了,就是提取數據轉換成我們ActiveMQ需要的對象,你可以簡單的把它想象成對象反序列化的過程。WireFormat接口有多種實現類,默認的是OpenWireFormat,它採用的是OpenWire協議,這是ActiveMQ自己的跨語言Wire協議,它允許客戶端多個不同的語言和平臺本機來和ActiveMQ服務器通訊在Java環境下,OpenWire的ActiveMQ4.x或更高版本默認傳輸方式,我們這裏不必過分去追求OpenWireFormat實現類中的細節(有興趣的讀者可以去官方文檔中瞭解:http://activemq.apache.org/openwire-version-2-specification.html),至少它不是採用JDK裏面序列化機制(序列化只適合Java平臺並且是低效的),到這裏,我們都差不多明白了傳送層主要的工作是獲得數據並且將數據轉換成對象,把對象再傳給連接ActiveMQConnection。

       前面所述,我們知道了ActiveMQConnection中的onCommand方法是由Transport來調用的,ActiveMQConnection是一個擁有將近3000行代碼的實現類,其重要性不言而喻,我們先來看看待會會用到的幾個重要的成員變量:

private final Transport transport; // 綁定的傳送器對象

private final ConnectionInfo info; // 負責管理連接的各種狀態變量

private final ThreadPoolExecutor executor; // 線程池執行器,用來調度需要多線程執行的任務

private final ConcurrentHashMap<ConsumerId, ActiveMQDispatcher> dispatchers = new ConcurrentHashMap<ConsumerId, ActiveMQDispatcher>(); // 消費者ID到分發對象映射的Map,這裏的分發者對象就是ActiveMQSession而不是ActiveMQConsumer本身。
private final ConcurrentHashMap<ProducerId, ActiveMQMessageProducer> producers = new ConcurrentHashMap<ProducerId, ActiveMQMessageProducer>(); // 生產者ID到生產者對象映射的Map
private final CopyOnWriteArrayList<TransportListener> transportListeners = new CopyOnWriteArrayList<TransportListener>(); // 如果某些框架(如Spring)給ActiveMQConnectionFactory上綁定了TransportListener(通過ActiveMQConnectionFactory#setTransportListener(TransportListener transportListener)方法綁定),則該TransportListener會被放入由該工廠創建的所有ActiveMQConnection的transportListeners中

       下面給出ActiveMQConnection構造函數源碼:

    protected ActiveMQConnection(final Transport transport, IdGenerator clientIdGenerator, IdGenerator connectionIdGenerator, JMSStatsImpl factoryStats) throws Exception {

        this.transport = transport;  // 讓自己綁定傳送器
        this.clientIdGenerator = clientIdGenerator;
        this.factoryStats = factoryStats;

        // 配置一個單線程的執行器,如果被閒置它的核心線程可以被超時
        executor = new ThreadPoolExecutor(1, 1, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r, "ActiveMQ Connection Executor: " + transport);
                // 不要使用守護線程 - 詳見 https://issues.apache.org/jira/browse/AMQ-796
                // thread.setDaemon(true);
                return thread;
            }
        });
        // asyncConnectionThread.allowCoreThreadTimeOut(true);
        String uniqueId = connectionIdGenerator.generateId();
        this.info = new ConnectionInfo(new ConnectionId(uniqueId));
        this.info.setManageable(true);
        this.info.setFaultTolerant(transport.isFaultTolerant());
        this.connectionSessionId = new SessionId(info.getConnectionId(), -1);

        this.transport.setTransportListener(this); // 讓傳送器綁定自己

        this.stats = new JMSConnectionStatsImpl(sessions, this instanceof XAConnection);
        this.factoryStats.addConnection(this);
        this.timeCreated = System.currentTimeMillis();  // 記錄創建時間
        this.connectionAudit.setCheckForDuplicates(transport.isFaultTolerant());
    }

       從構造函數可以看出,創建ActiveMQConnection對象時,除了和Transport相互綁定,還對線程池執行器executor進行了初始化。下面我們看看該類的核心方法——onCommand:

    @Override
    public void onCommand(final Object o) {
        final Command command = (Command)o;
        if (!closed.get() && command != null) {
            try {
                command.visit(new CommandVisitorAdapter() {
					/**
					 * 處理消息分發
					 * 如果傳入的command是MessageDispatch,
					 * 則該command的visit方法就會調用processMessageDispatch方法
					 */ 
                    @Override
                    public Response processMessageDispatch(MessageDispatch md) 
						throws Exception {
						// 等待Transport中斷處理完成
                        waitForTransportInterruptionProcessingToComplete();
						
						// 這裏通過消費者ID來獲取消費者對象
						//(ActiveMQMessageConsumer實現了ActiveMQDispatcher接口),
						// 這也意味着MessageDispatch對象已經包含了消息和
						// 該消息應該被分配給哪個消費者的所有信息
                        ActiveMQDispatcher dispatcher = dispatchers.get(md.getConsumerId());
						
                        if (dispatcher != null) {
                            // 以防是嵌入式代理(vm://),這裏將消息進行copy
                            // 如果md.getMessage() == null 意味着你已經瀏覽到消息隊列的末端。
                            Message msg = md.getMessage();
                            if (msg != null) {
                                msg = msg.copy();
                                msg.setReadOnlyBody(true);
                                msg.setReadOnlyProperties(true);
                                msg.setRedeliveryCounter(md.getRedeliveryCounter());
                                msg.setConnection(ActiveMQConnection.this);
                                msg.setMemoryUsage(null);
                                md.setMessage(msg);
                            }
							
							// 調用會話ActiveMQSession自己的dispatch方法來處理這條消息
                            dispatcher.dispatch(md); 
							
                        } else {
                            LOG.debug("{} no dispatcher for {} in {}", this, 
								md, dispatchers);
                        }
                        return null;
                    }

					/**
					 * 處理生產者應答
					 * 如果傳入的command是ProducerAck,
					 * 則該command的visit方法就會調用processProducerAck方法
					 */ 
                    @Override
                    public Response processProducerAck(ProducerAck pa) throws Exception {
                        if (pa != null && pa.getProducerId() != null) {
                            ActiveMQMessageProducer producer = producers.
								get(pa.getProducerId());
                            if (producer != null) {
                                producer.onProducerAck(pa);
                            }
                        }
                        return null;
                    }
                });
							
				// 注意:爲了簡化處理,這裏省略了CommandVisitorAdapter類的其他方法,
				// 有興趣的讀者可以直接閱讀源碼
				
				
            } catch (Exception e) {
                onClientInternalException(e);
            }
        }

		// 調用由ActiveMQConnectionFactory統一綁定給其下ActiveMQConnection的傳送監聽器的
		// 處理命令方法.
		// 一般情況下transportListeners爲空,但如果你使用Spring等框架,就另當別論了。
        for (Iterator<TransportListener> iter = transportListeners.iterator(); 
		iter.hasNext();) {
            TransportListener listener = iter.next();
            listener.onCommand(command);
        }
    }

      由於我們關注的是消費者,所以我們只關心processMessageDispatch方法,我們已經看到傳給processMessageDispatch方法的參數就是MessageDispatch對象,該對象包含了消息對象本身和消費者ID,所以,processMessageDispatch方法需要做的只是簡單的去調用ActiveMQConnection下的ActiveMQSession的dispatch方法來處理這條消息。

       我們先不着急看費者的dispatch方法做了哪些處理,我們更關心processMessageDispatch方法何時被調用?又是由誰來調用?需要注意的是CommandVisitorAdapter類中有諸多方法,它是個適配器類,如果command.visit(new CommandVisitorAdapter() ..);中的command是MessageDispatch(類MessageDispatch 派生於BaseCommand)類對象,則它的visit只會去調用適配器的processMessageDispatch方法,而不會去關心其他方法,所以,你可以根據CommandVisitorAdapter在這裏實現的幾個方法,就可以猜出傳進來的Command對象有幾種類型。這裏我們可以看看MessageDispatch中visit方法的實現:

    @Override
    public Response visit(CommandVisitor visitor) throws Exception {
        return visitor.processMessageDispatch(this);
    }

 所以,其實你就知道,原來傳給processMessageDispatch方法的MessageDispatch對象md就是傳給ActiveMQConnection#onCommand方法的final Object o,也就是說MessageDispatch對象在調用onCommand方法之前就已經被創建了,如果我們想知道消息被分配給哪個消費者,就得去追溯這個MessageDispatch對象到底在哪創建的,還記得前面段落提過的TcpTransport類中的wireFormat嗎?Command對象的形成就是來自WireFormat接口中Object unmarshal(ByteSequence packet) throws IOException;方法的實現,所以,Command對象的創建也包含了消息分發的過程,這裏我們先不糾結消息是怎麼決定分配給哪個消費者的,在《ActiveMQ中消費者是如何接收消息的(三)》中會給出答案,我們這裏接着看會話ActiveMQSession類的消息處理dispatch方法,下面給出源碼:

 

    @Override
    public void dispatch(MessageDispatch messageDispatch) {
        try {
	  ////  交給ActiveMQSessionExecutor對象來處理消息分發
            executor.execute(messageDispatch);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            connection.onClientInternalException(e);
        }
    }

       代碼中的executor對象就是ActiveMQSession類中重要的一員(protected final ActiveMQSessionExecutor executor;),專門負責處理消息分發,我們接着看ActiveMQSessionExecutor 類中主要的成員變量:

private final ActiveMQSession session;  // 對其“上級”ActiveMQSession的引用
private final MessageDispatchChannel messageQueue; // 等待分發的消息容器,消費者ActiveMQConsumer中也有哦
private boolean dispatchedBySessionPool; // 是否通過會話池分發
private volatile TaskRunner taskRunner; // 採用異步分發時的任務執行器對象
       大家可能會覺得這個dispatchedBySessionPool標誌是用來做什麼的,這需要我們追溯到它set方法的調用者——ActiveMQSession類,其setMessageListener方法源碼如下:

    @Override
    public void setMessageListener(MessageListener listener) throws JMSException {

		// 如果用戶想清理監聽器,則listener可以設置爲null
		// 如果不爲空,我們回去檢查此Session是否已經關閉,如果關閉,將拋異常
        if (listener != null) {
            checkClosed();
        }
		
		// 給ActiveMQSession綁定此監聽器
        this.messageListener = listener;

        if (listener != null) {
			// 給ActiveMQSession的會話執行器ActiveMQSessionExecutor
			// 的dispatchedBySessionPool標記設置爲true
            executor.setDispatchedBySessionPool(true);
        }
    }

 我們可能只知道消費者上可以設置消息監聽器,沒想到ActiveMQSession上也可以,一旦我們給ActiveMQSession設置了消息監聽器,則會話上其他形式的消息接收將變成不可用,也就是說,此時,其下的消費者都將接收不到消息,但所有的消息發送仍然是正常的。那ActiveMQ在會話上提供這個功能是出於什麼考慮呢?可以肯定,該方法不是給普通的JMS客戶端使用的,但如果你想完全自己來處理消息分配(Spring框架中有可能使用了此方法),使用此方法是個不錯的選擇。言歸正傳,我們繼續說會話執行器,ActiveMQSessionExecutor#execute方法的實現如下:

    void execute(MessageDispatch message) throws InterruptedException {

        if (!startedOrWarnedThatNotStarted) {

            ActiveMQConnection connection = session.connection;
            long aboutUnstartedConnectionTimeout = connection.getWarnAboutUnstartedConnectionTimeout();
            if (connection.isStarted() || aboutUnstartedConnectionTimeout < 0L) {
                startedOrWarnedThatNotStarted = true;
            } else {
                long elapsedTime = System.currentTimeMillis() - connection.getTimeCreated();

                // lets only warn when a significant amount of time has passed
                // just in case its normal operation
                if (elapsedTime > aboutUnstartedConnectionTimeout) {
                    LOG.warn("Received a message on a connection which is not yet started. Have you forgotten to call Connection.start()? Connection: " + connection
                             + " Received: " + message);
                    startedOrWarnedThatNotStarted = true;
                }
            }
        }

		///// 如果會話設置的不是異步分發且沒有采用Session池分發,則調用dispatch方法發送消息
        if (!session.isSessionAsyncDispatch() && !dispatchedBySessionPool) {
            dispatch(message);
        } else {
		    // 將消息放入隊列中
            messageQueue.enqueue(message);
            wakeup();
        }
    }

 上面關於startedOrWarnedThatNotStarted部分的代碼我們大可不必關心,重要的是下面的。如果是通過ActiveMQConnection的createSession創建出來的ActiveMQSession並且我們沒有通過ActiveMQSession#setAsyncDispatch方法設置過的話,默認是採用異步分發消息的。所以,在這裏直接把該消息放入了messageQueue中,並調用了wakeup()方法,wakeup源碼如下:

    public void wakeup() {
		/// 如果不是由會話池分發,則進行如下處理
        if (!dispatchedBySessionPool) {
			// 如果是異步分發
            if (session.isSessionAsyncDispatch()) {
                try {
                    TaskRunner taskRunner = this.taskRunner;
                    if (taskRunner == null) {
                        synchronized (this) {
                            if (this.taskRunner == null) {
                                if (!isRunning()) {
                                    // stop has been called
                                    return;
                                }
								// 注意這裏,createTaskRunner方法把this作爲Task傳進去了!
                                this.taskRunner = session.connection.getSessionTaskRunner().createTaskRunner(this,
                                        "ActiveMQ Session: " + session.getSessionId());
                            }
                            taskRunner = this.taskRunner;
                        }
                    }
					// 說白了就是將
                    taskRunner.wakeup();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
			// 如果是會話池分發
            } else {
                while (iterate()) {
                }
            }
        }
    }

         我們這裏先看異步分發是怎麼一回事,第16行代碼我們發現了關鍵,session.connection.getSessionTaskRunner()從ActiveMQConnection獲取到的是一個TaskRunnerFactory任務運行工廠(好高大上的樣子,有木有?有木有?),代碼中馬上又調用了工廠的createTaskRunner方法來創建一個任務運行器,盡然this是第一個參數,也就是把ActiveMQSessionExecutor自己給傳進去了(難道瘋了),定睛一看,原來ActiveMQSessionExecutor類實現的唯一接口就是Task,我們來看看Task接口:

public interface Task {
    boolean iterate();
}

 我靠,這麼簡單,讓我想起了Runnable接口。。。Task接口在ActiveMQ中表示可能需要迭代完成的任務,聰明的你一下就知道了,那不就是說 ActiveMQSessionExecutor的iterate()方法將被線程池中的一個線程給調用?那不也就是29行採用同步分發while中所調用的iterate()方法?沒錯!就是這樣!一談到線程池,那些多線程編程的愛好者們似乎都要血液沸騰了,那麼ActiveMQ中的這裏採用的是什麼線程池呢?是它自己實現的嗎?No,No,No,下面給出TaskRunnerFactory中的源碼:

	protected ExecutorService createDefaultExecutor() {
        ThreadPoolExecutor rc = new ThreadPoolExecutor(0, getMaxThreadPoolSize(), 
			getDefaultKeepAliveTime(), TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), 
			new ThreadFactory() {
			
            @Override
            public Thread newThread(Runnable runnable) {
                String threadName = name + "-" + id.incrementAndGet();
                Thread thread = new Thread(runnable, threadName);
                thread.setDaemon(daemon);
                thread.setPriority(priority);

                LOG.trace("Created thread[{}]: {}", threadName, thread);
                return thread;
            }
        });
		
        if (rejectedTaskHandler != null) {
            rc.setRejectedExecutionHandler(rejectedTaskHandler);
        }
        return rc;
    }

      果然,它用的就是ThreadPoolExecutor,構造函數中的getMaxThreadPoolSize()默認返回的是Integer.MAX_VALUE,而getDefaultKeepAliveTime()返回的是Integer.getInteger("org.apache.activemq.thread.TaskRunnerFactory.keepAliveTime", 30),也就是說默認是30秒。有沒有人好奇第11行設置的線程優先級是多少?是ThreadPriorities.INBOUND_CLIENT_SESSION!!看:

public interface ThreadPriorities {
    int INBOUND_BROKER_CONNECTION = 6;
    int OUT_BOUND_BROKER_DISPATCH = 6;
    int INBOUND_CLIENT_CONNECTION = 7;
    int INBOUND_CLIENT_SESSION = 7;
    int BROKER_MANAGEMENT = 9;
}

 這是題外話,迴歸正題,這裏,我們先不着急看iterate()方法中的內容,我們回過頭來看看ActiveMQSessionExecutor#execute方法中同步分發時的代碼(dispatch(message);):

    void dispatch(MessageDispatch message) {
        // TODO - we should use a Map for this indexed by consumerId
        for (ActiveMQMessageConsumer consumer : this.session.consumers) {
            ConsumerId consumerId = message.getConsumerId();
            if (consumerId.equals(consumer.getConsumerId())) {
                consumer.dispatch(message);
                break;
            }
        }
    }

    我們再給出ActiveMQSessionExecutor#execute方法中異步分發wakeup()中iterate() 的代碼:

	public boolean iterate() {
		// 將消費者監聽的所有消息投遞到消費者隊列中
                // 異步消費者(setMessageListener)纔會運行for循環代碼
        for (ActiveMQMessageConsumer consumer : this.session.consumers) {
            if (consumer.iterate()) {
                return true;
            }
        }

		// 處理messageQueue中遺留的消息(非阻塞取)
		// 同步消費者(receive())纔會運行下面的代碼
          MessageDispatch message = messageQueue.dequeueNoWait();
        if (message == null) {
            return false;
        } else {
			// 如果消息不爲空,則分發出去
            dispatch(message);
            return !messageQueue.isEmpty();
        }
    }

    從上面的代碼中我們發現了,同步分發時,ActiveMQSessionExecutor會去調用消費者ActiveMQMessageConsumer的dispatch方法,而異步分發時會去調用消費者ActiveMQMessageConsumer的iterate方法:

    public boolean iterate() {
        MessageListener listener = this.messageListener.get();
        if (listener != null) {
            MessageDispatch md = unconsumedMessages.dequeueNoWait();
            if (md != null) {
                dispatch(md);
                return true;
            }
        }
        return false;
    }

      這裏咱們發現了,不管同步分發還是異步分發,最終調用的都是消費者的dispatch方法,異步分發和同步分發的區別就是這麼簡單,調用的處理過程都是一樣的,只不過異步分發是將分發任務交給線程池去調度而已,默認採用的是異步分發。那什麼時候採用異步分發,什麼時候採用同步分發呢?我不說你也知道了,對於“快消費者”,我們建議採用同步分發,這樣省去了線程池資源調度的開銷,對於“慢消費者”,我們建議採用默認的異步分發,這樣讓消息分發更快。

      在這裏,我們還發現同步消費者和異步消費者的一個顯著區別:不管Session採用異步分發還是同步分發,異步消費者都不是從自己的unconsumedMessages中取消息來處理,而是直接處理ActiveMQSessionExecutor透傳過來的消息,而同步消費者的receive方法只能從unconsumedMessages取消息來處理。不信的話,你結合前面所說的加上我下面展示的ActiveMQMessageConsumer# dispatch代碼你就知道一切了:

    @Override
    public void dispatch(MessageDispatch md) {
        MessageListener listener = this.messageListener.get();
        try {
            clearMessagesInProgress();
            clearDeliveredList();
            synchronized (unconsumedMessages.getMutex()) {
                if (!unconsumedMessages.isClosed()) {
                    if (this.info.isBrowser() || !session.connection.isDuplicate(this, md.getMessage())) {
                        if (listener != null && unconsumedMessages.isRunning()) {
                            if (redeliveryExceeded(md)) {
                                posionAck(md, "dispatch to " + getConsumerId() + " exceeds redelivery policy limit:" + redeliveryPolicy);
                                return;
                            }
                            ActiveMQMessage message = createActiveMQMessage(md);
                            beforeMessageIsConsumed(md);
                            try {
                                boolean expired = message.isExpired();
                                if (!expired) {
                                    listener.onMessage(message);
                                }
                                afterMessageIsConsumed(md, expired);
                            } catch (RuntimeException e) {
                                LOG.error(getConsumerId() + " Exception while processing message: " + md.getMessage().getMessageId(), e);
                                if (isAutoAcknowledgeBatch() || isAutoAcknowledgeEach() || session.isIndividualAcknowledge()) {
                                    // schedual redelivery and possible dlq processing
                                    md.setRollbackCause(e);
                                    rollback();
                                } else {
                                    // Transacted or Client ack: Deliver the
                                    // next message.
                                    afterMessageIsConsumed(md, false);
                                }
                            }
                        } else {
                            if (!unconsumedMessages.isRunning()) {
                                // delayed redelivery, ensure it can be re delivered
                                session.connection.rollbackDuplicate(this, md.getMessage());
                            }
                            unconsumedMessages.enqueue(md);
                            if (availableListener != null) {
                                availableListener.onMessageAvailable(this);
                            }
                        }
                    } else {
                        if (!session.isTransacted()) {
                            LOG.warn("Duplicate non transacted dispatch to consumer: "  + getConsumerId() + ", poison acking: " + md);
                            posionAck(md, "Duplicate non transacted delivery to " + getConsumerId());
                        } else {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug(getConsumerId() + " tracking transacted redelivery of duplicate: " + md.getMessage());
                            }
                            boolean needsPoisonAck = false;
                            synchronized (deliveredMessages) {
                                if (previouslyDeliveredMessages != null) {
                                    previouslyDeliveredMessages.put(md.getMessage().getMessageId(), true);
                                } else {
                                    // delivery while pending redelivery to another consumer on the same connection
                                    // not waiting for redelivery will help here
                                    needsPoisonAck = true;
                                }
                            }
                            if (needsPoisonAck) {
                                LOG.warn("acking duplicate delivery as poison, redelivery must be pending to another"
                                        + " consumer on this connection, failoverRedeliveryWaitPeriod="
                                        + failoverRedeliveryWaitPeriod + ". Message: " + md);
                                posionAck(md, "Duplicate dispatch with transacted redeliver pending on another consumer, connection: "
                                        + session.getConnection().getConnectionInfo().getConnectionId());
                            } else {
                                if (transactedIndividualAck) {
                                    immediateIndividualTransactedAck(md);
                                } else {
                                    session.sendAck(new MessageAck(md, MessageAck.DELIVERED_ACK_TYPE, 1));
                                }
                            }
                        }
                    }
                }
            }
            if (++dispatchedCount % 1000 == 0) {
                dispatchedCount = 0;
                Thread.yield();
            }
        } catch (Exception e) {
            session.connection.onClientInternalException(e);
        }
    }

 完了,各位,希望能對你們理解ActiveMQ內部機制有所幫助!

 

 這裏,我引用一下噹噹網架構師張亮在程序員雜誌2014.12刊中發表的關於消息中間件的幾段話:

       消費消息有兩種模式:推送和拉取。推送模式的適用場景是,消息的消費者能力強於生產者,一旦有消息就快速進行消費,不會堆積,也沒有延遲,。拉取是消息消費者主動向消息中介(broker)發起請求,獲取消息。拉取模式的優點是消費者可以自己控制消息的拉取速度,消息中介不需要維護消費者的狀態。如果總是從消息中介推送消息,消費者能力不如生產者時,消費者會被壓垮或者必須在消息中介使用定時消息推送,增加消息中介的複雜度。缺點是消息及時性差,取決於拉取的間隔。而且有可能是空拉取,造成資源浪費。

       拉取模式的使用場景是,消息的生產者能力強於消費者,服務器在高峯時間允許堆積消息,然後在波谷時間完成消費。

       因爲ActiveMQ採用消息推送方式,所以最適合的場景是默認消息都可在短時間內被消費。數據量越大,查找和消費消息就越慢,消息積壓程度與消息速度成反比。

       ActiveMQ的缺點:

       1.吞吐量低。由於ActiveMQ需要建立索引,導致吞吐量下降。這是無法克服的缺點,只要使用完全符合JMS規範的消息中間件,就要接受這個級別的TPS。

       2.無分片功能。這是一個功能缺失,JMS並沒有規定消息中間件的集羣、分片機制。而由於ActiveMQ是偉企業級開發設計的消息中間件,初衷並不是爲了處理海量消息和高併發請求。如果一臺服務器不能承受更多消息,則需要橫向拆分。ActiveMQ官方不提供分片機制,需要自己實現。

       ActiveMQ的適用場景:

       1.業務系統沒有實現冪等性。消費不成功,消息連同業務數據一起回滾,適用於不易實現冪等性的複雜業務場景或敏感性業務。

       2.強事務一致性。消息和業務數據必須處於同一事務狀態,假如業務數據回滾,消息必須也回滾成未消費狀態。

       3.內部系統。對於TPS要求低的系統,ActiveMQ由於使用簡單,完全支持JMS,非常適合快速開發。並且ActiveMQ有完善的監控機制和操作界面。

       ActiveMQ不適用的場景:

       1.性能要求高,且不要求事務。性能是ActiveMQ的短板,如果業務要求消息中間件的性能很高,且不要求強一致性的事務,則不應使用ActiveMQ。

        2.消息量巨大的場景。ActiveMQ不支持消息自動分片機制,如果消息量巨大,導致一臺服務器不能處理全部消息,就需要自己開發消息分片功能。

下表是文章中列舉的常用消息中間件的對比:

 

ActiveMQ

RabbitMQ

Kafka

RocketMQ

HornetQ

版本號

5.10.0

3.3.4

0.8.1

3.1.9-SNAPSHOT

2.4.0

關注度

成熟度

成熟

成熟

比較成熟

不成熟

成熟

社區活躍度

文檔

開發語言

Java

Erlang

Scala

Java

Java

JMS支持

需付費

第三方提供

協議支持

AMQP
MQTT
STOMP
REST

AMQP
MQTT
STOMP
REST

自定義

自定義

AMQP
STOMP
REST

客戶端支持

JavaC
C++
Python
PHP
Perl
.Net

JavaC
C++
Python
PHP
Perl
.Net

JavaC
C++
Python
PHP
Perl
.Net

JavaC++

Java

持久化

內存
文件
數據庫

內存
文件

文件

文件

內存
文件

事務

支持

支持

不支持

不完全支持

支持

集羣

一般

較好

較好

管理界面

第三方提供

第三方提供

亮點

JMS標準
成功案例多

吞吐量略高於ActiveMQ

吞吐量極高
批量處理

初步支持分佈式事務
吞吐量不低於Kafka

JMS標準
完美整合Jboss

缺點

吞吐量低
無消息分片功能

吞吐量低
不支持JMS

不支持JMS
不支持事務

不成熟
分佈式事務未開發完全
監控界面不完善
文檔少

ActiveMQ

 

 

發佈了48 篇原創文章 · 獲贊 49 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章