activemq 应答ack机制与消费消息过程分析

主要关注有以下两点:
如何获取消息?
消息如何应答?

ActiveMQMessageConsumer.receive
这个方法是同步阻塞获取消息的方法,尝试从源码分析其工作原理。

    @Override
    public Message receive() throws JMSException {
    //检查连接
        checkClosed();
        //检查是否定义了listener,listener和当前的同步监听冲突,抛出异常
        checkMessageListener();
//发送拉取消息指令
        sendPullCommand(0);
        //从本地队列获取消息
        MessageDispatch md = dequeue(-1);
        if (md == null) {
            return null;
        }
//消息消费前准备
        beforeMessageIsConsumed(md);
        //消息消费后准备
        afterMessageIsConsumed(md, false);

        return createActiveMQMessage(md);
    }

以上是客户端消费消息的总体流程,看一下拉取消息指令sendPullCommand的源码如下

sendPullCommand
clearDeliveredList是清空在deliveredMessages中还未ack的消息,具体操作是事务性的会话则放入另一个列表previouslyDeliveredMessages做本地重发消息,非事务的会话就标记这些消息已经被接收了
sendPullCommand检查unconsumedMessages(未曾消费消息)是否为空和PrefetchSize[后面说这个变量含义]是不是等于0,满足才会异步发送拉取消息指令到broker,broker会推送消息到客户端的unconsumedMessages里面.

    protected void sendPullCommand(long timeout) throws JMSException {
    //清空在deliveredMessages中还未ack的消息
        clearDeliveredList();
        if (info.getCurrentPrefetchSize() == 0 && unconsumedMessages.isEmpty()) {
            MessagePull messagePull = new MessagePull();
            messagePull.configure(info);
            messagePull.setTimeout(timeout);
            session.asyncSendPacket(messagePull);
        }
    }

dequeue
这个方法是从本地消息队列unconsumedMessages出队列一条消息进行消费,那么broker是如何把消息推送到unconsumedMessages里面的呢?简单流程如下图所示。
这里写图片描述

然后就是消费unconsumedMessages的消息流程,如下所示。

  //获取消息操作 MessageDispatch md = unconsumedMessages.dequeue(timeout);

   public MessageDispatch dequeue(long timeout) throws InterruptedException {
        synchronized (mutex) {
            // Wait until the consumer is ready to deliver messages.
            while (timeout != 0 && !closed && (list.isEmpty() || !running)) {
                if (timeout == -1) {
                    mutex.wait();
                } else {
                    mutex.wait(timeout);
                    break;
                }
            }
            if (closed || !running || list.isEmpty()) {
                return null;
            }
            return list.removeFirst();
        }
    }

可以看出unconsumedMessages.dequeue获取不到消息会阻塞,也实现了receive方法的阻塞实现。

beforeMessageIsConsumed


    private void beforeMessageIsConsumed(MessageDispatch md) throws JMSException {
        md.setDeliverySequenceId(session.getNextDeliveryId());
        lastDeliveredSequenceId = md.getMessage().getMessageId().getBrokerSequenceId();
        if (!isAutoAcknowledgeBatch()) {
            synchronized(deliveredMessages) {
            //放入待确认消息列表
                deliveredMessages.addFirst(md);
            }
            if (session.getTransacted()) {
                if (transactedIndividualAck) {
                //可以马上确认的消息
                    immediateIndividualTransactedAck(md);
                } else {
                //放入稍后在事务commit的时候一起确认的消息
                    ackLater(md, MessageAck.DELIVERED_ACK_TYPE);
                }
            }
        }
    }

这里面主要是做消息消费之前的一些准备工作,如果ack不是【topic的DUPS_OK_ACKNOWLEDGE】,则所有的消息先放到deliveredMessages链表的开头。
如果当前是 事务类型的会话,还需要特殊操作,判断transactedIndividualAck,如果为true,表示单条消息直接返回ack。 否则,调用ackLater,消息放入pendingack,等待处理。批量应答, client端在消费消息后暂且不发送ACK,而是把它缓存下来(pendingACK),等到这 些消息的条数达到一定阀值时,session.commit指令把它们全部确认;这比对每条消息都逐个确认,在性能上 要提高很多,pendingACK就是事务的批量ack;

