RabbitMQ client source code series - Pulish Message

foreword

This time I plan to continue sharing RabbitMQ Client pulish--send messages. First, I will make a brief summary of the RabbitMQ client source code - Connection and RabbitMQ client source code - Channel发布消息 - Pulish Message and shared before (still based on the previous ones Java Client Connecting to RabbitMQ Demo)

RabbitMQ pulisher summary

It can be seen from the figure that RabbitMQ publishes the message process:ConnectionFactory --> Connection --> Channel --> Pulish Message

Pulish Message interactive packet capture

Old routine -- capture the package before sharing the source code ( to help with quick understanding )

Basic Publish & Ack

You can see from the packet capture: after the pulisher (RabbitMQ message sender) and the Broker (RabbitMQ Broker) are Channelopened , they initiate again Confirm.Select/Select-Ok-- notifying the Broker that the receipt of the published message needs to be confirmed, so there is a follow-upBasic.Pulish/Ack

After sorting out the interaction process, we start to enter today's themePulish Message

Analysis of Pulish Message source code

Publish message general entry -- ChannelN.basicPublish()

/** Public API - {@inheritDoc} */
@Override
public void basicPublish(String exchange, String routingKey,
                         boolean mandatory,
                         BasicProperties props, byte[] body)
  throws IOException
{
  basicPublish(exchange, routingKey, mandatory, false, props, body);
}


/** Public API - {@inheritDoc} */
@Override
public void basicPublish(String exchange, String routingKey,
                         boolean mandatory, boolean immediate,
                         BasicProperties props, byte[] body)
  throws IOException
{
  // Pulisher 配置了 `Confirm.Select` nextPublishSeqNo 设置从 1 开始
  // 将未确认的消息 放入 unconfirmedSet,并 自增加一
  if (nextPublishSeqNo > 0) {
    unconfirmedSet.add(getNextPublishSeqNo());
    nextPublishSeqNo++;
  }
  BasicProperties useProps = props;
  if (props == null) {
    useProps = MessageProperties.MINIMAL_BASIC;
  }
  // 构造 AMQCommand 并传输
  transmit(new AMQCommand(new Basic.Publish.Builder()
                          .exchange(exchange)
                          .routingKey(routingKey)
                          .mandatory(mandatory)
                          .immediate(immediate)
                          .build(),
                          useProps, body));
  // 用于指标统计和监控,默认是 NoOpMetricsCollector,需要配置才会可以使用 提供的 MicrometerMetricsCollector 和 StandardMetricsCollector(引入对应的包和配置 开箱即可食用~)
  metricsCollector.basicPublish(this);
}
复制代码

Construct AMQCommand

It is worth mentioning that the smallest unit of RabbitMQ Client application message is Frame(frame, mentioned in the Connection chapter), and Frame is mainly composed type 类型、channel 通道、payload 消息内容字节、accumulator 写出数据、NON_BODY_SIZEof

Frame structure

public class Frame {
    /** Frame type code */
  	// FRAME_HEARTBEAT :心跳, FRAME_METHOD: 方法, FRAME_HEADER : 头部信息, FRAME_BODY 内容主题
    public final int type;

    /** Frame channel number, 0-65535 */
  	// channel 序列号
    public final int channel;

    /** Frame payload bytes (for inbound frames) */
  	// 消息内容字节
    private final byte[] payload;

    /** Frame payload (for outbound frames) */
    // 写出数据
    private final ByteArrayOutputStream accumulator;

    private static final int NON_BODY_SIZE = 1 /* type */ + 2 /* channel */ + 4 /* payload size */ + 1 /* end character */;
 
  ...
  
}
复制代码

AMQP 0-9-1 specific Command read, accumulated from a series of frames方法、头部和正文

/**
* Construct a command with a specified method, header and body.
* @param method the wrapped method
* @param contentHeader the wrapped content header
* @param body the message body data
*/
public AMQCommand(com.rabbitmq.client.Method method, AMQContentHeader contentHeader, byte[] body) {
	this.assembler = new CommandAssembler((Method) method, contentHeader, body);
}

// AMQP 0-9-1 特定的Command,构造 方法、头部和正文
public CommandAssembler(Method method, AMQContentHeader contentHeader, byte[] body) {
  this.method = method;
  this.contentHeader = contentHeader;
  this.bodyN = new ArrayList<byte[]>(2);
  this.bodyLength = 0;
  this.remainingBodyBytes = 0;
  appendBodyFragment(body);
  if (method == null) {
    this.state = CAState.EXPECTING_METHOD;
  } else if (contentHeader == null) {
    this.state = method.hasContent() ? CAState.EXPECTING_CONTENT_HEADER : CAState.COMPLETE;
  } else {
    this.remainingBodyBytes = contentHeader.getBodySize() - this.bodyLength;
    updateContentBodyState();
  }
}
复制代码

