foreword
This time, Fangmu intends to share a series of source code analysis on the RabbitMQ Java
client ( com.rabbitmq:amqp-client:4.8.3
)
ps: I recently received the company's task to read and analyze
spring-rabbit
,amqp-client
so I plan to share it with youamqp-client
. Because itRabbitMQ
isErlang
language development (there is no plan to share this piece for the time being)Friendly reminder: This sharing is suitable for people who need to
RabbitMQ
have a certain understanding of
- RabbitMQ Getstarted: www.rabbitmq.com/#getstarted
- Java Client API Guide: www.rabbitmq.com/api-guide.h…
Not much nonsense, let's start! ! !
Java Client Connection Demo
Let's first look at an official website providedJava Client Connecting to RabbitMQ Demo
ConnectionFactory factory = new ConnectionFactory();
// "guest"/"guest" by default, limited to localhost connections
factory.setUsername(userName);
factory.setPassword(password);
factory.setVirtualHost(virtualHost);
factory.setHost(hostName);
factory.setPort(portNumber);
Connection conn = factory.newConnection();
Channel channel = connection.createChannel();
byte[] messageBodyBytes = "Hello, world!".getBytes();
channel.basicPublish(EXCHANGE_NAME, routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, messageBodyBytes);
channel.close();
connection.close();
复制代码
AMQP protocol interaction process
Students who have used RabbitMQ
, believe that they are no strangers, so I will briefly describe the process of RabbitMQ Broker
establishing Connection
and Channel
, after sending a message, and closing Connection
and Channel
. The following figure is the interactive process of using the Wireshark
packet capture view the entire AMQP
protocol for this process ( 172.30.0.74
for the client, that is, the local ip; 192.168.17.160
for RabbitMQ Broker
the ip of )
Client and broker create Connection, Channel, and send messages
Client and broker send heartbeat (Heartbeat), close Connection, Channel
为了让读者更容易看得源码,我先给大家描述下 client
与 broker
之间 AMQP
协议的交互流程描述(AMQP
协议中 不少命令都是成对存在的,抓包协议中 Info
里的命令是 -
,而代码里的是 驼峰式
此处以代码为准):
-
将
AMQP 0-9-1
的连接头写入底层套接字,包含指定的版本信息(客户端告诉 broker 自己使用的协议及版本,底层使用 java 自带的 socket) -
客户端等待 broker 发送的
Connection.Start
(broker 告诉客户端 通信的协议和版本、SASL认证机制(详细见)、语言环境以及RabbitMQ的版本信息和支持能力) -
客户端接收后 发送
Connection.StartOk
(客户端告诉 broker 连接使用的帐号和密码、认证机制、语言环境、客户的信息以及能力) -
客户端等待 broker 发送的
Connection.Tune
(broker 与 客户端 进行参数协商) -
客户端接收后 发送
Connection.TuneOk
(客户端 参数 [ChannelMax、FrameMax、Heartbeat] 协商完成后告诉 broker) -
客户端发送
Connection.Open
(客户端 告诉 broker 打开一个连接,并请求设置_virtualHost [vhost]) -
broker 接收到后返回
Connection.OpenOk
(client 对 vhost 进行验证,成功则返回如下此信息) -
客户端发送
Channel.Open
,broker 接收到后返回Channel.OpenOk
(客户端 创建通道;broker 收到并创建通道完成) -
The client sends
Confirm.Select
, and the broker returns after receiving itConfirm.SelectOk
(the client tells the broker that the message needs to use the confirm mechanism, and the broker receives and replies) -
The client sends a message
Basic.Publish
, and the broker responds backBasic.Ack
-
During this period, the client and the broker will check each other's heartbeats
heartbeat
-
The client closes the channel
Channel.Close
, and the broker responds backChannel.CloseOk
-
The client closes the connection
Connection.Close
, and the broker responds backConnection.CloseOk
Source code analysis
After being familiar with AMQP
the interaction process of the protocol, it is easy to understand the source code later. This time we mainly introduce the source code related to Connection:ConnectionFactory.newConnection --> AMQConnection.start
ConnectionFactory.newConnection()
public Connection newConnection(ExecutorService executor, AddressResolver addressResolver, String clientProvidedName)
throws IOException, TimeoutException {
if(this.metricsCollector == null) {
this.metricsCollector = new NoOpMetricsCollector();
}
// make sure we respect the provided thread factory
// 创建 socketFactory 和 初始化相应的配置
FrameHandlerFactory fhFactory = createFrameHandlerFactory();
// 初始化 Connection 涉及到的参数
ConnectionParams params = params(executor);
// set client-provided via a client property
if (clientProvidedName != null) {
Map<String, Object> properties = new HashMap<String, Object>(params.getClientProperties());
properties.put("connection_name", clientProvidedName);
params.setClientProperties(properties);
}
// 这块逻辑属于 rabbit提供自动回复连接的逻辑
if (isAutomaticRecoveryEnabled()) {
// see com.rabbitmq.client.impl.recovery.RecoveryAwareAMQConnectionFactory#newConnection
AutorecoveringConnection conn = new AutorecoveringConnection(params, fhFactory, addressResolver, metricsCollector);
conn.init();
return conn;
} else {
List<Address> addrs = addressResolver.getAddresses();
Exception lastException = null;
for (Address addr : addrs) {
try {
// 创建、连接 socket 并封装成 返回 SocketFrameHandler (socket 不采用Negale算法[Negale算法,大家有兴趣可以了解下这块针对socket缓存性能的优化])
FrameHandler handler = fhFactory.create(addr);
// 初始化配置、_channel0、_channelManager等等
AMQConnection conn = createConnection(params, handler, metricsCollector);
// 启动 AMQConnection 后续会进行详细介绍
conn.start();
this.metricsCollector.newConnection(conn);
return conn;
} catch (IOException e) {
lastException = e;
} catch (TimeoutException te) {
lastException = te;
}
}
if (lastException != null) {
if (lastException instanceof IOException) {
throw (IOException) lastException;
} else if (lastException instanceof TimeoutException) {
throw (TimeoutException) lastException;
}
}
throw new IOException("failed to connect");
}
}
复制代码
AMQP
The logic in the interaction process of 1~6
the belongs to AMQConnection.start()
the key logic of , and it is also the main point introduced to you this time.
public void start()
throws IOException, TimeoutException {
// 初始化工作线程
initializeConsumerWorkService();
// 初始化心跳发送
initializeHeartbeatSender();
// 将 Connection标志位 启动
this._running = true;
// 确认客户端 第一件事 发送header头部协议
AMQChannel.SimpleBlockingRpcContinuation connStartBlocker =
new AMQChannel.SimpleBlockingRpcContinuation();
// 进入Rpc队列进行阻塞,等待broker返回 connection.start method
_channel0.enqueueRpc(connStartBlocker);
try {
// The following two lines are akin to AMQChannel's
// transmit() method for this pseudo-RPC.
_frameHandler.setTimeout(handshakeTimeout);
// 1. 发送header头部协议 AMQP 0-9-1
_frameHandler.sendHeader();
} catch (IOException ioe) {
_frameHandler.close();
throw ioe;
}
// 初始化启动 startMainLoop -- 为了接收和处理broker发送的消息
this._frameHandler.initialize(this);
AMQP.Connection.Start connStart;
AMQP.Connection.Tune connTune = null;
try {
// 2. 客户端等待 broker 发送的 Connection.Start
connStart =
(AMQP.Connection.Start) connStartBlocker.getReply(handshakeTimeout/2).getMethod();
// 通信的协议和版本、SASL认证机制(详细见)、语言环境以及RabbitMQ的版本信息和支持能力
_serverProperties = Collections.unmodifiableMap(connStart.getServerProperties());
Version serverVersion =
new Version(connStart.getVersionMajor(),
connStart.getVersionMinor());
// 版本比对
if (!Version.checkVersion(clientVersion, serverVersion)) {
throw new ProtocolVersionMismatchException(clientVersion,
serverVersion);
}
String[] mechanisms = connStart.getMechanisms().toString().split(" ");
SaslMechanism sm = this.saslConfig.getSaslMechanism(mechanisms);
if (sm == null) {
throw new IOException("No compatible authentication mechanism found - " +
"server offered [" + connStart.getMechanisms() + "]");
}
String username = credentialsProvider.getUsername();
String password = credentialsProvider.getPassword();
LongString challenge = null;
LongString response = sm.handleChallenge(null, username, password);
do {
// 3. 客户端接收后 发送 `Connection.StartOk`
Method method = (challenge == null)
? new AMQP.Connection.StartOk.Builder()
.clientProperties(_clientProperties)
.mechanism(sm.getName())
.response(response)
.build()
: new AMQP.Connection.SecureOk.Builder().response(response).build();
try {
Method serverResponse = _channel0.rpc(method, handshakeTimeout/2).getMethod();
if (serverResponse instanceof AMQP.Connection.Tune) {
// 4. 客户端等待 broker 发送的 Connection.Tune
connTune = (AMQP.Connection.Tune) serverResponse;
} else {
challenge = ((AMQP.Connection.Secure) serverResponse).getChallenge();
response = sm.handleChallenge(challenge, username, password);
}
} catch (ShutdownSignalException e) {
Method shutdownMethod = e.getReason();
if (shutdownMethod instanceof AMQP.Connection.Close) {
AMQP.Connection.Close shutdownClose = (AMQP.Connection.Close) shutdownMethod;
if (shutdownClose.getReplyCode() == AMQP.ACCESS_REFUSED) {
throw new AuthenticationFailureException(shutdownClose.getReplyText());
}
}
throw new PossibleAuthenticationFailureException(e);
}
} while (connTune == null);
} catch (TimeoutException te) {
_frameHandler.close();
throw te;
} catch (ShutdownSignalException sse) {
_frameHandler.close();
throw AMQChannel.wrap(sse);
} catch(IOException ioe) {
_frameHandler.close();
throw ioe;
}
try {
// 最大通道数
int channelMax =
negotiateChannelMax(this.requestedChannelMax,
connTune.getChannelMax());
_channelManager = instantiateChannelManager(channelMax, threadFactory);
// 帧最大的大小
int frameMax =
negotiatedMaxValue(this.requestedFrameMax,
connTune.getFrameMax());
this._frameMax = frameMax;
// 心跳
int heartbeat =
negotiatedMaxValue(this.requestedHeartbeat,
connTune.getHeartbeat());
setHeartbeat(heartbeat);
// 5. 客户端接收后 发送 Connection.TuneOk
_channel0.transmit(new AMQP.Connection.TuneOk.Builder()
.channelMax(channelMax)
.frameMax(frameMax)
.heartbeat(heartbeat)
.build());
// 6. 客户端发送 Channel.Open
_channel0.exnWrappingRpc(new AMQP.Connection.Open.Builder()
.virtualHost(_virtualHost)
.build());
} catch (IOException ioe) {
_heartbeatSender.shutdown();
_frameHandler.close();
throw ioe;
} catch (ShutdownSignalException sse) {
_heartbeatSender.shutdown();
_frameHandler.close();
throw AMQChannel.wrap(sse);
}
// We can now respond to errors having finished tailoring the connection
this._inConnectionNegotiation = false;
}
复制代码
finally
The purpose of this sharing is to first let the RabbitMQ Client
readers RabbitMQ Broker
have a general understanding of the interaction process with the AMQP protocol, and have a certain understanding based on the analysis of the Connection source code. There are many Connection
detailed source codes that the readers need to understand slowly.
My WeChat public account: Java Architect Advanced Programming
Focus on sharing Java technology dry goods, looking forward to your attention!