Kafka Producer消息收发设计

前几篇文章分析了Kafka的发送流程以及NIO的使用方式,但是还是留下了不少坑,这里就对剩下的问题做一个总结。

收到的数据为什么要缓存起来?

Kafka中Selector读取从远端回来的数据的时候会先把收到的数据缓存起来

private void attemptRead(SelectionKey key, KafkaChannel channel) throws IOException {
  //if channel is ready and has bytes to read from socket or buffer, and has no
  //previous receive(s) already staged or otherwise in progress then read from it
  if (channel.ready() && (key.isReadable() || channel.hasBytesBuffered()) && !hasStagedReceive(channel)
      && !explicitlyMutedChannels.contains(channel)) {
      NetworkReceive networkReceive;
      while ((networkReceive = channel.read()) != null) {
          madeReadProgressLastPoll = true;
          addToStagedReceives(channel, networkReceive);
      }
  }
}

在NetworkClient中,往下传的是一个完整的ClientRequest,进到Selector,暂存到channel中的,也是一个完整的Send对象(1个数据包)。但这个Send对象,交由底层的channel.write(Bytebuffer b)的时候,并不一定一次可以完全发送,可能要调用多次write,才能把一个Send对象完全发出去。这是因为write是非阻塞的,不是等到完全发出去,才会返回。

Send send = channel.write();
if (send != null) {
    this.completedSends.add(send);
    this.sensors.recordBytesSent(channel.id(), send.size());
}

这里如果返回send==null就表示没有发送完毕,需要等到下一次Selector.poll再次进行发送。所以当下次发送的时候如果Channel里面的Send只发送了部分,那么此次这个node就不会处于ready状态,就不会从RecordAccumulator取出要往这个node发的数据,等到Send对象发送完毕之后,这个node才会处于ready状态,就又可以取出数据进行处理了。

同样,在接收的时候,channel.read(Bytebuffer b),一个response也可能要read多次,才能完全接收。所以就有了上面的while循环代码。

如何确定消息接收完成?

从上面知道,底层数据的通信,是在每一个channel上面,2个源源不断的byte流,一个send流,一个receive流。
send的时候,还好说,发送之前知道一个完整的消息的大小。
但是当我们接收消息response的时候,这个信息可能是不完整的(剩余的数据要晚些才能获得),也可能包含不止一条消息。那么我们是怎么判断消息发送完毕的呢?
对于消息的读取我们必须考虑消息结尾是如何表示的,标识消息结尾通常有以下几种方式:

  1. 固定的消息大小。
  2. 将消息的长度作为消息的前缀。
  3. 用一个特殊的符号来标识消息的结束。

很明显第一种和第三种方式不是很合适,因此Kafka采用了第二种方式来确定要发送消息的大小。在消息头部放入了4个字节来确定消息的大小。

//接收消息,前4个字节表示消息的大小
public class NetworkReceive implements Receive {
  private final String source;

  //确定消息size
  private final ByteBuffer size;

  private final int maxSize;

  //整个消息response的buffer
  private ByteBuffer buffer;  

  public NetworkReceive(String source) {
      this.source = source;

      //分配4字节的头部
      this.size = ByteBuffer.allocate(4);
      this.buffer = null;
      this.maxSize = UNLIMITED;
  }
}

//消息发送,前4个字节表示消息大小
public class NetworkSend extends ByteBufferSend {

  public NetworkSend(String destination, ByteBuffer buffer) {
      super(destination, sizeDelimit(buffer));
  }

  private static ByteBuffer[] sizeDelimit(ByteBuffer buffer) {
      return new ByteBuffer[] {sizeBuffer(buffer.remaining()), buffer};
  }

  private static ByteBuffer sizeBuffer(int size) {
    //4个字节表示消息大小
    ByteBuffer sizeBuffer = ByteBuffer.allocate(4);
    sizeBuffer.putInt(size);
    sizeBuffer.rewind();
    return sizeBuffer;
  }

}

OP_WRITE何时就绪?

上一篇文章虽然讲了epoll的原理,但是我相信还是有人觉得很迷惘,这里换个简单的说法再说下OP_WRITE事件。
OP_WRITE事件的就绪条件并不是发生在调用channel的write方法之后,也不是发生在调用channel.register(selector,SelectionKey.OP_WRITE)后,而是在当底层缓冲区有空闲空间的情况下。因为写缓冲区在绝大部分时候都是有空闲空间的,所以如果你注册了写事件,这会使得写事件一直处于写就绪,选择处理现场就会一直占用着CPU资源。所以,只有当你确实有数据要写时再注册写操作,并在写完以后马上取消注册。

max.in.flight.requests.per.connection

这个参数指定了生产者在收到服务器响应之前可以发送多少个消息,找Kafka Producer中对应有一个类InFlightRequests,表示在天上飞的请求,也就是请求发出去了response还没有回来的请求数,这个参数也是判断节点是否ready的关键因素。只有ready的节点数据才能从Accumulator中取出来进行发送。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要使用Kafka Producer发送消息,您需要按照以下步骤进行操作: 1. 首先,确保您已经安装并配置了Kafka。您可以从Apache Kafka官方网站下载和安装Kafka。 2. 创建一个Kafka Producer实例,并配置所需的参数,如Kafka集群的地址和端口号。 3. 使用Producer实例创建一个或多个ProducerRecord对象,该对象包含要发送的消息及其相关信息,如topic名称、消息键和值。 4. 使用Producer实例的send()方法将ProducerRecord发送到Kafka集群。 以下是一个示例代码片段,演示如何使用Java编写的Kafka Producer发送消息: ```java import org.apache.kafka.clients.producer.*; import java.util.Properties; public class KafkaProducerExample { public static void main(String[] args) { // Kafka集群的地址和端口号 String bootstrapServers = "localhost:9092"; // 配置Producer的参数 Properties props = new Properties(); props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer"); props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer"); // 创建Kafka Producer实例 Producer<String, String> producer = new KafkaProducer<>(props); // 创建要发送的消息 String topic = "your_topic"; String key = "your_key"; String value = "your_message"; ProducerRecord<String, String> record = new ProducerRecord<>(topic, key, value); // 发送消息Kafka集群 producer.send(record, new Callback() { @Override public void onCompletion(RecordMetadata metadata, Exception exception) { if (exception != null) { System.err.println("Error sending message: " + exception.getMessage()); } else { System.out.println("Message sent successfully! Topic: " + metadata.topic() + ", Partition: " + metadata.partition() + ", Offset: " + metadata.offset()); } } }); // 关闭Kafka Producer producer.close(); } } ``` 请注意,以上示例仅供参考,您需要根据您的实际情况进行适当的配置和修改。此外,还可以根据您选择的编程语言使用相应的Kafka客户端库来发送消息
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值