kafka学习笔记(六) --- JAVA生产者或者消费者是如何管理TCP连接的

apache kafka的所有通信都是基于TCP的,而不是HTTP或者其他协议。为什么?最主要的原因在于TCP和HTTP之间的区别,从社区的角度,开发客户端时,人们能够利用TCP本身提供的一些高级功能,比如多路复用请求以及同时轮询多个连接的能力。多路复用,即将两个或多个数据流合并到一个底层的物理连接中的过程,其实严格来说,TCP并不能多路复用,只是提供可靠的消息交付语义保证,比如自动重传丢失的报文。更严谨地说,作为一个基于报文的协议,TCP能够被用于多路复用连接场景的前提,是上层应用允许发送多条消息。另外一个原因,目前已知的HTTP库在很多编程语言中比较简陋。

  • 生产者如何管理TCP连接

Java生产者API主要的对象就是KafkaProducer。通常开发一个生产者分4步:

构造生产者对象所需的参数对象;

创建KafkaProducer对象实例;

使用该对象的send方法发送消息;

调用close方法关闭生产者并释放各种系统资源。

问题来了,生产者会向kafka集群中指定主题发送消息,必然会涉及与broker创建TCP连接。接下来看看producer客户端如何管理TCP连接。

何时创建:首先,生产者应用在创建KafkaProducer实例是会建立与broker的TCP连接。也就是说,生产者应用会在后台创建并启动一个名为Sender的线程,该线程开始运行时首先会创建于broker的连接。在调用send方法之前,并不知道给那个主题发送消息,更不知道与哪个broker建立连接,所以会连接bootstrap.servers参数指定的所有的broker 。这个参数是producer端核心参数之一,指定了producer启动时要连接的broker地址。如果指定了1000个broker连接信息,就会创建与这1000个broker的TCP连接。一般只需指定3~4台即可。因为一旦producer连接上集群中任意一台broker,就能拿到整个集群broker信息。

在上面的日志输出中,可以发现,在KafkaProducer实例被创建后以及消息被发送前,producer应用就开始与两台broker创建连接。这里在测试环境中,为bootstrap.servers配置了localhost:9092、localhost:9093来模拟不同broker。日志最后一行标明了,producer向某一台broker发送了METADATA请求,尝试获取集群元数据。

讲到这里,我们都知道了,在创建kafkaproducer实例的时候是会建立TCP连接的。不过,TCP连接还可能在两个地方被创建:一个是更新元数据,一个是消息发送时。也就是说,当更新元数据或者消息发送时,发现与某broker当前没有连接,就会创建一个连接。

更新元数据的两个场景:当Producer尝试给一个不存在的主题发送消息时,broker会告诉说这个主体不存在。此时,producer会发送METADATA请求给kafka集群,尝试获取最新的元数据信息;producer通过metadata.max.age.ms参数定期去更新元数据信息。该参数默认值300000,即5分钟。这两个场景都是,一个producer会默认与集群所有broker建立连接,不管是否真的需要传输请求,再加上kafka还支持强制关闭空闲的TCP连接资源,所以这里应该是有优化空间的。试想一下,在一个有着1000台broker集群中,你的producer可能只会与其中3~5台长期通信,但producer会长期与这1000台broker建立连接,一段时间后,大约有995个TCP连接被关闭,这难道不是一种资源浪费?

何时关闭:一种是用户主动关闭;一种是kafka自动关闭。主动关闭,是广义的主动关闭,甚至包括kill -9主动杀掉producer进程,不过还是推荐使用close方法来关闭。第二种是kafka帮你关闭连接,这与Producer端参数connections.max.idle.ms值有关。默认情况是9分钟,即如果在9分钟内,没有任何请求流过某个TCP连接,那么kafka会在broker端自动关闭TCP连接。用户可以再producer端设置connections.max.idle.ms=-1禁掉这种机制,这只是软件层面的“长连接”机制,由于kafka创建的socket连接开启了keepalive,因此keepalive探活机制还是会遵守的。值得注意的是,第二种方式中,TCP连接是在broker端被关闭,但这个链接发起端是客户端,因此在TCP看来,这属于被动关闭场景,即passive close。被动关闭后就会产生大量CLOSE_WAIT连接,因此client端没有机会显示观测到连接中断。

  • 消费者如何管理TCP连接

何时创建:消费者端的程序入口是KafkaConsumer类。和生产者不同的是,构建KafkaConsumer实例的时候不会创建任何连接,主要原因是KafkaProducer构建实例的时候,在后台启动了sender线程,这个线程负责socket连接的创建。从这一点来看,个人觉得KafkaConsumer 的设计要比 KafkaProducer要好。在消费者端,TCP连接是在调用KafkaConsumer.poll方法是创建的。再细粒度的说,再poll方法内部有3个时机可以创键TCP连接。

1. 发起端FindCoordinator请求时

在broker端有个协调者(coordinator)组件驻留在broker端内存中,负责消费者组的组成员管理和各个消费者的位移提交管理。当程序首次调用poll方法时,需要向kafka集群发送一个名为FindCoordinator请求,希望集群告诉它哪个broker是管理它的协调者。不过,消费者应该向哪个broker发送这个请求,理论上可以是集群的任意一台broker。这里,社区做了一点优化:消费者程序会向集群中当前负载最小的那台broker发送请求。负载最小,也就是看与消费者连接的broker中,谁的待发送请求最少,但是这只是消费者端的单向评估,并非是全局角度,所以不一定是最优解。注意,如果是刚开始启动的时候,消费者只能随机选择broker。