afterMessageIsConsumed
这个方法主要就是对消息进行响应的ack操作

    private void afterMessageIsConsumed(MessageDispatch md, boolean messageExpired) throws JMSException {
        if (unconsumedMessages.isClosed()) {
            return;
        }
        if (messageExpired) {
        //消息过期了,就ack过期了
            acknowledge(md, MessageAck.EXPIRED_ACK_TYPE);
            stats.getExpiredMessageCount().increment();
        } else {
            stats.onMessage();
            if (session.getTransacted()) {
                // Do nothing.
                //会话是事务型的,就不在这里处理,是在commit的时候进行批量ack
            } else if (isAutoAcknowledgeEach()) {
            //session.isAutoAcknowledge() || ( session.isDupsOkAcknowledge() && getDestination().isQueue() );
            //非事务性的 (队列延迟确认[Dups])或者(自动确认)执行以下逻辑
                if (deliveryingAcknowledgements.compareAndSet(false, true)) {
                    synchronized (deliveredMessages) {
                        if (!deliveredMessages.isEmpty()) {
                        //是否开启优化ack,开启则【等同开启批量确认】
                            if (optimizeAcknowledge) {
                                ackCounter++;

                                // AMQ-3956 evaluate both expired and normal msgs as
                                // otherwise consumer may get stalled
                                if (ackCounter + deliveredCounter >= (info.getPrefetchSize() * .65) || (optimizeAcknowledgeTimeOut > 0 && System.currentTimeMillis() >= (optimizeAckTimestamp + optimizeAcknowledgeTimeOut))) {
                                    MessageAck ack = makeAckForAllDeliveredMessages(MessageAck.STANDARD_ACK_TYPE);
                                    if (ack != null) {
                                        deliveredMessages.clear();
                                        ackCounter = 0;
                                        session.sendAck(ack);
                                        optimizeAckTimestamp = System.currentTimeMillis();
                                    }
                                    // AMQ-3956 - as further optimization send
                                    // ack for expired msgs when there are any.
                                    // This resets the deliveredCounter to 0 so that
                                    // we won't sent standard acks with every msg just
                                    // because the deliveredCounter just below
                                    // 0.5 * prefetch as used in ackLater()
                                    if (pendingAck != null && deliveredCounter > 0) {
                                    //达到批量确认阀值,进行批量确认
                                        session.sendAck(pendingAck);
                                        pendingAck = null;
                                        deliveredCounter = 0;
                                    }
                                }
                            } else {
                            //没开启优化,自动回传单条
                                MessageAck ack = makeAckForAllDeliveredMessages(MessageAck.STANDARD_ACK_TYPE);
                                if (ack!=null) {
                                    deliveredMessages.clear();
                                    session.sendAck(ack);
                                }
                            }
                        }
                    }
                    deliveryingAcknowledgements.set(false);
                }
            } else if (isAutoAcknowledgeBatch()) {
            //非事务性的 topic的延时确认【等同开启批量确认】
                ackLater(md, MessageAck.STANDARD_ACK_TYPE);
            } else if (session.isClientAcknowledge()||session.isIndividualAcknowledge()) {
            //客户端手动确认,CLIENT_ACKNOWLEDGE 批量确认之前所有,INDIVIDUAL_ACKNOWLEDGE确认当前单条
                boolean messageUnackedByConsumer = false;
                synchronized (deliveredMessages) {
                    messageUnackedByConsumer = deliveredMessages.contains(md);
                }
                if (messageUnackedByConsumer) {
                    ackLater(md, MessageAck.DELIVERED_ACK_TYPE);
                }
            }
            else {
                throw new IllegalStateException("Invalid session state.");
            }
        }
    }

optimizeAcknowledge是开启批量回传的标记,可以设置批量回传阀值来优化回传效率。

**

总结消息确认模式

**
事务性会话
SESSION_TRANSACTED
所有的事务性会话,不管如何设置ack模式,都是按照以下原则:同一事务内部,在执行commit之前接收到的消息,都是没有ack的,放在pendingAck里面。在进行session.commit的时候会把pendingAck里面的消息全部一次确认,视为手动批量确认也。事务会话要求效率高,因此都是批量进行ack的,没有别的方式。

非事务性会话
1.(队列延迟确认[DUPS_OK_ACKNOWLEDGE])或者(自动确认[AUTO_ACKNOWLEDGE])= auto each
1.1 开启optimizeAcknowledge优化回传,相当于批量回传,达到设置的阀值之后自动批量回传ack
1.2 没有开启的话就都是马上自动发送标准的ack,回传单条数据

2.( topic的延时确认[DUPS_OK_ACKNOWLEDGE])= auto batch
2.1 统一都是批量确认,达到设置的阀值之后自动批量回传ack

3 . (CLIENT_ACKNOWLEDGE | INDIVIDUAL_ACKNOWLEDGE)
3.1 CLIENT_ACKNOWLEDGE 按照客户端手动确认处理,类似于session.commit,如果堆积多条才手动确认,可以是等同手动批量确认,只不过是没有阀值的批量确认。INDIVIDUAL_ACKNOWLEDGE手动确认单条。

ack_type
上面的正常消息确认ack_type都是MessageAck.STANDARD_ACK_TYPE,ack_type总共有以下几种:
DELIVERED_ACK_TYPE:通知broker已经消息已经投递到消费者,但还没做处理
STANDARD_ACK_TYPE:通知broker消息处理成功
REDELIVERED_ACK_TYPE:通知broker消息重发
INDIVIDUAL_ACK_TYPE:表示手动确认时,只确认单条消息
UNMATCHED_ACK_TYPE :表示Topic中,如果一条消息在转发给“订阅者”时,发现此消息不符合Selector过滤条件,那么此消息将 不会转发给订阅者,消息将会被存储引擎删除(相当于在Broker上确认了消息)。
POSION_ACK_TYPE :消息”错误”,通常表示”抛弃”此有毒消息,比如消息重发多次后,都无法正确处理时,消息将会被删除或者DLQ(死信队列)

猜你喜欢

转载自blog.csdn.net/qq_20597727/article/details/81322507
今日推荐