prefacio
Esta vez, Fangmu tiene la intención de compartir una serie de análisis de código fuente directamente en el lado del RabbitMQ Java
cliente ( com.rabbitmq:amqp-client:4.8.3
)
pd: Recientemente recibí la tarea de la compañía para leer y analizar
spring-rabbit
,amqp-client
así que planeo compartirla con ustedesamqp-client
. PorqueRabbitMQ
esErlang
el desarrollo del lenguaje (no hay ningún plan para compartir esta pieza por el momento)Recordatorio amistoso: este intercambio es adecuado para personas que necesitan
RabbitMQ
tener una cierta comprensión de
- RabbitMQ Empezar: www.rabbitmq.com/#getstarted
- Guía de la API del cliente Java: www.rabbitmq.com/api-guide.h…
No hay muchas tonterías, ¡comencemos! ! !
Demostración de conexión de cliente Java
Veamos primero un sitio web oficial provistoJava 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();
复制代码
Proceso de interacción del protocolo AMQP
Los estudiantes que han usado RabbitMQ
, creen que no son extraños, por lo que describiré brevemente el proceso de RabbitMQ Broker
establecer Connection
y Channel
, luego de enviar un mensaje, y cerrar Connection
y Channel
. La siguiente figura es el proceso interactivo de usar la Wireshark
captura de paquetes ver el AMQP
protocolo completo para este proceso ( 172.30.0.74
para el cliente, es decir, la ip local; 192.168.17.160
para RabbitMQ Broker
la ip de)
El cliente y el corredor crean la conexión, el canal y envían mensajes
Cliente y broker envían heartbeat (Heartbeat), cierran 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 收到并创建通道完成) -
El cliente envía
Confirm.Select
y el corredor regresa después de recibirloConfirm.SelectOk
(el cliente le dice al corredor que el mensaje debe usar el mecanismo de confirmación, y el corredor recibe y responde) -
El cliente envía un mensaje
Basic.Publish
y el corredor respondeBasic.Ack
-
Durante este período, el cliente y el corredor verificarán los latidos del corazón del otro.
heartbeat
-
El cliente cierra el canal
Channel.Close
y el corredor respondeChannel.CloseOk
-
El cliente cierra la conexión
Connection.Close
y el corredor respondeConnection.CloseOk
Análisis de código fuente
Después de estar familiarizado con AMQP
el proceso de interacción del protocolo, es fácil entender el código fuente más adelante. Esta vez presentamos principalmente el código fuente relacionado con la conexión:ConnectionFactory.newConnection --> AMQConnection.start
ConnectionFactory.nuevaConexión()
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
La lógica en el proceso de interacción 1~6
del pertenece a AMQConnection.start()
la lógica clave de , y también es el punto principal que se le presenta esta vez.
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;
}
复制代码
finalmente
El propósito de este intercambio es primero permitir que los RabbitMQ Client
lectores RabbitMQ Broker
tengan una comprensión general del proceso de interacción con el protocolo AMQP, y tener una cierta comprensión basada en el análisis del código fuente de Connection. Hay muchos códigos fuente Connection
detallados que los lectores necesitan para entender lentamente.
Mi cuenta pública de WeChat: Programación avanzada Java Architect
Concéntrese en compartir productos secos de tecnología Java, ¡esperamos su atención!