AMQ生产者和Broker交互完整流程如下图。
该图完整的描述了Producer发送消息时和Broker的整个交互流程。
由于该过程交互较多,可以将整个流程分为三个部分。
1、第一部分为Producer和Broker建立连接,包括发送WireFormatInfo,ConnectionInfo,ConsumerInfo,SessionInfo,ProducerInfo等信息。
2、第二部分为发送消息部分,包括发送TransactionInfo开启事务,发送消息内容ActiveMQTextMessage,提交事务TransactionInfo等功能。
3、第三部分为发送消息之后的一些清理工作,包括发送两次RemoveInfo消息和最后的ShutdownInfo消息。
首先来看第一分部建立连接的时候Broker端都做了些什么。Broker端首先接收到Producer发送的WireFormatInfo消息,然后设置Broker端的协议版本。
public Response processWireFormat(WireFormatInfo info) throws Exception {
wireFormatInfo = info;
protocolVersion.set(info.getVersion());
return null;
}
客户端接收到服务端的WireFormatInfo响应信息之后,也设置协议版本,并且和服务端一致。
protected void onWireFormatInfo(WireFormatInfo info) {
protocolVersion.set(info.getVersion());
}
紧接着Broker收到Producer发送的ConnectionInfo信息,然后创建TransportConnectionState对象封装Connection,并将connectionId和TransportConnectionState的关系保存在一个Map中,以供后续使用。同时创建了ConnectionContext对象,该对象将connector,clientId,connectionInfo,wireFormatInfo等对象的值封装了起来。
第一部分的功能都比较简单,接着看第二部分的功能。
首先是Broker端接收到Producer发送的TransactionInfo信息,Broker端创建对应的LocalTransaction对象,并将xid和LocalTransaction关系维护在一个Map,供后续使用。
public void beginTransaction(ConnectionContext context, TransactionId xid) throws Exception {
。。。。。。省略
Map<TransactionId, Transaction> transactionMap = context.getTransactions();
Transaction transaction = transactionMap.get(xid);
if (transaction != null) {
throw new JMSException("Transaction '" + xid + "' has already been started.");
}
transaction = new LocalTransaction(transactionStore, (LocalTransactionId)xid, context);
transactionMap.put(xid, transaction);
}
接着Broker端接收到Producer发送的ActiveMQTextMessage信息,也就是我们平常最为关心的消息内容。Broker端接收到消息之后首先根据xid获取到LocalTransaction对象,然后获取到消息的destination,保存消息的时候会和消息进行关联,接着判断消息是否已过期,内存是否已满。
if (message.isExpired()) {
。。。。。。省略
}
if (memoryUsage.isFull()) {
。。。。。。省略
}
最后是将消息内容,事务信息封装成KahaAddMessageCommand对象,转换成对应的byte数组,保存在KahaDB中,关于消息存储这快的逻辑会在KahaDB部分仔细分析,此处不再展开。
public void addMessage(final ConnectionContext context, final Message message) throws IOException {
final KahaAddMessageCommand command = new KahaAddMessageCommand();
command.setDestination(dest);
command.setMessageId(message.getMessageId().toProducerKey());
command.setTransactionInfo(TransactionIdConversion.convert(transactionIdTransformer.transform(message.getTransactionId())));
command.setPriority(message.getPriority());
command.setPrioritySupported(isPrioritizedMessages());
org.apache.activemq.util.ByteSequence packet = wireFormat.marshal(message);
command.setMessage(new Buffer(packet.getData(), packet.getOffset(), packet.getLength()));
。。。。。。保存到KahaDB中
}
消息保存之后Broker会接收到Producer发送的事务提交消息,Broker会构造如下数据结构,并转成对应的字节数组,存入KahaDB中。
transaction_info {
local_transaction_id {
connection_id: ID:jiangzhiqiangdeMacBook-Pro.local-51411-1556719361587-1:1
transaction_id: 1
}
}
最后更新最后一次保存的索引地址。
metadata.lastUpdate = location;
事务,索引相关的内容也会在KahaDB部分仔细分析。
第三份部分主要是做一些清理工作,我们重点来看为什么Producer为什么要发送两次RemoveInfo信息。
第一次发送RemoveInfo信息,最终调用processRemoveConsumer方法,主要清理本次请求Broker的客户端信息,如清理当前连接的Consumer信息,Destination,断开订阅等。
protected void destroySubscription(Subscription sub) {
sub.destroy();
}
第二次发送RemoveInfo信息,执行processRemoveConnection方法,主要执行断开session连接,断开connection操作,然后从Broker删除Producer,Session,DestinationInfo,TransportConnection等信息,注意针对的不是本次的连接,而是所有的。Broker会遍历所有的Session对象,逐个断开连接,清除对应的属性值。
public void shutdown() {
if (shutdown.compareAndSet(false, true)) {
for (Iterator<SessionState> iter = sessions.values().iterator(); iter.hasNext();) {
SessionState ss = iter.next();
ss.shutdown();
}
}
}
本篇的主要介绍了Producer和Broker交互的整个过程,其中跟存储,索引,事务相关的实现原理和细节将在KahaDB部分进行详细的分析,此处暂且略过。