ActiveMQ 原理分析—消息发送篇

持久化和非持久化消息发送的策略

通过setDeliveMode设置持久跟非持久属性。
消息的同步发送,跟异步发送:

  1. 消息的同步发送跟异步发送是针对broker 而言。
    在默认情况下,非持久化的消息是异步发送的。
    非持久化消息且非事物模式下是同步发送的。
    在开启事务的情况下,消息都是异步发送的。

  2. 通过以下三种方式来设置异步发送:

    • 在链接的URL 传递参数jms.useAsyncSend=true
       ConnectionFactory connectionFactory=new ActiveMQConnectionFactory("tcp://127.0.0.1:61616?jms.useAsyncSend=true");
    
    • 调用ConnectionFactory的API 来设置。
    ((ActiveMQConnectionFactory) connectionFactory).setUseAsyncSend(true);
    
    • 调用Connection的API来设置
    ((ActiveMQConnection)connection).setUseAsyncSend(true);
    

    消息发送的流程图如下:
    在这里插入图片描述

  3. 源码入口ActiveMQMessageProducer#send()

public void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive, AsyncCallback onComplete) throws JMSException {
        checkClosed(); //检查session的状态,如果session关闭则抛异常
        if (destination == null) {
            if (info.getDestination() == null) {
                throw new UnsupportedOperationException("A destination must be specified.");
            }
            throw new InvalidDestinationException("Don't understand null destinations");
        }
        //检查destination的类型,如果符合要求,就转变为ActiveMQDestination
        ActiveMQDestination dest;
        if (destination.equals(info.getDestination())) {
            dest = (ActiveMQDestination)destination;
        } else if (info.getDestination() == null) {
            dest = ActiveMQDestination.transform(destination);
        } else {
            throw new UnsupportedOperationException("This producer can only send messages to: " + this.info.getDestination().getPhysicalName());
        }
        if (dest == null) {
            throw new JMSException("No destination specified");
        }
 
        if (transformer != null) {
            Message transformedMessage = transformer.producerTransform(session, this, message);
            if (transformedMessage != null) {
                message = transformedMessage;
            }
        }
        //如果发送窗口大小不为空,则判断发送窗口的大小决定是否阻塞
        if (producerWindow != null) {
            try {
                producerWindow.waitForSpace();
            } catch (InterruptedException e) {
                throw new JMSException("Send aborted due to thread interrupt.");
            }
        }
        //发送消息到broker的topic
        this.session.send(this, dest, message, deliveryMode, priority, timeToLive, producerWindow, sendTimeout, onComplete);
 
        stats.onMessage();
    }
  1. ActiveMQSession#send()方法去做真正的发送
 				 //。。。。这里省略一部分源码,主要做了对消息的封装,
                msg.setConnection(this.connection);
                msg.onSend();
                msg.setProducerId(msg.getMessageId().getProducerId());
				
				
	//消息异步发送的判断的条件: 回掉onComplete不为空、超时时间、不需要反馈、是否为异步,是否持久化
				 if (onComplete==null && sendTimeout <= 0 && !msg.isResponseRequired() && !connection.isAlwaysSyncSend() (!msg.isPersistent() || connection.isUseAsyncSend() || txid != null)) {
				                this.connection.asyncSendPacket(msg);
	            if (producerWindow != null) {
	                    int size = msg.getSize();//异步发送的情况下,需要设置producerWindow的大小
	                    producerWindow.increaseUsage(size);
	                }
	            } else {
	                if (sendTimeout > 0 && onComplete==null) {
	                    this.connection.syncSendPacket(msg,sendTimeout);
	                }else {
	                    this.connection.syncSendPacket(msg, onComplete);
	                }
	            }
  1. 首先我们看下异步发送
    异步发送的情况下,需要设置producerWindow的大小,producer每发送一个消息,统计一下发送的字节数,当发送的总字节数达到ProducerWindowSize峰值时,需要等待broker的确认。主要用来约束在异步发送时producer端允许积压的(尚未ACK)的消息的大小。每次发送消息之后,都将会导致memoryUsage大小增加(+msg.size),当broker返回producerAck时,memoryUsage尺寸减少(producerAck.size,此size表示先前发送消息的大小)。
    ProducerWindowSize值初始化的方式有2种

    在brokerUrl中设置: “tcp://127.0.0.1:61616?jms.producerWindowSize=10204”,这种设置将会对所有的producer生效。
    在destinationUri中设置: “myQueue?producer.windowSize=10204”,此参数只会对使用此Destination实例的producer生效,将会覆盖上面brokerUrl中的producerWindowSize值

  2. 接下来我们接着看ActiveMQConnection#asyncSendPacket方法
    在这里插入图片描述
    紧接着我们分析下transport 对象是如何初始化的?通过ActiveMQConnection对象构造器可以看出,transport对象是通过初始化Connection链接的时候创建的。创建ActiveMQConnection对象代码如下:

ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://127.0.0.161616");
Connection connection= connectionFactory.createConnection();