Transmit AMQCommand -- Channel.transmit()

public void transmit(AMQCommand c) throws IOException {
  synchronized (_channelMutex) {
    // 确认 channel 是否打开(逻辑比较简单:判断 shutdownCause 为空即是打开)
    ensureIsOpen();
    quiescingTransmit(c);
  }
}

public void quiescingTransmit(AMQCommand c) throws IOException {
  // 防止并发同时使用 同一个channel
  synchronized (_channelMutex) {
    // 判断 该消息是否 携带content,如果有 需要判断该 channel是否是阻塞(如果channel state为 `FLOW` 即为 阻塞 _blockContent = true)
    if (c.getMethod().hasContent()) {
      while (_blockContent) {
        try {
          _channelMutex.wait();
        } catch (InterruptedException ignored) {}

        // 防止 从阻塞中被唤醒时,channel 已经关闭(挺好的一个 多线程操作的案例)
        ensureIsOpen();
      }
    }
    c.transmit(this);
  }
}
复制代码

AMQCommand.transmit

/**
 * Sends this command down the named channel on the channel's
 * connection, possibly in multiple frames.
 * @param channel the channel on which to transmit the command
 * @throws IOException if an error is encountered
 */
public void transmit(AMQChannel channel) throws IOException {
  // 每个 channel 都有序列号 从 0开始,(0是特殊的channel)
  int channelNumber = channel.getChannelNumber();
  AMQConnection connection = channel.getConnection();

  synchronized (assembler) {
    // 方法:FRAME_HEARTBEAT :心跳, FRAME_METHOD: 方法, FRAME_HEADER : 头部信息, FRAME_BODY 内容主题
    Method m = this.assembler.getMethod();
    if (m.hasContent()) {
      byte[] body = this.assembler.getContentBody();

      // FRAME_HEADER : 头部信息
      Frame headerFrame = this.assembler.getContentHeader().toFrame(channelNumber, body.length);

      int frameMax = connection.getFrameMax();
      boolean cappedFrameMax = frameMax > 0;
      int bodyPayloadMax = cappedFrameMax ? frameMax - EMPTY_FRAME_SIZE : body.length;

      if (cappedFrameMax && headerFrame.size() > frameMax) {
        String msg = String.format("Content headers exceeded max frame size: %d > %d", headerFrame.size(), frameMax);
        throw new IllegalArgumentException(msg);
      }
      // 1. 写 channelNumber帧  FRAME_METHOD
      connection.writeFrame(m.toFrame(channelNumber));
      // 2. 写 头部信息帧   AMQP.FRAME_HEADER
      connection.writeFrame(headerFrame);

      // 3. 如果 body过多,会拆成多个帧  AMQP.FRAME_BODY
      for (int offset = 0; offset < body.length; offset += bodyPayloadMax) {
        int remaining = body.length - offset;

        int fragmentLength = (remaining < bodyPayloadMax) ? remaining
          : bodyPayloadMax;
        Frame frame = Frame.fromBodyFragment(channelNumber, body,
                                             offset, fragmentLength);
        connection.writeFrame(frame);
      }
    } else {
      // 1. 写 channelNumber帧  FRAME_METHOD
      connection.writeFrame(m.toFrame(channelNumber));
    }
  }
	// 最后刷新 输出缓冲区
  connection.flush();
}
复制代码

Finally, analyze connection.writeFrame(frame)

/**
 * Public API - sends a frame directly to the broker.
 */
public void writeFrame(Frame f) throws IOException {
  _frameHandler.writeFrame(f);
  // lastActivityTime
  _heartbeatSender.signalActivity();
}

@Override
public void writeFrame(Frame frame) throws IOException {
  synchronized (_outputStream) {
    frame.writeTo(_outputStream);
  }
}

/**
 * Public API - writes this Frame to the given DataOutputStream
 */
public void writeTo(DataOutputStream os) throws IOException {
  // 1. 写type 类型
  os.writeByte(type);
  // 2. 写channel 序列号
  os.writeShort(channel);
  if (accumulator != null) {
    // 3. 写出数据大小
    os.writeInt(accumulator.size());
    // 4. 输出数据
    accumulator.writeTo(os);
  } else {
    // 3. 写消息内容字节大小
    os.writeInt(payload.length);
    // 4. 写消息内容
    os.write(payload);
  }
  // 5. 帧结束标志位
  os.write(AMQP.FRAME_END);
}
复制代码

finally

I hope it can give you a clear understanding of RabbitMQ Clientand accordingRabbitMQ Broker to the AMQP protocol , and help you familiarize yourself with the source code. Of course, there are many details of the source code that need to be slowly tasted by readers~发布消息ChannelN.basicPublish


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/7083484960375439368