2. 连接协调者时

broker处理完上一步发送的FindCoordinator请求之后,会返回响应结果,显示地告诉消费者哪个broker是真正的协调者,然后消费者就会创建与该broker的socket连接。只有成功连入协调者,协调者才能开启正常的组协调操作,比如加入组、等待组分配方案、心跳请求处理、位移获取、位移提交等。

3. 消费数据时

消费者会为每个要消费的分区创建与该分区leader副本所在broker的TCP连接。假设消费者要消费5个分区的数据,这5个分区的leader副本分布在4台broker上,那么消费者在消费时会与这4台broker建立TCP连接。

创建多少个TCP连接:

   

看上面这段日志,第一个日志段,消费者程序创建第一个TCP连接,用于发送FindCoordinator请求。因为是第一个连接,消费者对kafka集群一无所知,因此broker节点的ID 是-1,表示消费者不知道要连接的broker的任何信息。第二个日志段,消费者复用了刚刚建立的连接,发送元数据请求,获取整个集群信息。第三个日志段,消费者开始发送FindCoordinator请求,在十几ms之后,消费者程序成功获悉协调者所在的broker信息,也就是第四个日志段的中橙色的"node_id=2"。

完成这些之后,消费者就知道协调者broker的连接信息,因此在第五个日志段发起第二个socket连接,创建连向localhost:9094的TCP连接。只有连接协调者,消费者进程才能正常开启消费者组的各种功能以及后续的消息消费。这里注意一下,id为什么是2147483645,它是由Java的Interger.MAX_VALUE减去协调者所在broker的真实id, 即2147483645 - 2 得来的。这点是kafka社区特意为之,目的就是为了让组协调请求和真正的数据获取请求使用不同的socket连接。最后三个日志段,消费者又分别创建了新的TCP连接,即连向要消费分区的leader副本所在的broker,这三个id就是 server.property中配置的broker.id值。

总结一下以上的3类TCP连接:确定协调者和获取集群元数据;连接协调者,令其执行组成员管理;执行实际的消息获取。

何时关闭:消费者关闭socket连接也分主动关闭和kafka主动关闭,主动关闭是指手动调用KafkaConsumer.close()方法,或是执行kill命令。kafka自动关闭同样也是由消费者端参数connection.max.idle.ms控制的。但是如果在程序中循环调用poll方法消费消息,上面提到的所有的请求都会被定期发送到broker,因此socket连接上总是能保证有请求发送,从而实现“长连接”效果。

另外,上面提到的第三类连接成功后,消费者程序会废弃掉第一类TCP连接,之后在定期请求元数据时,会改为使用第三类TCP连接。对于运行过一段时间的消费者程序,后台只会有后两类连接存在。但是可能存在一些问题,目前kafka只使用ID这一个维度数据表征socket连接信息,所以第一类连接是无法重用的,这样在实际使用过程中,当connection.max.idle.ms被设置为-1,即禁用定时关闭时,像第一类连接不会被定期清理,只会成为“僵尸”连接。基于这个原因,社区应该考虑更好的解决方案,比如考虑使用<主机、端口、ID>三元组方式定位socket资源,这样或许能少创建一些连接。

 

标注:这个系列文章是本人在极客时间专栏---kafka核心技术与实战中的学习笔记

    https://time.geekbang.org/column/article/101171

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Kafka ,Producer 是用来发送消息到 Kafka 集群的组件。在本篇文章,我们将介绍如何使用 KafkaJava 客户 API 来编写一个简单的 Producer。 1. 引入 Kafka 依赖 首先,需要在 Maven 或 Gradle 构建引入 Kafka 客户依赖: ```xml <dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka-clients</artifactId> <version>2.8.0</version> </dependency> ``` 2. 创建 Producer 实例 接下来,在 Java 代码创建一个 KafkaProducer 实例: ```java Properties props = new Properties(); props.put("bootstrap.servers", "localhost:9092"); props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); Producer<String, String> producer = new KafkaProducer<>(props); ``` 其,bootstrap.servers 是必须设置的属性,用于指定 Kafka 集群至少一个 Broker 的地址。key.serializer 和 value.serializer 用于指定消息的键和值的序列化器。这里我们使用的是 StringSerializer,也可以使用其他序列化器实现自定义序列化逻辑。 3. 发送消息 一旦创建了 KafkaProducer 实例,就可以使用它来发送消息到指定的 Kafka 主题: ```java ProducerRecord<String, String> record = new ProducerRecord<>("test-topic", "key", "value"); producer.send(record); ``` 这里的 ProducerRecord 构造函数,第一个参数是要发送消息的主题名称,第二个参数是消息的键,第三个参数是消息的值。send() 方法用于将 ProducerRecord 发送到 Kafka 集群。 4. 关闭 Producer 在使用完 Producer 后,需要关闭它以释放资源: ```java producer.close(); ``` 完整代码示例: ```java import org.apache.kafka.clients.producer.*; import java.util.Properties; public class KafkaProducerExample { public static void main(String[] args) { Properties props = new Properties(); props.put("bootstrap.servers", "localhost:9092"); props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); Producer<String, String> producer = new KafkaProducer<>(props); ProducerRecord<String, String> record = new ProducerRecord<>("test-topic", "key", "value"); producer.send(record); producer.close(); } } ``` 这就是一个简单的 Kafka Producer 的使用示例。在实际应用,还可以根据需要设置其他属性,例如消息的分区策略、消息的压缩方式等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值