在创建connection 方法中我们发现transport创建的代码如下:

在这里插入图片描述
createTransport();来创建Transport对象
在这里插入图片描述
TransportFactory#connect(java.net.URI)

 public static Transport connect(URI location) throws Exception {
        TransportFactory tf = findTransportFactory(location);
        return tf.doConnect(location);
    }

1.创建上一步传入的URL 创建对应的 TransportFactory,调用TransportFactory#findTransportFactory方法创建工厂对象:
在这里插入图片描述
其中TRANSPORT_FACTORY_FINDER定义如下

FactoryFinder TRANSPORT_FACTORY_FINDER = new FactoryFinder("META-INF/services/org/apache/activemq/transport/");

这点类似与之前dubbo 源码分析中的SPI思想,将需要加载的类写入固定文件夹下,通过解析加载配置文件来初始化对应的对象。
在这里插入图片描述
tcp 文件中的定义如下
在这里插入图片描述
因此 findTransportFactory创建的工厂对象为TcpTransportFactory

2.调用TransportFactory#doConnect(java.net.URI)创建Transport 对象
在这里插入图片描述
TransportFactory#createTransport方法,这里的TransportFactory为TcpTransportFactory

protected Transport createTransport(URI location, WireFormat wf) throws UnknownHostException, IOException {
        URI localLocation = null;
        String path = location.getPath();
        if(path != null && path.length() > 0) {
            int localPortIndex = path.indexOf(58);

            try {
                Integer.parseInt(path.substring(localPortIndex + 1, path.length()));
                String localString = location.getScheme() + ":/" + path;
                localLocation = new URI(localString);
            } catch (Exception var7) {
                LOG.warn("path isn't a valid local location for TcpTransport to use", var7.getMessage());
                if(LOG.isDebugEnabled()) {
                    LOG.debug("Failure detail", var7);
                }
            }
        }

        SocketFactory socketFactory = this.createSocketFactory(); //因为目前是Tcp 传输的
        return this.createTcpTransport(wf, socketFactory, location, localLocation);//因此得到的是tcptransport
    }

从代码上看返回的是一个tcptransport 对象再往上看返回的是rc 对象。通过TransportFactory#configure对创建好的tcptransport 进行包装代码如下:

public Transport configure(Transport transport, WireFormat wf, Map options) throws Exception {
//组装一个复合的transport,这里会包装两层,一个是IactivityMonitor.另一个是WireFormatNegotiator
        transport = compositeConfigure(transport, wf, options);
        //再做一层包装,MutexTransport
        transport = new MutexTransport(transport);
        //包装ResponseCorrelator
        transport = new ResponseCorrelator(transport);
        return transport;
    }

