RabbitMQ client source code series - Connection

foreword

This time, Fangmu intends to share a series of source code analysis on the RabbitMQ Javaclient ( com.rabbitmq:amqp-client:4.8.3)

ps: I recently received the company's task to read and analyze spring-rabbit, amqp-clientso I plan to share it with you amqp-client. Because it RabbitMQis Erlanglanguage development (there is no plan to share this piece for the time being)

Friendly reminder: This sharing is suitable for people who need to RabbitMQhave a certain understanding of

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 Brokerestablishing Connectionand Channel, after sending a message, and closing Connectionand Channel. The following figure is the interactive process of using the Wiresharkpacket capture view the entire AMQPprotocol for this process ( 172.30.0.74for the client, that is, the local ip; 192.168.17.160for RabbitMQ Brokerthe ip of )

Client and broker create Connection, Channel, and send messages

Client and broker send heartbeat (Heartbeat), close Connection, Channel

为了让读者更容易看得源码,我先给大家描述下 clientbroker 之间 AMQP 协议的交互流程描述(AMQP 协议中 不少命令都是成对存在的,抓包协议中 Info 里的命令是 -,而代码里的是 驼峰式 此处以代码为准):

  1. AMQP 0-9-1 的连接头写入底层套接字,包含指定的版本信息(客户端告诉 broker 自己使用的协议及版本,底层使用 java 自带的 socket)

  2. 客户端等待 broker 发送的 Connection.Start (broker 告诉客户端 通信的协议和版本、SASL认证机制(详细见)、语言环境以及RabbitMQ的版本信息和支持能力)

  3. 客户端接收后 发送 Connection.StartOk (客户端告诉 broker 连接使用的帐号和密码、认证机制、语言环境、客户的信息以及能力)

  4. 客户端等待 broker 发送的 Connection.Tune (broker 与 客户端 进行参数协商)

  5. 客户端接收后 发送 Connection.TuneOk (客户端 参数 [ChannelMax、FrameMax、Heartbeat] 协商完成后告诉 broker)

  6. 客户端发送 Connection.Open (客户端 告诉 broker 打开一个连接,并请求设置_virtualHost [vhost])

  7. broker 接收到后返回 Connection.OpenOk (client 对 vhost 进行验证,成功则返回如下此信息)

  8. 客户端发送 Channel.Open,broker 接收到后返回 Channel.OpenOk (客户端 创建通道;broker 收到并创建通道完成)

  9. The client sends Confirm.Select, and the broker returns after receiving it Confirm.SelectOk(the client tells the broker that the message needs to use the confirm mechanism, and the broker receives and replies)

  10. The client sends a message Basic.Publish, and the broker responds backBasic.Ack

  11. During this period, the client and the broker will check each other's heartbeatsheartbeat

  12. The client closes the channel Channel.Close, and the broker responds backChannel.CloseOk

  13. The client closes the connection Connection.Close, and the broker responds backConnection.CloseOk

Source code analysis

After being familiar with AMQPthe 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");
        }
    }
复制代码

AMQPThe logic in the interaction process of 1~6the 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 Clientreaders RabbitMQ Brokerhave 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 Connectiondetailed 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!

Guess you like

Origin juejin.im/post/7080141024403816456