Kafka Java API 之Producer源码解析

本文系原创,转载请注明!

原帖地址:http://blog.csdn.net/xeseo/article/details/18315451

从我的OneNote copy过来,格式似乎有点问题,懒得整了。将就着看吧,各位。

  1. Kafka提供了Producer类作为java producerapi该类有syncasync两种发送方式。

     

    默认是sync方式,即producer的调用类在消息真正发送到队列中去以后才返回,其工作原理如下:

     

  2. new Producer

     

    当我们new了一个kafka java api提供的Producer类时,其底层,实际会产生两个核心类的实例:Producer(Scala的,不是java api提供的那个)DefaultEventHandler。在创建的同时,会默认new一个ProducerPool,即我们每new一个javaProducer类,就会有一个scalaProducerEventHandlerProducerPool

     

  3. producer.send()

    当我们调用javaproducer.send方法时,底层调用scalaProducersend方法,其内部其实调的是eventhandler.handle(message)方法。

    1. eventHandler会首先序列化该消息,

                 eventHandler.serialize(events

     

    1. 然后会根据传入的broker信息、topic信息,去取最新的该topicmetadata信息

    BrokerPartitionInfo.updateInfo

     | -> ClientUtils.fetchTopicMetadata   //创建一个topicMetadataRequest,并随机的选取传入的broker信息中任何一个去取metadata,直到取到为止

             | -> val producer: SyncProducer = ProducerPool.createSyncProducer(producerConfig, shuffledBrokers(i)) //对随机选到的broker会创建一个SyncProducer

                         | -> SyncProducer.send  //发送topicMetadataRequest到该broker去取metadata,获得该topic所对应的所有的broker信息

     

    看到这里也就明白了为什么kafkaapi里面并不要求你提供完整的整个kafka集群的broker信息,而是任选一个或几个。因为在这里它会去你提供的broker取该topic的最新的所有的broker信息。

    这里要注意的是,用于发送topicMetadataRequest的SyncProducer虽然是用ProducerPool.createSyncProducer方法建出来的,但用完并不还回pool,而是直接Close,所以会发现有INFO log打出来

    [INFO] <main> Connected toxxx.xxx.xxx.xxx:9092 for producing

    [INFO] <main> Disconnecting fromxxx.xxx.xxx.xxx:9092

    注意:

    这个刷新metadata并不仅在第一次初始化时做。为了能适应kafka broker运行中因为各种原因挂掉、paritition改变等变化,eventHandler会定期的再去刷新一次该metadata,刷新的间隔用参数topic.metadata.refresh.interval.ms定义,默认值是10分钟。

    这里有三点需要强调:

    1. 不调用send, 不会建立socket,不会去定期刷新metadata
    2. 在每次取metadata时,kafka会单独开一个socket去取metadata,开完再关掉。
    3. 根据取得的最新的完整的metadata,刷新Pool中到broker的连接(第一次建立时,pool里面是空的)
    4. 每10分钟的刷新会直接重新把到每个broker的socket连接重建,意味着在这之后的第一个请求会有几百毫秒的延迟。如果不想要该延迟,把topic.metadata.refresh.interval.ms值改为-1,这样只有在发送失败时,才会重新刷新。Kafka的集群中如果某个partition所在的broker挂了,可以检查错误后重启重新加入集群,手动做rebalance,producer的连接会再次断掉,直到rebalance完成,那么刷新后取到的连接着中就会有这个新加入的broker。

    在ClientUtils.fetchTopicMetadata调用完成后,回到BrokerPartitionInfo.updateInfo继续执行,在其末尾,pool会根据上面取得的最新的metadata建立所有的SyncProducer,即Socket通道producerPool.updateProducer(topicsMetadata)

    注意:

    ProducerPool中,SyncProducer的数目是由该topicpartition数目控制的,即每一个SyncProducer对应一个broker,内部封了一个到该brokersocket连接。

    每次刷新时,会把已存在SyncProducerclose掉,即关闭socket连接,然后新建SyncProducer,即新建socket连接,去覆盖老的。

    如果不存在,则直接创建新的。

     

    1. 然后,才是真正发送数据

    dispatchSerializedData(outstandingProduceRequests)

     

    1. 如果发送失败,会进行重试。重试时,又会刷新metadata,而kafkaleader选举需要一定的时间,所以这次刷新可能需要等待,最大等待时间由参数retry.backoff.ms(默认为100)定义。

    重试最大次数由参数message.send.max.retries定义默认为3

     

     

    async方式通过参数producer.type控制,例子:

    Properties p = new Properties();

    props.put("producer.type", "async");

    ProducerConfig config = new ProducerConfig(props);

    producer = new Producer<String, byte[]>(config);

     

    async方式与sync方式的不同在于,在初始化scalaproducer时,会创建一个ProducerSendThread对象。然后,在调用send时,它并不是直接调用eventHandler.handle方法,而是把消息放入一个长度由queue.buffering.max.messages参数定义的队列(默认10000),当队列满足以下两种条件时,会由ProducerSendThread触发eventHandler.handle方法,把队列中的消息作为一个batch发送

    1. 时间超过queue.buffering.max.ms定义的值,默认5000ms
    2. 队列中当前消息个数超过batch.num.messages定义的值,默认200

     

结论:

1. Kafka提供的java api中的Producer,底层只是维护该topic到每个broker的连接,并不是一个传统意义上的连接池。在使用sync方式时,我们应该自己实现一个连接池,里面包含若干Producer对象,以实现最大化写入效率。我自己写了一个简单的:https://github.com/EdisonXu/simple-kafka-producer-pool

2. 在写入的数据频率不高或要求获得写入结果时,应使用sync方式,否则会因async的等待时间引入额外的延迟

3. 在写入的数据频率很高时,应使用async方式,以batch的形式写入,获得最大效率

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值