到目前为止,tcptransport 是一系列的包装 ResponseCorrelator(MutexTransport(WireFormatNegotiator(IactivityMonitor(TcpTransport()))
ResponseCorrelator 异步请求包装。
MutexTransport 加锁包装。
WireFormatNegotiator发送数据解析相关协议包装。
IactivityMonitor 心跳机制包装。

至此transport 对象就创建完毕。我们回到之前的消息异步发送.syncSendPacket(msg)方法

private void doAsyncSendPacket(Command command) throws JMSException {
        try {
            this.transport.oneway(command);
        } catch (IOException e) {
            throw JMSExceptionSupport.create(e);
        }
    }

这里的transport对象指的是ResponseCorrelator,onway 设置了Command

 public void oneway(Object o) throws IOException {
        Command command = (Command)o;
        command.setCommandId(this.sequenceGenerator.getNextSequenceId());
        command.setResponseRequired(false);
        this.next.oneway(command);
    }

next 指的是MutexTransport ,主要是加锁,

  public void oneway(Object command) throws IOException {
        this.writeLock.lock();

        try {
            this.next.oneway(command);
        } finally {
            this.writeLock.unlock();
        }

    }

next 指的的WireFormatNegotiator#oneway 解析我们发送的内容

 public void oneway(Object command) throws IOException {
        boolean wasInterrupted = Thread.interrupted();

        try {
            if(this.readyCountDownLatch.getCount() > 0L && !this.readyCountDownLatch.await(this.negotiateTimeout, TimeUnit.MILLISECONDS)) {
                throw new IOException("Wire format negotiation timeout: peer did not send his wire format.");
            }
        } catch (InterruptedException var14) {
            InterruptedIOException interruptedIOException = new InterruptedIOException("Interrupted waiting for wire format negotiation");
            interruptedIOException.initCause(var14);

            try {
                this.onException(interruptedIOException);
            } finally {
                Thread.currentThread().interrupt();
                wasInterrupted = false;
            }

            throw interruptedIOException;
        } finally {
            if(wasInterrupted) {
                Thread.currentThread().interrupt();
            }

        }

        super.oneway(command);
    }

这个里面调用了父类的 oneway ,父类是 TransportFilter 类

  public void oneway(Object command) throws IOException {
        this.next.oneway(command);
    }

这里的next 是InactivityMonitor,我们发现并没有对此方法进行实现,我们去看他的父类。

 public void oneway(Object o) throws IOException {
        this.sendLock.readLock().lock();
        this.inSend.set(true);

        try {
            this.doOnewaySend(o);
        } finally {
            this.commandSent.set(true);
            this.inSend.set(false);
            this.sendLock.readLock().unlock();
        }

    }

doOnewaySend()通过模板模式调用子类的

private void doOnewaySend(Object command) throws IOException {
        if(this.failed.get()) {
            throw new InactivityIOException("Cannot send, channel has already failed: " + this.next.getRemoteAddress());
        } else {
            if(command.getClass() == WireFormatInfo.class) {
                synchronized(this) {
                    this.processOutboundWireFormatInfo((WireFormatInfo)command);
                }
            }

            this.next.oneway(command);
        }
    }

这里的next 其实就是我们最总的transport 对象

public void oneway(Object command) throws IOException {
        this.checkStarted();
        this.wireFormat.marshal(command, this.dataOut);
        this.dataOut.flush();
    }

通过wireFormat对数据进行格式化,然后通过sokect 对数据进行传输。至此异步发送到此结束。

8 . 同步发送过程如下

在这里插入图片描述
调用request 方法,这里的transport 对象跟异步的一样。

在这里插入图片描述
发送的过程还是异步,它与上面的异步方式的区别:
通过阻塞的方式从FutureResponse 异步拿到一个结果。拿到的过程是阻塞的,所以就实现了一个阻塞的同步同步发送。

在这里插入图片描述
同步等等过程。在这里插入图片描述
自此同步就发送完了。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值