【Kafka】KafkaProducer生产者客户端实现原理

一、发送消息

 	KafkaProducer<String,String> producer = new KafkaProducer<>(props);
    ProducerRecord<String,String> record = new ProducerRecord<>(topic,"hello");
    producer.send(record)

构建的消息对象ProducerRecord,包含了多个属性,而消息体只是其中一个value,其完整定义:

public class ProducerRecord<K,V>{
    String topic //主题
    Integer partition  // 分区号
    Headers headers  //消息头部
    K key  
    V value
    Long timestamp //消息时间戳
    ...
}

发送消息的模式

  1. 发后即忘
producer.send(record)
  1. 同步
producer.send(record).get()

send方法的返回值实际上是Future对象,我们可以使用get方法阻塞等待。另外可以使用Future中的get(long timeout,TimeUnit unit)来实现可超时的阻塞。

KafkaProducer中一般会发生两种类型的异常:

  • 可重试的异常:如网络异常、分区的Leader副本不可用
  • 不可重试的异常:如消息太大

对于可重试的异常,可以配置retries参数自动重试(默认为0),超出重试次数之后再抛出异常。

  1. 异步
producer.send(record,new Callback(){
    @Override
    public void onCompletion(RecordMetadata metadata,Exception exception){
        if(exception != null){
            exception.printStackTrace();
        } else{
            System.out.println(metadata.topic() + "-
                + metadata.partition() + ":" + metadata.offset())
        }
    }
})

在send方法里指定一个Callback的回调函数

二、分区器

消息在通过send方法发往broker过程中,有可能需要经过拦截器(Interceptor)、序列化器(Serializer)和分区器(Partitioner)的一系列作用之后才能被真正地发往broker。

如果ProducerRecord中指定了partition字段,那么直接发往对应的分区。如果没有指定,就需要依赖分区器,根据key字段来计算partition的值。

  • 如果key不为null,那么默认的分区器会对key进行哈希(MurmurHash2)算法,根据得到的哈希值计算分区号
  • 如果key为null,消息将以轮询的方式发往主题内各个分区

三、生产者拦截器

生产者拦截器可以在消息发送前做一些准备工作,比如按照某个规则过滤不符合要求的信息、修改消息的内容等。

其使用就是自定义实现org.apache.kafka.clients.producer.ProducerInterceptor接口。

ProducerInterceptor接口:

public ProducerRecord<K,V> onSend(ProducerRecord<K,V> record);
public void onAcknowledgement(RecoredMetadata metadata,Exception exception);
public void close(); 
  • onSend:消息序列化和计算分区之前
  • onAcknowledgement:消息被应答之前,或者消息发送失败时(运行在Producer的IO线程中,代码逻辑越简单越好,否则影响消息的发送速度)
  • close:关闭拦截器时一些资源清理

四、实现原理

整个生产者客户端由两个线程协调运行:

1. 主线程
kafkaProducer创建消息、通过拦截器、序列化器、分区器、缓存到消息收集器RecordAccumulator。存储格式:< partition , Deque < ProducerBatch >>

  • RecordAccumulator
    作用:缓存消息以便Sender批量发送,减少网络传输的资源消耗以提升性能。
    存储:为每个分区都维护了一个双端队列,队列中的内容是ProducerBatch,即Deque< ProducerBatch >。ProducerBatch中包含一个至多个ProducerRecord
    BufferPool缓存:消息的网络传输以字节byte的形式,在发送之前需要创建一块内存区域来保存对应消息。RecordAccumulator中有一个BufferPool用来实现ByteBuffer的复用。它只对特定大小(默认16k)的ByteBuffer进行管理,其他大小的byteBuffer不会被缓存。

在这里插入图片描述
2. Sender线程

  1. 从RecordAccumulator中双端队列的头部读取消息

  2. 将原本< partition , Deque < ProducerBatch >> 保存形式转变为< BrokerNode , List < ProducerBatch >>

对于生产者KafkaProducer的应用逻辑,也就是用户视角而言,我们只关注向哪个分区中发送哪些消息;

对于网络传输客户端来说,是与具体broker节点建立的连接,向具体的broker发送消息,并不关心属于哪个分区;

所以在这里需要做一个应用逻辑层到网络IO层的转换。

  1. Sender进一步封装为< BrokerNode , Rquest >的形式

  2. 在发送请求之前,会将消息保存到InFilghtQequests中,存储形式为Map < BrokerNode_Id , Deque< Rquest >>,缓存已经发出去,但还没有收到响应的请求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值